3535#include "zend_exceptions.h"
3636#include "zend_closures.h"
3737
38- /* The hook names, indexed by php_async_hook_id: used only to map the
39- * incoming array keys (the Async\SchedulerHook class constants) . */
38+ /* The Async\Scheduler method name for each hook (lower-cased, as stored in the
39+ * class function table), indexed by php_async_hook_id . */
4040static const struct {
4141 const char * name ;
4242 size_t len ;
43- } php_async_hook_names [PHP_ASYNC_HOOK_COUNT ] = {
43+ } php_async_hook_methods [PHP_ASYNC_HOOK_COUNT ] = {
4444 [PHP_ASYNC_HOOK_LAUNCH ] = { ZEND_STRL ("launch" ) },
4545 [PHP_ASYNC_HOOK_SHUTDOWN ] = { ZEND_STRL ("shutdown" ) },
46- [PHP_ASYNC_HOOK_INTERCEPT_FIBER ] = { ZEND_STRL ("intercept_fiber " ) },
47- [PHP_ASYNC_HOOK_ENQUEUE ] = { ZEND_STRL ("enqueue_coroutine " ) },
46+ [PHP_ASYNC_HOOK_INTERCEPT_FIBER ] = { ZEND_STRL ("interceptfiber " ) },
47+ [PHP_ASYNC_HOOK_ENQUEUE ] = { ZEND_STRL ("enqueue " ) },
4848 [PHP_ASYNC_HOOK_SUSPEND ] = { ZEND_STRL ("suspend" ) },
4949 [PHP_ASYNC_HOOK_RESUME ] = { ZEND_STRL ("resume" ) },
5050 [PHP_ASYNC_HOOK_CANCEL ] = { ZEND_STRL ("cancel" ) },
51- [PHP_ASYNC_HOOK_CONTEXT_FIND ] = { ZEND_STRL ("context_find " ) },
52- [PHP_ASYNC_HOOK_CONTEXT_SET ] = { ZEND_STRL ("context_set " ) },
53- [PHP_ASYNC_HOOK_CONTEXT_UNSET ] = { ZEND_STRL ("context_unset " ) },
51+ [PHP_ASYNC_HOOK_CONTEXT_FIND ] = { ZEND_STRL ("contextfind " ) },
52+ [PHP_ASYNC_HOOK_CONTEXT_SET ] = { ZEND_STRL ("contextset " ) },
53+ [PHP_ASYNC_HOOK_CONTEXT_UNSET ] = { ZEND_STRL ("contextunset " ) },
5454 [PHP_ASYNC_HOOK_DEFER ] = { ZEND_STRL ("defer" ) },
5555};
5656
57+ /* Class entries, filled by zend_register_scheduler_hook(). */
58+ static zend_class_entry * async_ce_Scheduler ;
59+ static zend_class_entry * async_ce_AbstractScheduler ;
60+
5761/* The storage lives in the per-thread async globals: correct under ZTS,
5862 * request-local by construction. */
5963#define PHP_ASYNC_HANDLERS (ZEND_ASYNC_G(scheduler_hooks))
6064#define PHP_ASYNC_HOOK (id ) (&PHP_ASYNC_HANDLERS.hooks[(id)])
6165
62- /* Read the callable for `id` from the incoming array into storage. */
63- static bool php_async_hook_take (HashTable * array , php_async_hook_id id )
66+ /* Bind hook `id` to the scheduler's method of the same name. A hook is
67+ * considered "provided" only when the method is overridden below
68+ * Async\AbstractScheduler; a method that resolves to AbstractScheduler is its
69+ * inert default, so the engine keeps its own behaviour for that hook. */
70+ static void php_async_hook_bind (zend_object * scheduler , php_async_hook_id id )
6471{
6572 php_async_hook_t * hook = PHP_ASYNC_HOOK (id );
66- zval * entry = zend_hash_str_find (array , php_async_hook_names [id ].name , php_async_hook_names [id ].len );
73+ zend_function * fn = zend_hash_str_find_ptr (& scheduler -> ce -> function_table ,
74+ php_async_hook_methods [id ].name , php_async_hook_methods [id ].len );
6775
68- if (entry == NULL ) {
76+ if (fn == NULL || fn -> common . scope == async_ce_AbstractScheduler ) {
6977 hook -> set = false;
70- return true ;
78+ return ;
7179 }
7280
73- char * error = NULL ;
74-
75- if (zend_fcall_info_init (entry , 0 , & hook -> fci , & hook -> fcc , NULL , & error ) != SUCCESS ) {
76- zend_type_error ("Async scheduler hook \"%s\" must be a valid callable: %s" ,
77- php_async_hook_names [id ].name , error != NULL ? error : "unknown error" );
78- if (error != NULL ) {
79- efree (error );
80- }
81-
82- return false;
83- }
81+ memset (& hook -> fci , 0 , sizeof (hook -> fci ));
82+ memset (& hook -> fcc , 0 , sizeof (hook -> fcc ));
8483
85- if ( error != NULL ) {
86- efree ( error ) ;
87- }
84+ hook -> fci . size = sizeof ( hook -> fci );
85+ hook -> fci . object = scheduler ;
86+ ZVAL_UNDEF ( & hook -> fci . function_name );
8887
89- /* Keep the callable alive for the whole request. */
90- Z_TRY_ADDREF (hook -> fci .function_name );
91- if (hook -> fci .object != NULL ) {
92- GC_ADDREF (hook -> fci .object );
93- }
88+ hook -> fcc .function_handler = fn ;
89+ hook -> fcc .object = scheduler ;
90+ hook -> fcc .called_scope = scheduler -> ce ;
9491
9592 hook -> set = true;
96- return true;
9793}
9894
9995static void php_async_hook_release (php_async_hook_t * hook )
10096{
101- if (!hook -> set ) {
102- return ;
103- }
104-
105- zval_ptr_dtor (& hook -> fci .function_name );
106-
107- if (hook -> fci .object != NULL ) {
108- OBJ_RELEASE (hook -> fci .object );
109- }
110-
97+ /* The scheduler object (and thus the bound method) is owned once by the
98+ * handlers container; nothing per-hook to release. */
11199 hook -> set = false;
112100}
113101
@@ -383,6 +371,11 @@ static void php_async_handlers_reset(void)
383371 php_async_hook_release (PHP_ASYNC_HOOK (id ));
384372 }
385373
374+ if (PHP_ASYNC_HANDLERS .scheduler != NULL ) {
375+ OBJ_RELEASE (PHP_ASYNC_HANDLERS .scheduler );
376+ PHP_ASYNC_HANDLERS .scheduler = NULL ;
377+ }
378+
386379 if (PHP_ASYNC_HANDLERS .module != NULL ) {
387380 zend_string_release (PHP_ASYNC_HANDLERS .module );
388381 PHP_ASYNC_HANDLERS .module = NULL ;
@@ -451,11 +444,11 @@ static void php_async_build_api(zend_async_scheduler_api_t *api)
451444ZEND_METHOD (Async_SchedulerHook , register )
452445{
453446 zend_string * module ;
454- HashTable * hooks ;
447+ zend_object * scheduler ;
455448
456449 ZEND_PARSE_PARAMETERS_START (2 , 2 )
457450 Z_PARAM_STR (module )
458- Z_PARAM_ARRAY_HT ( hooks )
451+ Z_PARAM_OBJ_OF_CLASS ( scheduler , async_ce_Scheduler )
459452 ZEND_PARSE_PARAMETERS_END ();
460453
461454 /* A scheduler is registered once per process — by a C extension or by
@@ -466,12 +459,11 @@ ZEND_METHOD(Async_SchedulerHook, register)
466459 }
467460
468461 for (php_async_hook_id id = 0 ; id < PHP_ASYNC_HOOK_COUNT ; id ++ ) {
469- if (!php_async_hook_take (hooks , id )) {
470- php_async_handlers_reset ();
471- RETURN_THROWS ();
472- }
462+ php_async_hook_bind (scheduler , id );
473463 }
474464
465+ PHP_ASYNC_HANDLERS .scheduler = scheduler ;
466+ GC_ADDREF (scheduler );
475467 PHP_ASYNC_HANDLERS .module = zend_string_copy (module );
476468 PHP_ASYNC_HANDLERS .active = true;
477469
@@ -533,8 +525,64 @@ ZEND_METHOD(Async_SchedulerHook, defer)
533525 }
534526}
535527
528+ /////////////////////////////////////////////////////////////////////
529+ /// Async\AbstractScheduler default hooks
530+ /////////////////////////////////////////////////////////////////////
531+
532+ /* These bodies are the inert defaults: the bridge detects them by scope and
533+ * keeps the engine's own behaviour instead of calling them. They only run when
534+ * userland invokes them directly; the argument count/types are already checked
535+ * against the arginfo at the call boundary, so the bodies just return. */
536+
537+ ZEND_METHOD (Async_AbstractScheduler , interceptFiber )
538+ {
539+ RETURN_NULL ();
540+ }
541+
542+ ZEND_METHOD (Async_AbstractScheduler , resume )
543+ {
544+ RETURN_FALSE ;
545+ }
546+
547+ ZEND_METHOD (Async_AbstractScheduler , cancel )
548+ {
549+ RETURN_FALSE ;
550+ }
551+
552+ ZEND_METHOD (Async_AbstractScheduler , defer )
553+ {
554+ RETURN_FALSE ;
555+ }
556+
557+ ZEND_METHOD (Async_AbstractScheduler , launch )
558+ {
559+ RETURN_TRUE ;
560+ }
561+
562+ ZEND_METHOD (Async_AbstractScheduler , shutdown )
563+ {
564+ RETURN_TRUE ;
565+ }
566+
567+ ZEND_METHOD (Async_AbstractScheduler , contextFind )
568+ {
569+ RETURN_NULL ();
570+ }
571+
572+ ZEND_METHOD (Async_AbstractScheduler , contextSet )
573+ {
574+ RETURN_FALSE ;
575+ }
576+
577+ ZEND_METHOD (Async_AbstractScheduler , contextUnset )
578+ {
579+ RETURN_FALSE ;
580+ }
581+
536582void zend_register_scheduler_hook (void )
537583{
584+ async_ce_Scheduler = register_class_Async_Scheduler ();
585+ async_ce_AbstractScheduler = register_class_Async_AbstractScheduler (async_ce_Scheduler );
538586 register_class_Async_SchedulerHook ();
539587}
540588
0 commit comments