Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new build tag: newworker #1186

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions backoff.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package frankenphp

import (
"sync"
"time"
)

type exponentialBackoff struct {
backoff time.Duration
failureCount int
mu sync.RWMutex
maxBackoff time.Duration
minBackoff time.Duration
maxConsecutiveFailures int
}

// recordSuccess resets the backoff and failureCount
func (e *exponentialBackoff) recordSuccess() {
e.mu.Lock()
e.failureCount = 0
e.backoff = e.minBackoff
e.mu.Unlock()
}

// recordFailure increments the failure count and increases the backoff, it returns true if maxConsecutiveFailures has been reached
func (e *exponentialBackoff) recordFailure() bool {
e.mu.Lock()
e.failureCount += 1
if e.backoff < e.minBackoff {
e.backoff = e.minBackoff
}

e.backoff = min(e.backoff*2, e.maxBackoff)

e.mu.Unlock()
return e.failureCount >= e.maxConsecutiveFailures
}

// wait sleeps for the backoff duration if failureCount is non-zero.
// NOTE: this is not tested and should be kept 'obviously correct' (i.e., simple)
func (e *exponentialBackoff) wait() {
e.mu.RLock()
if e.failureCount == 0 {
e.mu.RUnlock()

return
}
e.mu.RUnlock()

time.Sleep(e.backoff)
}
41 changes: 41 additions & 0 deletions backoff_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package frankenphp

import (
"github.com/stretchr/testify/assert"
"testing"
"time"
)

func TestExponentialBackoff_Reset(t *testing.T) {
e := &exponentialBackoff{
maxBackoff: 5 * time.Second,
minBackoff: 500 * time.Millisecond,
maxConsecutiveFailures: 3,
}

assert.False(t, e.recordFailure())
assert.False(t, e.recordFailure())
e.recordSuccess()

e.mu.RLock()
defer e.mu.RUnlock()
assert.Equal(t, 0, e.failureCount, "expected failureCount to be reset to 0")
assert.Equal(t, e.backoff, e.minBackoff, "expected backoff to be reset to minBackoff")
}

func TestExponentialBackoff_Trigger(t *testing.T) {
e := &exponentialBackoff{
maxBackoff: 500 * 3 * time.Millisecond,
minBackoff: 500 * time.Millisecond,
maxConsecutiveFailures: 3,
}

assert.False(t, e.recordFailure())
assert.False(t, e.recordFailure())
assert.True(t, e.recordFailure())

e.mu.RLock()
defer e.mu.RUnlock()
assert.Equal(t, e.failureCount, e.maxConsecutiveFailures, "expected failureCount to be maxConsecutiveFailures")
assert.Equal(t, e.backoff, e.maxBackoff, "expected backoff to be maxBackoff")
}
74 changes: 74 additions & 0 deletions env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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 {
envString := C.GoStringN(str, length)

// Check if '=' is present in the string
if key, val, found := strings.Cut(envString, "="); found {
return os.Setenv(key, val) == nil
}

// No '=', unset the environment variable
return os.Unsetenv(envString) == nil
}

//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)
}
28 changes: 28 additions & 0 deletions frankenphp.c
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,11 @@ PHP_FUNCTION(frankenphp_finish_request) { /* {{{ */
php_header();

if (ctx->has_active_request) {
#ifdef NEW_WORKER
go_frankenphp_finish_php_request(thread_index);
#else
go_frankenphp_finish_request(thread_index, false);
#endif
}

ctx->finished = true;
Expand Down Expand Up @@ -443,7 +447,11 @@ PHP_FUNCTION(frankenphp_handle_request) {

frankenphp_worker_request_shutdown();
ctx->has_active_request = false;
#ifdef NEW_WORKER
go_frankenphp_finish_worker_request(thread_index);
#else
go_frankenphp_finish_request(thread_index, true);
#endif

RETURN_TRUE;
}
Expand Down Expand Up @@ -832,15 +840,35 @@ static void *php_thread(void *arg) {
cfg_get_string("filter.default", &default_filter);
should_filter_var = default_filter != NULL;

#ifdef NEW_WORKER
go_frankenphp_on_thread_startup(thread_index);

while(true) {
char *scriptName = go_frankenphp_before_script_execution(thread_index);

// if the script name is NULL, the thread should exit
if (scriptName == NULL || scriptName[0] == '\0') {
break;
}

int exit_status = frankenphp_execute_script(scriptName);
go_frankenphp_after_script_execution(thread_index, exit_status);
}
#else
while (go_handle_request(thread_index)) {
}
#endif

go_frankenphp_release_known_variable_keys(thread_index);

#ifdef ZTS
ts_free_thread();
#endif

#ifdef NEW_WORKER
go_frankenphp_on_thread_shutdown(thread_index);
#endif

return NULL;
}

Expand Down
82 changes: 0 additions & 82 deletions frankenphp.go
Original file line number Diff line number Diff line change
Expand Up @@ -505,88 +505,6 @@ 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)
}

//export go_handle_request
func go_handle_request(threadIndex C.uintptr_t) bool {
select {
Expand Down
Loading