Skip to content

Commit 982e0c5

Browse files
Qardclaude
andcommitted
Restore debug logging and add ModuleScope type
This commit restores the debug logging that was inadvertently removed. The debug logging is critical for diagnosing CI failures, especially on macOS aarch64 where we're seeing intermittent segfaults. Also adds a ModuleScope type as a placeholder for proper php_module_startup/shutdown pairing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 6ce26e6 commit 982e0c5

File tree

2 files changed

+68
-6
lines changed

2 files changed

+68
-6
lines changed

src/embed.rs

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -341,47 +341,61 @@ 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");
345+
344346
// Initialize thread-local storage for this worker thread.
345347
// This calls ext_php_rs_sapi_per_thread_init() -> ts_resource(0) which sets up
346348
// PHP's thread-local storage for the current thread.
347349
let _thread_scope = ThreadScope::new();
350+
eprintln!("DEBUG [spawn_blocking] Step 2: ThreadScope created");
348351

349352
// Verify SAPI is accessible (no longer calls php_module_startup per-thread).
350353
// php_module_startup is only called once in Sapi::new(), not per-thread.
351354
sapi
352355
.startup()
353356
.map_err(|_| EmbedRequestError::SapiNotStarted)?;
357+
eprintln!("DEBUG [spawn_blocking] Step 3: SAPI startup verified");
354358

355359
// Setup RequestContext (always streaming from SAPI perspective)
356360
// RequestContext::new() will extract the request body's read stream and add it as RequestStream extension
361+
eprintln!("DEBUG [spawn_blocking] Step 4: Creating RequestContext");
357362
let ctx = RequestContext::new(
358363
request,
359364
docroot.clone(),
360365
response_writer.clone(),
361366
headers_sent_tx,
362367
);
363368
RequestContext::set_current(Box::new(ctx));
369+
eprintln!("DEBUG [spawn_blocking] Step 5: RequestContext set as current");
364370

365371
// All estrdup calls happen here, inside spawn_blocking, after ThreadScope::new()
366372
// has initialized PHP's thread-local storage. These will be freed by efree in
367373
// sapi_module_deactivate during request shutdown.
374+
eprintln!("DEBUG [spawn_blocking] Step 6: About to call estrdup for request_uri");
368375
let request_uri_c = estrdup(request_uri_str.as_str());
376+
eprintln!("DEBUG [spawn_blocking] Step 7: estrdup for request_uri completed");
369377
let path_translated = estrdup(translated_path_str.as_str());
378+
eprintln!("DEBUG [spawn_blocking] Step 8: estrdup for path_translated completed");
370379
let request_method = estrdup(method_str.as_str());
380+
eprintln!("DEBUG [spawn_blocking] Step 9: estrdup for request_method completed");
371381
let query_string = estrdup(query_str.as_str());
382+
eprintln!("DEBUG [spawn_blocking] Step 10: estrdup for query_string completed");
372383
let content_type = content_type_str
373384
.as_ref()
374385
.map(|s| estrdup(s.as_str()))
375386
.unwrap_or(std::ptr::null_mut());
387+
eprintln!("DEBUG [spawn_blocking] Step 11: estrdup for content_type completed");
376388

377389
// Prepare argv pointers
378390
let argc = args.len() as i32;
379391
let mut argv_ptrs: Vec<*mut c_char> = args
380392
.iter()
381393
.map(|s| estrdup(s.as_str()))
382394
.collect();
395+
eprintln!("DEBUG [spawn_blocking] Step 12: argv_ptrs prepared");
383396

384397
// Set SAPI globals BEFORE php_request_startup since PHP reads these during initialization
398+
eprintln!("DEBUG [spawn_blocking] Step 13: Setting SAPI globals");
385399
{
386400
let mut globals = SapiGlobals::get_mut();
387401
globals.options |= ext_php_rs::ffi::SAPI_OPTION_NO_CHDIR as i32;
@@ -397,40 +411,61 @@ impl Handler for Embed {
397411
globals.request_info.content_type = content_type;
398412
globals.request_info.content_length = content_length;
399413
}
414+
eprintln!("DEBUG [spawn_blocking] Step 14: SAPI globals set");
400415

401416
let result = try_catch_first(|| {
417+
eprintln!("DEBUG [spawn_blocking] Step 15: Creating RequestScope");
402418
let _request_scope = RequestScope::new()?;
419+
eprintln!("DEBUG [spawn_blocking] Step 16: RequestScope created");
403420

404421
// Execute PHP script
422+
eprintln!("DEBUG [spawn_blocking] Step 17: Creating FileHandleScope");
405423
{
406424
let mut file_handle = FileHandleScope::new(translated_path_str.clone());
425+
eprintln!("DEBUG [spawn_blocking] Step 18: Executing PHP script");
407426
try_catch(|| unsafe { php_execute_script(file_handle.deref_mut()) })
408427
.map_err(|_| EmbedRequestError::Bailout)?;
428+
eprintln!("DEBUG [spawn_blocking] Step 19: PHP script execution completed");
409429
}
410430

411431
// Handle exceptions
412432
if let Some(err) = ExecutorGlobals::take_exception() {
413433
let ex = Error::Exception(err);
434+
eprintln!("DEBUG [spawn_blocking] PHP exception occurred: {}", ex);
414435
return Err(EmbedRequestError::Exception(ex.to_string()));
415436
}
416437

438+
eprintln!("DEBUG [spawn_blocking] Step 20: No exceptions, returning Ok");
417439
Ok(())
418440
// RequestScope drops here, triggering request shutdown
419441
// Output buffering flush happens during shutdown, calling ub_write
420442
// RequestContext must still be alive at this point!
421443
});
422444

445+
eprintln!("DEBUG [spawn_blocking] Step 21: try_catch_first completed, reclaiming RequestContext");
423446
// Reclaim RequestContext AFTER RequestScope has dropped
424447
// This ensures output buffer flush during shutdown can still access the context
425448
// Note: reclaim() also shuts down the response stream to signal EOF to consumers
426449
let _ctx = RequestContext::reclaim();
450+
eprintln!("DEBUG [spawn_blocking] Step 22: RequestContext reclaimed");
427451

428452
// Flatten the result
429-
match result {
430-
Ok(Ok(())) => Ok(()),
431-
Ok(Err(e)) => Err(e),
432-
Err(_) => Err(EmbedRequestError::Bailout),
433-
}
453+
let final_result = match result {
454+
Ok(Ok(())) => {
455+
eprintln!("DEBUG [spawn_blocking] Step 23: Returning success");
456+
Ok(())
457+
}
458+
Ok(Err(e)) => {
459+
eprintln!("DEBUG [spawn_blocking] Step 23: Returning error: {:?}", e);
460+
Err(e)
461+
}
462+
Err(_) => {
463+
eprintln!("DEBUG [spawn_blocking] Step 23: Returning bailout");
464+
Err(EmbedRequestError::Bailout)
465+
}
466+
};
467+
eprintln!("DEBUG [spawn_blocking] Step 24: spawn_blocking task ending");
468+
final_result
434469
});
435470

436471
// Wait for headers to be sent (with owned status, mimetype, custom headers, and logs)

src/scopes.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use ext_php_rs::{
88
alloc::{efree, estrdup},
99
embed::{ext_php_rs_sapi_per_thread_init, ext_php_rs_sapi_per_thread_shutdown},
1010
ffi::{
11-
_zend_file_handle__bindgen_ty_1, php_request_shutdown, php_request_startup,
11+
_zend_file_handle__bindgen_ty_1, php_module_shutdown, php_request_shutdown, php_request_startup,
1212
zend_destroy_file_handle, zend_file_handle, zend_stream_init_filename, ZEND_RESULT_CODE_SUCCESS,
1313
},
1414
};
@@ -19,18 +19,40 @@ pub(crate) struct ThreadScope();
1919

2020
impl ThreadScope {
2121
pub fn new() -> Self {
22+
eprintln!("DEBUG [ThreadScope::new] Calling ext_php_rs_sapi_per_thread_init()");
2223
unsafe {
2324
ext_php_rs_sapi_per_thread_init();
2425
}
26+
eprintln!("DEBUG [ThreadScope::new] ext_php_rs_sapi_per_thread_init() completed");
2527
Self()
2628
}
2729
}
2830

2931
impl Drop for ThreadScope {
3032
fn drop(&mut self) {
33+
eprintln!("DEBUG [ThreadScope::drop] Calling ext_php_rs_sapi_per_thread_shutdown()");
3134
unsafe {
3235
ext_php_rs_sapi_per_thread_shutdown();
3336
}
37+
eprintln!("DEBUG [ThreadScope::drop] ext_php_rs_sapi_per_thread_shutdown() completed");
38+
}
39+
}
40+
41+
/// A scope for PHP module shutdown (module startup is called once in Sapi::new()).
42+
/// This is used to ensure php_module_shutdown is called on drop.
43+
///
44+
/// NOTE: php_module_startup is only called once in Sapi::new() via the startup callback.
45+
/// Per-thread TLS initialization is handled by ThreadScope via ts_resource(0).
46+
#[allow(dead_code)]
47+
pub(crate) struct ModuleScope;
48+
49+
impl Drop for ModuleScope {
50+
fn drop(&mut self) {
51+
eprintln!("DEBUG [ModuleScope::drop] Calling php_module_shutdown()");
52+
unsafe {
53+
php_module_shutdown();
54+
}
55+
eprintln!("DEBUG [ModuleScope::drop] php_module_shutdown() completed");
3456
}
3557
}
3658

@@ -42,19 +64,24 @@ pub(crate) struct RequestScope();
4264
impl RequestScope {
4365
/// Starts a new request scope in which a PHP request may operate.
4466
pub fn new() -> Result<Self, EmbedRequestError> {
67+
eprintln!("DEBUG [RequestScope::new] Calling php_request_startup()");
4568
if unsafe { php_request_startup() } != ZEND_RESULT_CODE_SUCCESS {
69+
eprintln!("DEBUG [RequestScope::new] php_request_startup() FAILED");
4670
return Err(EmbedRequestError::SapiRequestNotStarted);
4771
}
72+
eprintln!("DEBUG [RequestScope::new] php_request_startup() succeeded");
4873

4974
Ok(RequestScope())
5075
}
5176
}
5277

5378
impl Drop for RequestScope {
5479
fn drop(&mut self) {
80+
eprintln!("DEBUG [RequestScope::drop] Calling php_request_shutdown()");
5581
unsafe {
5682
php_request_shutdown(std::ptr::null_mut::<c_void>());
5783
}
84+
eprintln!("DEBUG [RequestScope::drop] php_request_shutdown() completed");
5885
}
5986
}
6087

0 commit comments

Comments
 (0)