@@ -85,13 +85,27 @@ HashTable *main_thread_env = NULL;
8585
8686__thread uintptr_t thread_index ;
8787__thread bool is_worker_thread = false;
88+ __thread bool is_http_thread = true;
89+ __thread int sidekick_argc = 0 ;
90+ __thread char * * sidekick_argv = NULL ;
8891__thread HashTable * sandboxed_env = NULL ;
8992
90- void frankenphp_update_local_thread_context (bool is_worker ) {
93+ void frankenphp_update_local_thread_context (bool is_worker , bool httpEnabled ) {
9194 is_worker_thread = is_worker ;
95+ is_http_thread = httpEnabled ;
9296
9397 /* workers should keep running if the user aborts the connection */
9498 PG (ignore_user_abort ) = is_worker ? 1 : original_user_abort_setting ;
99+
100+ /* Sidekick workers are long-running, disable execution timeout */
101+ if (is_worker && !httpEnabled ) {
102+ zend_unset_timeout ();
103+ }
104+ }
105+
106+ void frankenphp_set_thread_argv (int argc , char * * argv ) {
107+ sidekick_argc = argc ;
108+ sidekick_argv = argv ;
95109}
96110
97111static void frankenphp_update_request_context () {
@@ -246,6 +260,9 @@ static void frankenphp_reset_session_state(void) {
246260
247261/* Adapted from php_request_shutdown */
248262static void frankenphp_worker_request_shutdown () {
263+ if (!is_http_thread ) {
264+ return ;
265+ }
249266 /* Flush all output buffers */
250267 zend_try { php_output_end_all (); }
251268 zend_end_try ();
@@ -296,6 +313,9 @@ void get_full_env(zval *track_vars_array) {
296313/* Adapted from php_request_startup() */
297314static int frankenphp_worker_request_startup () {
298315 int retval = SUCCESS ;
316+ if (!is_http_thread ) {
317+ return retval ;
318+ }
299319
300320 frankenphp_update_request_context ();
301321
@@ -608,6 +628,65 @@ PHP_FUNCTION(frankenphp_handle_request) {
608628 RETURN_TRUE ;
609629}
610630
631+ PHP_FUNCTION (frankenphp_set_server_var ) {
632+ char * key = NULL ;
633+ size_t key_len = 0 ;
634+ char * value = NULL ;
635+ size_t value_len = 0 ;
636+
637+ ZEND_PARSE_PARAMETERS_START (2 , 2 );
638+ Z_PARAM_STRING (key , key_len );
639+ Z_PARAM_STRING_OR_NULL (value , value_len );
640+ ZEND_PARSE_PARAMETERS_END ();
641+
642+ if (key_len == 0 ) {
643+ zend_value_error ("Key must not be empty" );
644+ RETURN_THROWS ();
645+ }
646+
647+ /* Reject HTTP_* keys; these are set per-request from HTTP headers */
648+ if (key_len >= 5 && memcmp (key , "HTTP_" , 5 ) == 0 ) {
649+ zend_value_error ("Key must not start with \"HTTP_\"" );
650+ RETURN_THROWS ();
651+ }
652+
653+ if (value == NULL ) {
654+ go_frankenphp_unset_server_var (key , key_len );
655+ } else {
656+ go_frankenphp_set_server_var (key , key_len , value , value_len );
657+ }
658+ }
659+
660+ PHP_FUNCTION (frankenphp_sidekick_start ) {
661+ char * name = NULL ;
662+ size_t name_len = 0 ;
663+
664+ ZEND_PARSE_PARAMETERS_START (1 , 1 );
665+ Z_PARAM_STRING (name , name_len );
666+ ZEND_PARSE_PARAMETERS_END ();
667+
668+ if (name_len == 0 ) {
669+ zend_value_error ("Sidekick name must not be empty" );
670+ RETURN_THROWS ();
671+ }
672+
673+ char * error = go_frankenphp_start_sidekick (thread_index , name , name_len );
674+ if (error ) {
675+ zend_throw_exception (spl_ce_RuntimeException , error , 0 );
676+ free (error );
677+ RETURN_THROWS ();
678+ }
679+ }
680+
681+ PHP_FUNCTION (frankenphp_sidekick_should_stop ) {
682+ ZEND_PARSE_PARAMETERS_NONE ();
683+
684+ if (go_frankenphp_sidekick_should_stop (thread_index )) {
685+ RETURN_TRUE ;
686+ }
687+ RETURN_FALSE ;
688+ }
689+
611690PHP_FUNCTION (headers_send ) {
612691 zend_long response_code = 200 ;
613692
@@ -966,6 +1045,34 @@ static void frankenphp_register_variables(zval *track_vars_array) {
9661045
9671046 /* Some variables are already present in SG(request_info) */
9681047 frankenphp_register_variables_from_request_info (track_vars_array );
1048+
1049+ /* For sidekick threads: inject $argv/$argc into $_SERVER
1050+ * argv[0] is the script name (like CLI), followed by the sidekick args */
1051+ if (!is_http_thread && sidekick_argc > 0 ) {
1052+ zval argv_array ;
1053+ array_init (& argv_array );
1054+
1055+ /* Get SCRIPT_FILENAME to use as argv[0] */
1056+ zval * script_filename =
1057+ zend_hash_str_find (Z_ARRVAL_P (track_vars_array ), "SCRIPT_FILENAME" ,
1058+ sizeof ("SCRIPT_FILENAME" ) - 1 );
1059+ if (script_filename ) {
1060+ add_next_index_zval (& argv_array , script_filename );
1061+ Z_TRY_ADDREF_P (script_filename );
1062+ }
1063+
1064+ for (int i = 0 ; i < sidekick_argc ; i ++ ) {
1065+ add_next_index_string (& argv_array , sidekick_argv [i ]);
1066+ }
1067+
1068+ zval argc_zval ;
1069+ ZVAL_LONG (& argc_zval , zend_hash_num_elements (Z_ARRVAL (argv_array )));
1070+
1071+ zend_hash_str_update (Z_ARRVAL_P (track_vars_array ), "argv" ,
1072+ sizeof ("argv" ) - 1 , & argv_array );
1073+ zend_hash_str_update (Z_ARRVAL_P (track_vars_array ), "argc" ,
1074+ sizeof ("argc" ) - 1 , & argc_zval );
1075+ }
9691076}
9701077
9711078static void frankenphp_log_message (const char * message , int syslog_type_int ) {
@@ -1217,6 +1324,11 @@ int frankenphp_execute_script(char *file_name) {
12171324
12181325 file_handle .primary_script = 1 ;
12191326
1327+ /* Sidekick entrypoints (e.g. bin/console) may have a shebang line */
1328+ if (!is_http_thread ) {
1329+ CG (skip_shebang ) = 1 ;
1330+ }
1331+
12201332 zend_first_try {
12211333 EG (exit_status ) = 0 ;
12221334 php_execute_script (& file_handle );
0 commit comments