Skip to content

Commit

Permalink
Refactors new thread loop approach.
Browse files Browse the repository at this point in the history
  • Loading branch information
Alliballibaba2 committed Nov 15, 2024
1 parent ecce5d5 commit 06ebd67
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 157 deletions.
84 changes: 84 additions & 0 deletions env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package frankenphp

// #include "frankenphp.h"
import "C"
import (
"os"
"strings"
"unsafe"
)

//export go_putenv
func go_putenv(str *C.char, length C.int) C.bool {
// Create a byte slice from C string with a specified length
s := C.GoBytes(unsafe.Pointer(str), length)

// Convert byte slice to string
envString := string(s)

// Check if '=' is present in the string
if key, val, found := strings.Cut(envString, "="); found {
if os.Setenv(key, val) != nil {
return false // Failure
}
} else {
// No '=', unset the environment variable
if os.Unsetenv(envString) != nil {
return false // Failure
}
}

return true // Success
}

//export go_getfullenv
func go_getfullenv(threadIndex C.uintptr_t) (*C.go_string, C.size_t) {
thread := phpThreads[threadIndex]

env := os.Environ()
goStrings := make([]C.go_string, len(env)*2)

for i, envVar := range env {
key, val, _ := strings.Cut(envVar, "=")
goStrings[i*2] = C.go_string{C.size_t(len(key)), thread.pinString(key)}
goStrings[i*2+1] = C.go_string{C.size_t(len(val)), thread.pinString(val)}
}

value := unsafe.SliceData(goStrings)
thread.Pin(value)

return value, C.size_t(len(env))
}

//export go_getenv
func go_getenv(threadIndex C.uintptr_t, name *C.go_string) (C.bool, *C.go_string) {
thread := phpThreads[threadIndex]

// Create a byte slice from C string with a specified length
envName := C.GoStringN(name.data, C.int(name.len))

// Get the environment variable value
envValue, exists := os.LookupEnv(envName)
if !exists {
// Environment variable does not exist
return false, nil // Return 0 to indicate failure
}

// Convert Go string to C string
value := &C.go_string{C.size_t(len(envValue)), thread.pinString(envValue)}
thread.Pin(value)

return true, value // Return 1 to indicate success
}

//export go_sapi_getenv
func go_sapi_getenv(threadIndex C.uintptr_t, name *C.go_string) *C.char {
envName := C.GoStringN(name.data, C.int(name.len))

envValue, exists := os.LookupEnv(envName)
if !exists {
return nil
}

return phpThreads[threadIndex].pinCString(envValue)
}
4 changes: 2 additions & 2 deletions frankenphp.c
Original file line number Diff line number Diff line change
Expand Up @@ -830,7 +830,7 @@ static void *php_thread(void *arg) {

// perform work until go signals to stop
while (true) {
char *scriptName = go_frankenphp_on_thread_work(thread_index);
char *scriptName = go_frankenphp_before_script_execution(thread_index);

// if the script name is NULL, the thread should exit
if (scriptName == NULL) {
Expand All @@ -840,7 +840,7 @@ static void *php_thread(void *arg) {
// if the script name is not empty, execute the PHP script
if (strlen(scriptName) != 0) {
int exit_status = frankenphp_execute_script(scriptName);
go_frankenphp_after_thread_work(thread_index, exit_status);
go_frankenphp_after_script_execution(thread_index, exit_status);
}
}

Expand Down
88 changes: 4 additions & 84 deletions frankenphp.go
Original file line number Diff line number Diff line change
Expand Up @@ -476,91 +476,10 @@ func ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) error
return nil
}

//export go_putenv
func go_putenv(str *C.char, length C.int) C.bool {
// Create a byte slice from C string with a specified length
s := C.GoBytes(unsafe.Pointer(str), length)

// Convert byte slice to string
envString := string(s)

// Check if '=' is present in the string
if key, val, found := strings.Cut(envString, "="); found {
if os.Setenv(key, val) != nil {
return false // Failure
}
} else {
// No '=', unset the environment variable
if os.Unsetenv(envString) != nil {
return false // Failure
}
}

return true // Success
}

//export go_getfullenv
func go_getfullenv(threadIndex C.uintptr_t) (*C.go_string, C.size_t) {
thread := phpThreads[threadIndex]

env := os.Environ()
goStrings := make([]C.go_string, len(env)*2)

for i, envVar := range env {
key, val, _ := strings.Cut(envVar, "=")
k := unsafe.StringData(key)
v := unsafe.StringData(val)
thread.Pin(k)
thread.Pin(v)

goStrings[i*2] = C.go_string{C.size_t(len(key)), (*C.char)(unsafe.Pointer(k))}
goStrings[i*2+1] = C.go_string{C.size_t(len(val)), (*C.char)(unsafe.Pointer(v))}
}

value := unsafe.SliceData(goStrings)
thread.Pin(value)

return value, C.size_t(len(env))
}

//export go_getenv
func go_getenv(threadIndex C.uintptr_t, name *C.go_string) (C.bool, *C.go_string) {
thread := phpThreads[threadIndex]

// Create a byte slice from C string with a specified length
envName := C.GoStringN(name.data, C.int(name.len))

// Get the environment variable value
envValue, exists := os.LookupEnv(envName)
if !exists {
// Environment variable does not exist
return false, nil // Return 0 to indicate failure
}

// Convert Go string to C string
val := unsafe.StringData(envValue)
thread.Pin(val)
value := &C.go_string{C.size_t(len(envValue)), (*C.char)(unsafe.Pointer(val))}
thread.Pin(value)

return true, value // Return 1 to indicate success
}

//export go_sapi_getenv
func go_sapi_getenv(threadIndex C.uintptr_t, name *C.go_string) *C.char {
envName := C.GoStringN(name.data, C.int(name.len))

envValue, exists := os.LookupEnv(envName)
if !exists {
return nil
}

return phpThreads[threadIndex].pinCString(envValue)
}

func handleRequest(thread *phpThread) {
select {
case <-done:
// no script should be executed if the server is shutting down
thread.scriptName = ""
return

Expand All @@ -570,8 +489,10 @@ func handleRequest(thread *phpThread) {

if err := updateServerContext(thread, r, true, false); err != nil {
rejectRequest(fc.responseWriter, err.Error())
thread.scriptName = ""
afterRequest(thread, 0)
thread.Unpin()
// no script should be executed if the request was rejected
thread.scriptName = ""
return
}

Expand All @@ -585,7 +506,6 @@ func afterRequest(thread *phpThread, exitStatus int) {
fc.exitStatus = exitStatus
maybeCloseContext(fc)
thread.mainRequest = nil
thread.Unpin()
}

func maybeCloseContext(fc *FrankenPHPContext) {
Expand Down
38 changes: 20 additions & 18 deletions php_thread.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package frankenphp

// #include <stdint.h>
// #include <stdbool.h>
// #include <php_variables.h>
// #include "frankenphp.h"
import "C"
import (
Expand Down Expand Up @@ -31,9 +28,9 @@ type phpThread struct {
// right before the first work iteration
onStartup func(*phpThread)
// the actual work iteration (done in a loop)
onWork func(*phpThread)
beforeScriptExecution func(*phpThread)
// after the work iteration is done
onWorkDone func(*phpThread, int)
afterScriptExecution func(*phpThread, int)
// after the thread is done
onShutdown func(*phpThread)
// chan to signal the thread to stop the current work iteration
Expand All @@ -56,7 +53,7 @@ func (thread *phpThread) getActiveRequest() *http.Request {
func (thread *phpThread) setInactive() {
thread.isActive.Store(false)
thread.scriptName = ""
thread.onWork = func(thread *phpThread) {
thread.beforeScriptExecution = func(thread *phpThread) {
thread.requestChan = make(chan *http.Request)
select {
case <-done:
Expand All @@ -65,7 +62,12 @@ func (thread *phpThread) setInactive() {
}
}

func (thread *phpThread) setActive(onStartup func(*phpThread), onWork func(*phpThread), onWorkDone func(*phpThread, int), onShutdown func(*phpThread)) {
func (thread *phpThread) setActive(
onStartup func(*phpThread),
beforeScriptExecution func(*phpThread),
afterScriptExecution func(*phpThread, int),
onShutdown func(*phpThread),
) {
thread.isActive.Store(true)

// to avoid race conditions, the thread sets its own hooks on startup
Expand All @@ -74,9 +76,9 @@ func (thread *phpThread) setActive(onStartup func(*phpThread), onWork func(*phpT
thread.onShutdown(thread)
}
thread.onStartup = onStartup
thread.onWork = onWork
thread.beforeScriptExecution = beforeScriptExecution
thread.onShutdown = onShutdown
thread.onWorkDone = onWorkDone
thread.afterScriptExecution = afterScriptExecution
if thread.onStartup != nil {
thread.onStartup(thread)
}
Expand All @@ -100,9 +102,9 @@ func (thread *phpThread) pinCString(s string) *C.char {
return thread.pinString(s + "\x00")
}

//export go_frankenphp_on_thread_work
func go_frankenphp_on_thread_work(threadIndex C.uintptr_t) *C.char {
// first check if FrankPHP is shutting down
//export go_frankenphp_before_script_execution
func go_frankenphp_before_script_execution(threadIndex C.uintptr_t) *C.char {
// returning nil signals the thread to stop
if threadsAreDone.Load() {
return nil
}
Expand All @@ -121,21 +123,21 @@ func go_frankenphp_on_thread_work(threadIndex C.uintptr_t) *C.char {
}
}

// do the actual work
thread.onWork(thread)
// execute a hook before the script is executed
thread.beforeScriptExecution(thread)

// return the name of the PHP script that should be executed
return thread.pinCString(thread.scriptName)
}

//export go_frankenphp_after_thread_work
func go_frankenphp_after_thread_work(threadIndex C.uintptr_t, exitStatus C.int) {
//export go_frankenphp_after_script_execution
func go_frankenphp_after_script_execution(threadIndex C.uintptr_t, exitStatus C.int) {
thread := phpThreads[threadIndex]
if exitStatus < 0 {
panic(ScriptExecutionError)
}
if thread.onWorkDone != nil {
thread.onWorkDone(thread, int(exitStatus))
if thread.afterScriptExecution != nil {
thread.afterScriptExecution(thread, int(exitStatus))
}
thread.Unpin()
}
Expand Down
1 change: 0 additions & 1 deletion php_threads.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package frankenphp

// #include <stdint.h>
// #include "frankenphp.h"
import "C"
import (
Expand Down
18 changes: 11 additions & 7 deletions php_threads_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,18 @@ func TestStartAndStop100PHPThreadsThatDoNothing(t *testing.T) {
readyThreads.Add(1)
}
},
// onWork => while the thread is running (we stop here immediately)
// beforeScriptExecution => we stop here immediately
func(thread *phpThread) {
if thread.threadIndex == newThread.threadIndex {
workingThreads.Add(1)
}
workWG.Done()
newThread.setInactive()
},
nil,
// afterScriptExecution => no script is executed, we shouldn't reach here
func(thread *phpThread, exitStatus int) {
panic("hook afterScriptExecution should not be called here")
},
// onShutdown => after the thread is done
func(thread *phpThread) {
if thread.threadIndex == newThread.threadIndex {
Expand Down Expand Up @@ -94,7 +97,7 @@ func TestSleep10000TimesIn100Threads(t *testing.T) {
thread.mainRequest = r
thread.scriptName = scriptPath
},
// onWork => execute the sleep.php script until we reach maxExecutions
// beforeScriptExecution => execute the sleep.php script until we reach maxExecutions
func(thread *phpThread) {
executionMutex.Lock()
if executionCount >= maxExecutions {
Expand All @@ -106,9 +109,9 @@ func TestSleep10000TimesIn100Threads(t *testing.T) {
workWG.Done()
executionMutex.Unlock()
},
// onWorkDone => check the exit status of the script
func(thread *phpThread, existStatus int) {
if int(existStatus) != 0 {
// afterScriptExecution => check the exit status of the script
func(thread *phpThread, exitStatus int) {
if int(exitStatus) != 0 {
panic("script execution failed: " + scriptPath)
}
},
Expand Down Expand Up @@ -144,12 +147,13 @@ func TestStart100ThreadsAndConvertThemToDifferentThreads10Times(t *testing.T) {
func(thread *phpThread) {
startUpTypes[numberOfConversion].Add(1)
},
// onWork => while the thread is running
// beforeScriptExecution => while the thread is running
func(thread *phpThread) {
workTypes[numberOfConversion].Add(1)
thread.setInactive()
workWG.Done()
},
// afterScriptExecution => we don't execute a script
nil,
// onShutdown => after the thread is done
func(thread *phpThread) {
Expand Down
Loading

0 comments on commit 06ebd67

Please sign in to comment.