@@ -410,6 +410,11 @@ fn is_transient_ptrace_error(err: &PtraceError) -> bool {
410410///
411411/// Uses a single per-thread deadline across attempts so that a slow thread
412412/// cannot consume more than STOP_TIMEOUT_PER_THREAD total.
413+ ///
414+ /// A capture that succeeds but produces zero frames is also retried: on a
415+ /// running thread with a confirmed non-zero IP, empty frames indicates a
416+ /// transient libunwind issue (e.g. stale address-space cache state) rather
417+ /// than a permanent problem.
413418fn capture_with_retry (
414419 tid : libc:: pid_t ,
415420 resolve_frames : crate :: StacktraceCollection ,
@@ -418,10 +423,16 @@ fn capture_with_retry(
418423) -> Option < CapturedThreadContext > {
419424 let thread_deadline = ( Instant :: now ( ) + STOP_TIMEOUT_PER_THREAD ) . min ( overall_deadline) ;
420425
421- match capture_thread_context ( tid, resolve_frames, addr_space, thread_deadline) {
422- Ok ( ctx) => return Some ( ctx) ,
423- Err ( ref e) if is_transient_ptrace_error ( e) => { }
424- Err ( _) => return None ,
426+ let should_retry =
427+ match capture_thread_context ( tid, resolve_frames, addr_space, thread_deadline) {
428+ Ok ( ctx) if !ctx. stack_trace . frames . is_empty ( ) => return Some ( ctx) ,
429+ Ok ( _) => true ,
430+ Err ( ref e) if is_transient_ptrace_error ( e) => true ,
431+ Err ( _) => false ,
432+ } ;
433+
434+ if !should_retry {
435+ return None ;
425436 }
426437
427438 let remaining = thread_deadline. saturating_duration_since ( Instant :: now ( ) ) ;
0 commit comments