@@ -341,62 +341,48 @@ impl Handler for Embed {
341341
342342 // Spawn blocking PHP execution - ALL PHP operations happen here
343343 let blocking_handle = tokio:: task:: spawn_blocking ( move || {
344- eprintln ! ( "DEBUG [spawn_blocking] Step 1: Entered spawn_blocking" ) ;
344+ // Keep sapi alive for the duration of the blocking task
345+ let _sapi = sapi;
345346
346347 // Initialize thread-local storage for this worker thread.
347348 // This calls ext_php_rs_sapi_per_thread_init() -> ts_resource(0) which sets up
348349 // PHP's thread-local storage for the current thread.
350+ //
351+ // NOTE: php_module_startup() is called ONCE on the main thread when Sapi is created.
352+ // Worker threads only need per-thread TLS initialization via ThreadScope, NOT
353+ // another php_module_startup call. Calling php_module_startup from multiple threads
354+ // concurrently corrupts global state (memory allocator function pointers).
349355 let _thread_scope = ThreadScope :: new ( ) ;
350- eprintln ! ( "DEBUG [spawn_blocking] Step 2: ThreadScope created" ) ;
351-
352- // Initialize PHP module for this thread.
353- // In ZTS mode, php_module_startup must be called per-thread after the thread's
354- // local storage has been initialized. ModuleScope ensures matching shutdown.
355- let _module_scope = sapi
356- . module_scope ( )
357- . map_err ( |_| EmbedRequestError :: SapiNotStarted ) ?;
358- eprintln ! ( "DEBUG [spawn_blocking] Step 3: ModuleScope created (php_module_startup called)" ) ;
359356
360357 // Setup RequestContext (always streaming from SAPI perspective)
361358 // RequestContext::new() will extract the request body's read stream and add it as RequestStream extension
362- eprintln ! ( "DEBUG [spawn_blocking] Step 4: Creating RequestContext" ) ;
363359 let ctx = RequestContext :: new (
364360 request,
365361 docroot. clone ( ) ,
366362 response_writer. clone ( ) ,
367363 headers_sent_tx,
368364 ) ;
369365 RequestContext :: set_current ( Box :: new ( ctx) ) ;
370- eprintln ! ( "DEBUG [spawn_blocking] Step 5: RequestContext set as current" ) ;
371366
372367 // All estrdup calls happen here, inside spawn_blocking, after ThreadScope::new()
373368 // has initialized PHP's thread-local storage. These will be freed by efree in
374369 // sapi_module_deactivate during request shutdown.
375- eprintln ! ( "DEBUG [spawn_blocking] Step 6: About to call estrdup for request_uri" ) ;
376- let request_uri_c = estrdup ( request_uri_str. as_str ( ) ) ;
377- eprintln ! ( "DEBUG [spawn_blocking] Step 7: estrdup for request_uri completed" ) ;
378- let path_translated = estrdup ( translated_path_str. as_str ( ) ) ;
379- eprintln ! ( "DEBUG [spawn_blocking] Step 8: estrdup for path_translated completed" ) ;
380- let request_method = estrdup ( method_str. as_str ( ) ) ;
381- eprintln ! ( "DEBUG [spawn_blocking] Step 9: estrdup for request_method completed" ) ;
382- let query_string = estrdup ( query_str. as_str ( ) ) ;
383- eprintln ! ( "DEBUG [spawn_blocking] Step 10: estrdup for query_string completed" ) ;
370+ let request_uri_c = estrdup ( request_uri_str) ;
371+ let path_translated = estrdup ( translated_path_str. clone ( ) ) ;
372+ let request_method = estrdup ( method_str) ;
373+ let query_string = estrdup ( query_str) ;
384374 let content_type = content_type_str
385- . as_ref ( )
386- . map ( |s| estrdup ( s. as_str ( ) ) )
375+ . map ( estrdup)
387376 . unwrap_or ( std:: ptr:: null_mut ( ) ) ;
388- eprintln ! ( "DEBUG [spawn_blocking] Step 11: estrdup for content_type completed" ) ;
389377
390378 // Prepare argv pointers
391379 let argc = args. len ( ) as i32 ;
392380 let mut argv_ptrs: Vec < * mut c_char > = args
393381 . iter ( )
394382 . map ( |s| estrdup ( s. as_str ( ) ) )
395383 . collect ( ) ;
396- eprintln ! ( "DEBUG [spawn_blocking] Step 12: argv_ptrs prepared" ) ;
397384
398385 // Set SAPI globals BEFORE php_request_startup since PHP reads these during initialization
399- eprintln ! ( "DEBUG [spawn_blocking] Step 13: Setting SAPI globals" ) ;
400386 {
401387 let mut globals = SapiGlobals :: get_mut ( ) ;
402388 globals. options |= ext_php_rs:: ffi:: SAPI_OPTION_NO_CHDIR as i32 ;
@@ -412,61 +398,40 @@ impl Handler for Embed {
412398 globals. request_info . content_type = content_type;
413399 globals. request_info . content_length = content_length;
414400 }
415- eprintln ! ( "DEBUG [spawn_blocking] Step 14: SAPI globals set" ) ;
416401
417402 let result = try_catch_first ( || {
418- eprintln ! ( "DEBUG [spawn_blocking] Step 15: Creating RequestScope" ) ;
419403 let _request_scope = RequestScope :: new ( ) ?;
420- eprintln ! ( "DEBUG [spawn_blocking] Step 16: RequestScope created" ) ;
421404
422405 // Execute PHP script
423- eprintln ! ( "DEBUG [spawn_blocking] Step 17: Creating FileHandleScope" ) ;
424406 {
425407 let mut file_handle = FileHandleScope :: new ( translated_path_str. clone ( ) ) ;
426- eprintln ! ( "DEBUG [spawn_blocking] Step 18: Executing PHP script" ) ;
427408 try_catch ( || unsafe { php_execute_script ( file_handle. deref_mut ( ) ) } )
428409 . map_err ( |_| EmbedRequestError :: Bailout ) ?;
429- eprintln ! ( "DEBUG [spawn_blocking] Step 19: PHP script execution completed" ) ;
430410 }
431411
432412 // Handle exceptions
433413 if let Some ( err) = ExecutorGlobals :: take_exception ( ) {
434414 let ex = Error :: Exception ( err) ;
435- eprintln ! ( "DEBUG [spawn_blocking] PHP exception occurred: {}" , ex) ;
436415 return Err ( EmbedRequestError :: Exception ( ex. to_string ( ) ) ) ;
437416 }
438417
439- eprintln ! ( "DEBUG [spawn_blocking] Step 20: No exceptions, returning Ok" ) ;
440418 Ok ( ( ) )
441419 // RequestScope drops here, triggering request shutdown
442420 // Output buffering flush happens during shutdown, calling ub_write
443421 // RequestContext must still be alive at this point!
444422 } ) ;
445423
446- eprintln ! ( "DEBUG [spawn_blocking] Step 21: try_catch_first completed, reclaiming RequestContext" ) ;
447424 // Reclaim RequestContext AFTER RequestScope has dropped
448425 // This ensures output buffer flush during shutdown can still access the context
449426 // Note: reclaim() also shuts down the response stream to signal EOF to consumers
450427 let _ctx = RequestContext :: reclaim ( ) ;
451- eprintln ! ( "DEBUG [spawn_blocking] Step 22: RequestContext reclaimed" ) ;
452428
453429 // Flatten the result
454- let final_result = match result {
455- Ok ( Ok ( ( ) ) ) => {
456- eprintln ! ( "DEBUG [spawn_blocking] Step 23: Returning success" ) ;
457- Ok ( ( ) )
458- }
459- Ok ( Err ( e) ) => {
460- eprintln ! ( "DEBUG [spawn_blocking] Step 23: Returning error: {:?}" , e) ;
461- Err ( e)
462- }
463- Err ( _) => {
464- eprintln ! ( "DEBUG [spawn_blocking] Step 23: Returning bailout" ) ;
465- Err ( EmbedRequestError :: Bailout )
466- }
467- } ;
468- eprintln ! ( "DEBUG [spawn_blocking] Step 24: spawn_blocking task ending" ) ;
469- final_result
430+ match result {
431+ Ok ( Ok ( ( ) ) ) => Ok ( ( ) ) ,
432+ Ok ( Err ( e) ) => Err ( e) ,
433+ Err ( _) => Err ( EmbedRequestError :: Bailout )
434+ }
470435 } ) ;
471436
472437 // Wait for headers to be sent (with owned status, mimetype, custom headers, and logs)
0 commit comments