22//! implementation reasons, it has cpu-time code as well.
33
44use crate :: bindings:: { zend_execute_data, zend_interrupt_function, VmInterruptFn } ;
5- use crate :: { profiling:: Profiler , RefCellExt , REQUEST_LOCALS } ;
5+ use crate :: module_globals;
6+ use crate :: profiling:: Profiler ;
67use core:: ptr;
7- use log:: debug;
8- use std:: sync:: atomic:: Ordering ;
8+ use core:: sync:: atomic:: Ordering ;
99
1010#[ cfg( not( php_frameless) ) ]
1111mod execute_internal {
@@ -79,7 +79,7 @@ mod execute_internal {
7979 unsafe { prev_execute_internal ( execute_data, return_value) } ;
8080
8181 // See safety section of `execute_data_func_is_trampoline` docs for why
82- // the leaf frame is used instead of the execute_data ptr.
82+ // the leaf frame is used instead of the execute_data ptr.
8383 ddog_php_prof_interrupt_function ( leaf_frame) ;
8484 }
8585
@@ -109,31 +109,29 @@ static mut PREV_INTERRUPT_FUNCTION: Option<VmInterruptFn> = None;
109109#[ no_mangle]
110110#[ inline( never) ]
111111pub extern "C" fn ddog_php_prof_interrupt_function ( execute_data : * mut zend_execute_data ) {
112- let result = REQUEST_LOCALS . try_with_borrow ( |locals| {
113- if !locals. system_settings ( ) . profiling_enabled {
114- return ;
115- }
116-
117- /* Other extensions/modules or the engine itself may trigger an
118- * interrupt, but given how expensive it is to gather a stack trace,
119- * it should only be done if we triggered it ourselves. So
120- * interrupt_count serves dual purposes:
121- * 1. Track how many interrupts there were.
122- * 2. Ensure we don't collect on someone else's interrupt.
123- */
124- let interrupt_count = locals. interrupt_count . swap ( 0 , Ordering :: SeqCst ) ;
125- if interrupt_count == 0 {
126- return ;
127- }
128-
129- if let Some ( profiler) = Profiler :: get ( ) {
130- // Safety: execute_data was provided by the engine, and the profiler doesn't mutate it.
131- profiler. collect_time ( execute_data, interrupt_count) ;
132- }
133- } ) ;
112+ // SAFETY: interrupt callbacks run while the current PHP thread's module globals are valid.
113+ let atomic_count = unsafe { & ( * module_globals:: get_profiler_globals ( ) ) . interrupt_count } ;
114+
115+ /* Other extensions/modules or the engine itself may trigger an
116+ * interrupt, but given how expensive it is to gather a stack trace,
117+ * it should only be done if we triggered it ourselves. So
118+ * interrupt_count serves dual purposes:
119+ * 1. Track how many interrupts there were.
120+ * 2. Ensure we don't collect on someone else's interrupt.
121+ */
122+ let interrupt_count = atomic_count. swap ( 0 , Ordering :: Relaxed ) ;
123+ if interrupt_count == 0 {
124+ return ;
125+ }
126+ collect_time_if_enabled ( execute_data, interrupt_count) ;
127+ }
134128
135- if let Err ( err) = result {
136- debug ! ( "ddog_php_prof_interrupt_function failed to borrow request locals: {err}" ) ;
129+ #[ inline( never) ]
130+ #[ export_name = "ddog_php_prof_collect_time_if_enabled" ]
131+ extern "C" fn collect_time_if_enabled ( execute_data : * mut zend_execute_data , interrupt_count : u32 ) {
132+ if let Some ( profiler) = Profiler :: get ( ) {
133+ // Safety: execute_data was provided by the engine, and the profiler doesn't mutate it.
134+ profiler. collect_time ( execute_data, interrupt_count) ;
137135 }
138136}
139137
@@ -144,7 +142,9 @@ mod frameless {
144142 use crate :: bindings:: {
145143 zend_flf_functions, zend_flf_handlers, zend_frameless_function_info,
146144 } ;
147- use crate :: { profiling:: Profiler , zend, RefCellExt , REQUEST_LOCALS } ;
145+ use crate :: module_globals;
146+ use crate :: wall_time:: collect_time_if_enabled;
147+ use crate :: zend;
148148 use dynasmrt:: { dynasm, DynasmApi , ExecutableBuffer } ;
149149 use log:: error;
150150 use std:: ffi:: c_void;
@@ -270,24 +270,19 @@ mod frameless {
270270 #[ no_mangle]
271271 #[ inline( never) ]
272272 pub extern "C" fn ddog_php_prof_icall_trampoline_target ( ) {
273- let interrupt_count = REQUEST_LOCALS
274- . try_with_borrow ( |locals| {
275- if !locals. system_settings ( ) . profiling_enabled {
276- return 0 ;
277- }
278- locals. interrupt_count . swap ( 0 , Ordering :: SeqCst )
279- } )
280- . unwrap_or ( 0 ) ;
273+ // SAFETY: frameless handlers run while the current PHP thread's module globals are
274+ // valid. Retain the pointer so the authoritative swap reuses the same TSRM lookup.
275+ let atomic_count =
276+ unsafe { & ( * module_globals:: get_profiler_globals ( ) ) . interrupt_count } ;
281277
278+ let interrupt_count = atomic_count. swap ( 0 , Ordering :: Relaxed ) ;
282279 if interrupt_count == 0 {
283280 return ;
284281 }
285282
286- if let Some ( profiler) = Profiler :: get ( ) {
287- // SAFETY: profiler doesn't mutate execute_data
288- let execute_data = unsafe { zend:: ddog_php_prof_get_current_execute_data ( ) } ;
289- profiler. collect_time ( execute_data, interrupt_count) ;
290- }
283+ // Fetching execute data is intentionally delayed until a profiler interrupt is pending.
284+ let execute_data = unsafe { zend:: ddog_php_prof_get_current_execute_data ( ) } ;
285+ collect_time_if_enabled ( execute_data, interrupt_count) ;
291286 }
292287 }
293288
0 commit comments