@@ -243,76 +243,78 @@ push_threadinfo_to_sample(Datadog::Sample& sample)
243243 // Error will be restored automatically by error_restorer destructor
244244}
245245
246- /* Helper function to extract code object and line number from a PyFrameObject */
246+ /* Helper function to extract frame info from PyFrameObject and push to sample */
247247static void
248- extract_frame_info_from_pyframe (PyFrameObject* frame, PyCodeObject** code_out, int * lineno_out )
248+ push_pyframe_to_sample (Datadog::Sample& sample, PyFrameObject* frame )
249249{
250- *code_out = NULL ;
251- *lineno_out = 0 ;
252-
250+ // Get line number
253251 int lineno_val = PyFrame_GetLineNumber (frame);
254252 if (lineno_val < 0 )
255253 lineno_val = 0 ;
256254
255+ // Get code object
257256#ifdef _PY39_AND_LATER
258257 PyCodeObject* code = PyFrame_GetCode (frame);
259258#else
260259 PyCodeObject* code = frame->f_code ;
261260#endif
262261
263- *code_out = code;
264- *lineno_out = lineno_val;
265- }
262+ std::string_view name_sv = " <unknown>" ;
263+ std::string_view filename_sv = " <unknown>" ;
266264
267- /* Helper function to push frame info to sample */
268- static void
269- push_frame_to_sample (Datadog::Sample& sample, PyCodeObject* code, int lineno_val)
270- {
271- // Extract frame info for Sample using helper function
265+ if (code != NULL ) {
266+ // Extract function name (use co_qualname for Python 3.11+ for better context)
272267#if defined(_PY311_AND_LATER)
273- // Python 3.11+ has co_qualname which provides fully qualified names (e.g., "MyClass.method")
274- PyObject* name_obj = code ? (code->co_qualname ? code->co_qualname : code->co_name ) : nullptr ;
268+ PyObject* name_obj = code->co_qualname ? code->co_qualname : code->co_name ;
275269#else
276- PyObject* name_obj = code ? code ->co_name : nullptr ;
270+ PyObject* name_obj = code->co_name ;
277271#endif
278- std::string_view name_sv = unicode_to_string_view (name_obj);
279- std::string_view filename_sv = code ? unicode_to_string_view (code->co_filename ) : " <unknown>" ;
272+ name_sv = unicode_to_string_view (name_obj);
273+ filename_sv = unicode_to_string_view (code->co_filename );
274+ }
280275
281276 // Push frame to Sample (root to leaf order)
282277 // push_frame copies the strings immediately into its StringArena
283278 sample.push_frame (name_sv, filename_sv, 0 , lineno_val);
279+
280+ #ifdef _PY39_AND_LATER
281+ Py_XDECREF (code);
282+ #endif
284283}
285284
286285/* Helper function to collect frames from PyFrameObject chain and push to sample */
287286static void
288287push_stacktrace_to_sample (Datadog::Sample& sample)
289288{
290289 PyThreadState* tstate = PyThreadState_Get ();
291- if (tstate == NULL )
290+ if (tstate == NULL ) {
291+ // Push a placeholder frame when thread state is unavailable
292+ sample.push_frame (" <no thread state>" , " <unknown>" , 0 , 0 );
292293 return ;
294+ }
293295
294296#ifdef _PY39_AND_LATER
295297 PyFrameObject* pyframe = PyThreadState_GetFrame (tstate);
296298#else
297299 PyFrameObject* pyframe = tstate->frame ;
298300#endif
299301
300- if (pyframe == NULL )
302+ if (pyframe == NULL ) {
303+ // No Python frames available (e.g., during thread initialization/cleanup in "Dummy" threads).
304+ // This occurs in Python 3.10-3.12 but not in 3.13+ due to threading implementation changes.
305+ //
306+ // The previous implementation (before dd_wrapper Sample refactor) dropped these samples entirely
307+ // by returning NULL from traceback_new(). This new approach is strictly better: we still capture
308+ // allocation metrics and make it explicit in profiles that the stack wasn't available.
309+ //
310+ // TODO(profiling): Investigate if there's a way to capture C-level stack traces or other context
311+ // when Python frames aren't available during thread initialization/cleanup.
312+ sample.push_frame (" <no Python frames>" , " <unknown>" , 0 , 0 );
301313 return ;
314+ }
302315
303316 for (PyFrameObject* frame = pyframe; frame != NULL ;) {
304- PyCodeObject* code = NULL ;
305- int lineno_val = 0 ;
306-
307- extract_frame_info_from_pyframe (frame, &code, &lineno_val);
308-
309- if (code != NULL ) {
310- push_frame_to_sample (sample, code, lineno_val);
311- }
312-
313- #ifdef _PY39_AND_LATER
314- Py_XDECREF (code);
315- #endif
317+ push_pyframe_to_sample (sample, frame);
316318
317319#ifdef _PY39_AND_LATER
318320 PyFrameObject* back = PyFrame_GetBack (frame);
0 commit comments