Fixes all leaks.

This commit is contained in:
Alliballibaba
2025-10-15 23:13:05 +02:00
parent 05c4776dd3
commit d56a14bd98
8 changed files with 81 additions and 43 deletions

10
testdata/tasks/task-dispatcher-date.php vendored Normal file
View File

@@ -0,0 +1,10 @@
<?php
require_once __DIR__ . '/../_executor.php';
return function () {
$date = new DateTime('2024-01-01 12:00:00', new DateTimeZone('Europe/Vienna'));
frankenphp_dispatch_task($date);
echo "dispatched tasks\n";
};

View File

@@ -6,11 +6,7 @@ return function () {
$taskCount = $_GET['count'] ?? 0;
$workerName = $_GET['worker'] ?? '';
for ($i = 0; $i < $taskCount; $i++) {
$c = new DateTime();
$c->setTimestamp(time()+123);
$c->setTimezone(new Datetimezone('America/New_York'));
#$c = serialize($c);
frankenphp_dispatch_task($c, $workerName);
frankenphp_dispatch_task("task$i", $workerName);
}
echo "dispatched $taskCount tasks\n";
};

View File

@@ -241,10 +241,11 @@ func go_frankenphp_worker_handle_task(threadIndex C.uintptr_t) *C.zval {
}
// if the task has no callback, forward it to PHP
zval := phpValue(task.arg)
thread.Pin(unsafe.Pointer(zval))
var zval C.zval
phpValue(&zval, task.arg)
thread.Pin(unsafe.Pointer(&zval))
return zval
return &zval
case <-handler.thread.drainChan:
thread.state.markAsWaiting(false)
// send an empty task to drain the thread

View File

@@ -117,3 +117,26 @@ func TestDispatchToMultipleWorkers(t *testing.T) {
assertGetRequest(t, script+"?count=1&worker=worker2", "dispatched 1 tasks")
assertGetRequest(t, script+"?count=1&worker=worker3", "No worker found to handle this task") // fail
}
func TestDispatchInternalDateObject(t *testing.T) {
var buf bytes.Buffer
handler := slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug})
logger := slog.New(handler)
assert.NoError(t, Init(
WithWorkers("worker1", "./testdata/tasks/task-worker.php", 1, AsTaskWorker(true, 0)),
WithNumThreads(2),
WithLogger(logger),
))
assertGetRequest(t, "http://example.com/testdata/tasks/task-dispatcher-date.php", "dispatched task")
time.Sleep(10 * time.Millisecond)
Shutdown()
// task output appears in logs at info level
logOutput := buf.String()
assert.Contains(t, logOutput, "object(DateTime)")
assert.Contains(t, logOutput, "2024")
assert.Contains(t, logOutput, "Europe/Vienna")
}

View File

@@ -78,3 +78,8 @@ void __zval_unserialize__(zval *retval, zend_string *str) {
zval_ptr_dtor(&func);
zend_string_release(str);
}
zval *__init_zval__() {
zval *zv = (zval *)emalloc(sizeof(zval));
return zv;
}

View File

@@ -198,14 +198,16 @@ func phpArray(entries map[string]any, order []string) unsafe.Pointer {
zendArray = createNewArray((uint32)(len(order)))
for _, key := range order {
val := entries[key]
zval := phpValue(val)
C.zend_hash_str_update(zendArray, toUnsafeChar(key), C.size_t(len(key)), zval)
var zval C.zval
phpValue(&zval, val)
C.zend_hash_str_update(zendArray, toUnsafeChar(key), C.size_t(len(key)), &zval)
}
} else {
zendArray = createNewArray((uint32)(len(entries)))
for key, val := range entries {
zval := phpValue(val)
C.zend_hash_str_update(zendArray, toUnsafeChar(key), C.size_t(len(key)), zval)
var zval C.zval
phpValue(&zval, val)
C.zend_hash_str_update(zendArray, toUnsafeChar(key), C.size_t(len(key)), &zval)
}
}
@@ -216,8 +218,9 @@ func phpArray(entries map[string]any, order []string) unsafe.Pointer {
func PHPPackedArray(slice []any) unsafe.Pointer {
zendArray := createNewArray((uint32)(len(slice)))
for _, val := range slice {
zval := phpValue(val)
C.zend_hash_next_index_insert(zendArray, zval)
var zval C.zval
phpValue(&zval, val)
C.zend_hash_next_index_insert(zendArray, &zval)
}
return unsafe.Pointer(zendArray)
@@ -280,43 +283,41 @@ func goValue(zval *C.zval) any {
// EXPERIMENTAL: PHPValue converts a Go any to a PHP zval
func PHPValue(value any) unsafe.Pointer {
return unsafe.Pointer(phpValue(value))
var zval C.zval // TODO: emalloc?
phpValue(&zval, value)
return unsafe.Pointer(&zval)
}
func phpValue(value any) *C.zval {
var zval C.zval
func phpValue(zval *C.zval, value any) {
switch v := value.(type) {
case nil:
C.__zval_null__(&zval)
C.__zval_null__(zval)
case bool:
C.__zval_bool__(&zval, C._Bool(v))
C.__zval_bool__(zval, C._Bool(v))
case int:
C.__zval_long__(&zval, C.zend_long(v))
C.__zval_long__(zval, C.zend_long(v))
case int64:
C.__zval_long__(&zval, C.zend_long(v))
C.__zval_long__(zval, C.zend_long(v))
case float64:
C.__zval_double__(&zval, C.double(v))
C.__zval_double__(zval, C.double(v))
case string:
if v == "" {
C.__zval_empty_string__(&zval)
C.__zval_empty_string__(zval)
break
}
str := (*C.zend_string)(PHPString(v, false))
C.__zval_string__(&zval, str)
C.__zval_string__(zval, str)
case AssociativeArray:
C.__zval_arr__(&zval, (*C.zend_array)(PHPAssociativeArray(v)))
C.__zval_arr__(zval, (*C.zend_array)(PHPAssociativeArray(v)))
case map[string]any:
C.__zval_arr__(&zval, (*C.zend_array)(PHPMap(v)))
C.__zval_arr__(zval, (*C.zend_array)(PHPMap(v)))
case []any:
return (*C.zval)(PHPPackedArray(v))
C.__zval_arr__(zval, (*C.zend_array)(PHPPackedArray(v)))
case Object:
phpObject(&zval, v)
phpObject(zval, v)
default:
panic(fmt.Sprintf("unsupported Go type %T", v))
}
return &zval
}
func GoObject(obj unsafe.Pointer) Object {
@@ -334,13 +335,13 @@ func goObject(obj *C.zend_object) Object {
className := GoString(unsafe.Pointer(classEntry.name))
//C.instanceof_function(obj.ce, dateTimeCe)
if C.is_internal_class(classEntry){
return Object{
if C.is_internal_class(classEntry) {
return Object{
ClassName: className,
serialized: C.__zval_serialize__(obj),
ce: classEntry,
ce: classEntry,
}
}
}
props := make(map[string]any)
@@ -410,13 +411,14 @@ func phpObject(zv *C.zval, obj Object) {
C.object_init_ex(zv, classEntry)
var zendObj *C.zend_object
zendObj = (*C.zend_object)(extractZvalValue(zv, C.IS_OBJECT))
zendObj = *(**C.zend_object)(unsafe.Pointer(&zv.value[0]))
// set the properties
for key, val := range obj.Props {
zval := phpValue(val) // TODO: put on stack
C.zend_update_property(classEntry, zendObj, toUnsafeChar(key), C.size_t(len(key)), zval)
C.zval_ptr_dtor(zval)
var zval C.zval
phpValue(&zval, val)
C.zend_update_property(classEntry, zendObj, toUnsafeChar(key), C.size_t(len(key)), &zval)
C.zval_ptr_dtor(&zval)
}
// TODO: wakeup?
@@ -471,5 +473,5 @@ func zendStringRelease(p unsafe.Pointer) {
func zendHashDestroy(p unsafe.Pointer) {
ht := (*C.zend_array)(p)
C.zend_hash_destroy(ht)
C.zend_array_destroy(ht)
}

View File

@@ -26,5 +26,6 @@ zend_array *__zend_new_array__(uint32_t size);
bool is_internal_class(zend_class_entry *entry);
zend_string *__zval_serialize__(zend_object *obj);
void __zval_unserialize__(zval *retval, zend_string *str);
zval *__init_zval__();
#endif

View File

@@ -163,9 +163,9 @@ func TestPHPObject(t *testing.T) {
phpObject := PHPObject(originalObject)
defer zvalPtrDtor(phpObject)
convertedObject := GoObject(phpObject)
assert.Equal(t, originalObject.ClassName, convertedObject.ClassName, "nested mixed array should be equal after conversion")
assert.Equal(t, originalObject.Props, convertedObject.Props, "nested mixed array should be equal after conversion")
convertedObject := GoObject(phpObject)
assert.Equal(t, originalObject.ClassName, convertedObject.ClassName, "object class should be equal after conversion")
assert.Equal(t, originalObject.Props, convertedObject.Props, "object props should be equal after conversion")
})
}