@@ -459,12 +459,81 @@ static PHP_GINIT_FUNCTION(ddtrace) {
459
459
zai_hook_ginit ();
460
460
}
461
461
462
+ #if defined(COMPILE_DL_DDTRACE ) && defined(__GLIBC__ ) && __GLIBC_MINOR__
463
+ #define CXA_THREAD_ATEXIT_WRAPPER 1
464
+ #endif
465
+ #ifdef CXA_THREAD_ATEXIT_WRAPPER
466
+ struct dd_rust_thread_destructor {
467
+ void (* dtor )(void * );
468
+ void * obj ;
469
+ struct dd_rust_thread_destructor * next ;
470
+ };
471
+ ZEND_TLS struct dd_rust_thread_destructor * dd_rust_thread_destructors = NULL ;
472
+
473
+ void dd_run_rust_thread_destructors (void * unused ) {
474
+ UNUSED (unused );
475
+ struct dd_rust_thread_destructor * entry = dd_rust_thread_destructors ;
476
+ while (entry ) {
477
+ struct dd_rust_thread_destructor * cur = entry ;
478
+ cur -> dtor (cur -> obj );
479
+ entry = entry -> next ;
480
+ free (cur );
481
+ }
482
+ }
483
+ #endif
484
+
462
485
static PHP_GSHUTDOWN_FUNCTION (ddtrace ) {
463
486
if (ddtrace_globals -> remote_config_reader ) {
464
487
ddog_agent_remote_config_reader_drop (ddtrace_globals -> remote_config_reader );
465
488
}
466
489
zai_hook_gshutdown ();
490
+
491
+ #ifdef CXA_THREAD_ATEXIT_WRAPPER
492
+ dd_run_rust_thread_destructors (NULL );
493
+ #endif
494
+ }
495
+
496
+ // Rust code will call __cxa_thread_atexit_impl. This is a weak symbol; it's defined by glibc.
497
+ // The problem is that calls to __cxa_thread_atexit_impl cause shared libraries to remain referenced until the calling thread terminates.
498
+ // However in NTS builds the calling thread is the main thread and thus the shared object (i.e. ddtrace.so) WILL remain loaded.
499
+ // This is problematic with e.g. apache which, upon reloading will unload all PHP modules including PHP itself, then reload.
500
+ // This prevents us from a) having the weak symbols updated to the new locations and b) having ddtrace updates going live without hard restart.
501
+ // Thus, we need to intercept it: define it ourselves so that the linker will force the rust code to call our code here.
502
+ // Then we can collect the callbacks and invoke them ourselves right at thread shutdown, i.e. GSHUTDOWN.
503
+ #ifdef CXA_THREAD_ATEXIT_WRAPPER
504
+ #define CXA_THREAD_ATEXIT_PHP ((void *)0)
505
+ #define CXA_THREAD_ATEXIT_UNINITIALIZED ((void *)1)
506
+ #define CXA_THREAD_ATEXIT_UNAVAILABLE ((void *)2)
507
+
508
+ static int (* glibc__cxa_thread_atexit_impl )(void (* func )(void * ), void * obj , void * dso_symbol ) = CXA_THREAD_ATEXIT_UNINITIALIZED ;
509
+ static pthread_key_t dd_cxa_thread_atexit_key ; // fallback for sidecar
510
+
511
+ // Note: this symbol is not public
512
+ int __cxa_thread_atexit_impl (void (* func )(void * ), void * obj , void * dso_symbol ) {
513
+ while (glibc__cxa_thread_atexit_impl != CXA_THREAD_ATEXIT_PHP && glibc__cxa_thread_atexit_impl != CXA_THREAD_ATEXIT_UNAVAILABLE ) {
514
+ if (glibc__cxa_thread_atexit_impl == CXA_THREAD_ATEXIT_UNINITIALIZED ) {
515
+ glibc__cxa_thread_atexit_impl = DL_FETCH_SYMBOL (NULL , "__cxa_thread_atexit_impl" );
516
+ if (glibc__cxa_thread_atexit_impl == NULL ) {
517
+ glibc__cxa_thread_atexit_impl = CXA_THREAD_ATEXIT_UNAVAILABLE ;
518
+ pthread_key_create (& dd_cxa_thread_atexit_key , NULL );
519
+ break ;
520
+ }
521
+ }
522
+ return glibc__cxa_thread_atexit_impl (func , obj , dso_symbol );
523
+ }
524
+
525
+ if (glibc__cxa_thread_atexit_impl == CXA_THREAD_ATEXIT_UNAVAILABLE ) {
526
+ pthread_setspecific (dd_cxa_thread_atexit_key , (void * )0x1 ); // needs to be non-NULL
527
+ }
528
+
529
+ struct dd_rust_thread_destructor * entry = malloc (sizeof (struct dd_rust_thread_destructor ));
530
+ entry -> dtor = func ;
531
+ entry -> obj = obj ;
532
+ entry -> next = dd_rust_thread_destructors ;
533
+ dd_rust_thread_destructors = entry ;
534
+ return 0 ;
467
535
}
536
+ #endif
468
537
469
538
/* DDTrace\SpanLink */
470
539
zend_class_entry * ddtrace_ce_span_link ;
@@ -898,6 +967,9 @@ static void dd_disable_if_incompatible_sapi_detected(void) {
898
967
static PHP_MINIT_FUNCTION (ddtrace ) {
899
968
UNUSED (type );
900
969
970
+ #ifdef CXA_THREAD_ATEXIT_WRAPPER
971
+ glibc__cxa_thread_atexit_impl = CXA_THREAD_ATEXIT_PHP ;
972
+ #endif
901
973
902
974
zai_hook_minit ();
903
975
zai_uhook_minit (module_number );
0 commit comments