diff --git a/testdata/tasks/task-dispatcher-date.php b/testdata/tasks/task-dispatcher-date.php new file mode 100644 index 00000000..8abbc4e5 --- /dev/null +++ b/testdata/tasks/task-dispatcher-date.php @@ -0,0 +1,10 @@ +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"; }; diff --git a/threadtaskworker.go b/threadtaskworker.go index 9858acbd..b7eb03d4 100644 --- a/threadtaskworker.go +++ b/threadtaskworker.go @@ -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 diff --git a/threadtaskworker_test.go b/threadtaskworker_test.go index e36003a6..6d65e697 100644 --- a/threadtaskworker_test.go +++ b/threadtaskworker_test.go @@ -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") +} diff --git a/types.c b/types.c index 11e89f9e..4f1c823b 100644 --- a/types.c +++ b/types.c @@ -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; +} \ No newline at end of file diff --git a/types.go b/types.go index 75aabec6..39b927a0 100644 --- a/types.go +++ b/types.go @@ -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) } diff --git a/types.h b/types.h index 89bd9923..54c78144 100644 --- a/types.h +++ b/types.h @@ -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 diff --git a/types_test.go b/types_test.go index 95f94694..be102c00 100644 --- a/types_test.go +++ b/types_test.go @@ -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") }) }