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 :: { profiling:: Profiler , RefCellExt , RequestLocals , REQUEST_LOCALS } ;
66use core:: ptr;
77use log:: debug;
88use std:: sync:: atomic:: Ordering ;
@@ -79,8 +79,8 @@ 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.
83- ddog_php_prof_interrupt_function ( leaf_frame) ;
82+ // the leaf frame is used instead of the execute_data ptr.
83+ ddog_php_prof_interrupt_function_unlikely ( leaf_frame) ;
8484 }
8585
8686 /// # Safety
@@ -109,31 +109,65 @@ 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- }
112+ if let Err ( err) =
113+ REQUEST_LOCALS . try_with_borrow ( |locals| interrupt_function ( locals, execute_data) )
114+ {
115+ debug ! ( "ddog_php_prof_interrupt_function failed to borrow request locals: {err}" ) ;
116+ }
117+ }
118+
119+ fn interrupt_function ( locals : & RequestLocals , execute_data : * mut zend_execute_data ) {
120+ let profiling_disabled = !locals. system_settings ( ) . profiling_enabled ;
121+
122+ /* Other extensions/modules or the engine itself may trigger an
123+ * interrupt, but given how expensive it is to gather a stack trace,
124+ * it should only be done if we triggered it ourselves. So
125+ * interrupt_count serves dual purposes:
126+ * 1. Track how many interrupts there were.
127+ * 2. Ensure we don't collect on someone else's interrupt.
128+ */
129+ let interrupt_count = locals. interrupt_count . swap ( 0 , Ordering :: SeqCst ) ;
130+ if profiling_disabled | ( interrupt_count == 0 ) {
131+ return ;
132+ }
133+
134+ if let Some ( profiler) = Profiler :: get ( ) {
135+ // Safety: execute_data was provided by the engine, and the profiler doesn't mutate it.
136+ profiler. collect_time ( execute_data, interrupt_count) ;
137+ }
138+ }
116139
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 {
140+ /// This function is like [`ddog_php_prof_interrupt_function`] except it's
141+ /// optimized for the expectation that it's unlikely that there's actually an
142+ /// interrupt actively needing to be handled. This is because we have to insert
143+ /// this check in a variety of places, and in many of them (execute_internal,
144+ /// frameless functions) there won't be an interrupt at all.
145+ ///
146+ /// # Safety
147+ /// The zend_execute_data pointer should come from the engine to ensure it and
148+ /// its sub-objects are valid.
149+ #[ no_mangle]
150+ #[ inline( never) ]
151+ pub extern "C" fn ddog_php_prof_interrupt_function_unlikely ( execute_data : * mut zend_execute_data ) {
152+ let result = REQUEST_LOCALS . try_with_borrow ( |locals| {
153+ // Optimize here with the expectation there isn't an interrupt, because
154+ // overwhelmingly, there will not be one. The relaxed load avoids the
155+ // more expensive atomic read-modify-write in `interrupt_function` on
156+ // the idle path. It is only a fast-path hint: `interrupt_function` does
157+ // the authoritative swap. If another consumer clears the count between
158+ // the load and swap, the swap simply observes zero; if a producer adds
159+ // an interrupt after this load observes zero, the count remains pending
160+ // and the corresponding VM interrupt will provide another opportunity
161+ // to handle it.
162+ if locals. interrupt_count . load ( Ordering :: Relaxed ) == 0 {
126163 return ;
127164 }
128165
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- }
166+ interrupt_function ( locals, execute_data) ;
133167 } ) ;
134168
135169 if let Err ( err) = result {
136- debug ! ( "ddog_php_prof_interrupt_function failed to borrow request locals: {err}" ) ;
170+ debug ! ( "ddog_php_prof_interrupt_function_unlikely failed to borrow request locals: {err}" ) ;
137171 }
138172}
139173
@@ -144,7 +178,8 @@ mod frameless {
144178 use crate :: bindings:: {
145179 zend_flf_functions, zend_flf_handlers, zend_frameless_function_info,
146180 } ;
147- use crate :: { profiling:: Profiler , zend, RefCellExt , REQUEST_LOCALS } ;
181+ use crate :: wall_time:: ddog_php_prof_interrupt_function;
182+ use crate :: { zend, RefCellExt , REQUEST_LOCALS } ;
148183 use dynasmrt:: { dynasm, DynasmApi , ExecutableBuffer } ;
149184 use log:: error;
150185 use std:: ffi:: c_void;
@@ -271,23 +306,17 @@ mod frameless {
271306 #[ inline( never) ]
272307 pub extern "C" fn ddog_php_prof_icall_trampoline_target ( ) {
273308 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- } )
309+ . try_with_borrow ( |locals| locals. interrupt_count . load ( Ordering :: Relaxed ) )
280310 . unwrap_or ( 0 ) ;
281311
282312 if interrupt_count == 0 {
283313 return ;
284314 }
285315
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- }
316+ // Fetching the execute data is intentionally delayed until we know
317+ // that the interrupt count is greater than 0 for perf.
318+ let execute_data = unsafe { zend:: ddog_php_prof_get_current_execute_data ( ) } ;
319+ ddog_php_prof_interrupt_function ( execute_data) ;
291320 }
292321 }
293322
0 commit comments