Skip to content

Commit 78b3ebf

Browse files
committed
Trigger CI
1 parent dc76d1f commit 78b3ebf

1 file changed

Lines changed: 34 additions & 22 deletions

File tree

libdd-crashtracker/src/receiver/ptrace_collector.rs

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -392,56 +392,68 @@ pub fn capture_thread_context(
392392
/// Maximum time to wait for a single thread to enter ptrace-stop.
393393
const STOP_TIMEOUT_PER_THREAD: Duration = Duration::from_millis(50);
394394

395-
/// Delay between retry attempts.
396-
const RETRY_DELAY: Duration = Duration::from_millis(5);
395+
/// Delay between retry attempts. Uses an exponential back-off starting from
396+
/// this value (10ms, 20ms, 40ms).
397+
const RETRY_BASE_DELAY: Duration = Duration::from_millis(10);
398+
399+
/// Maximum number of retry attempts per thread.
400+
const MAX_RETRIES: u32 = 3;
397401

398402
/// Returns true if err is worth retrying.
399403
///
400-
/// Only ETIMEDOUT (register-wait deadline expired before IP became readable)
401-
/// should be retired.
404+
/// ETIMEDOUT (register-wait deadline expired before IP became readable) and
405+
/// zero-frame captures are transient on older kernels and should be retried.
402406
///
403407
/// EPERM (Yama denial / missing PR_SET_PTRACER) and ESRCH (thread exited)
404408
/// are permanent for the receiver's lifetime and are not retried.
405409
fn is_transient_ptrace_error(err: &PtraceError) -> bool {
406410
matches!(err, PtraceError::Attach(_, libc::ETIMEDOUT))
407411
}
408412

409-
/// Attempt to capture a thread context, retrying once on transient failures.
413+
/// Attempt to capture a thread context, retrying on transient failures.
410414
///
411-
/// Uses a single per-thread deadline across attempts so that a slow thread
412-
/// cannot consume more than STOP_TIMEOUT_PER_THREAD total.
415+
/// Each attempt gets its own `STOP_TIMEOUT_PER_THREAD` budget (capped at the
416+
/// overall deadline) so that a retry after a timeout-induced failure actually
417+
/// has enough time to succeed. On older kernels (e.g. CentOS 7 / kernel 3.10)
418+
/// the first attempt can consume its entire budget waiting for registers to
419+
/// become readable; reusing that exhausted deadline would make the retry a
420+
/// no-op.
413421
///
414422
/// A capture that succeeds but produces zero frames is also retried: on a
415423
/// running thread with a confirmed non-zero IP, empty frames indicates a
416424
/// transient libunwind issue (e.g. stale address-space cache state) rather
417-
/// than a permanent problem.
425+
/// than a permanent problem. Retries use exponential back-off to give the
426+
/// kernel time to fully commit thread state between attempts.
418427
fn capture_with_retry(
419428
tid: libc::pid_t,
420429
resolve_frames: crate::StacktraceCollection,
421430
addr_space: UnwAddrSpaceT,
422431
overall_deadline: Instant,
423432
) -> Option<CapturedThreadContext> {
424-
let thread_deadline = (Instant::now() + STOP_TIMEOUT_PER_THREAD).min(overall_deadline);
433+
for attempt in 0..=MAX_RETRIES {
434+
let thread_deadline = (Instant::now() + STOP_TIMEOUT_PER_THREAD).min(overall_deadline);
425435

426-
let should_retry =
427436
match capture_thread_context(tid, resolve_frames, addr_space, thread_deadline) {
428437
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-
};
438+
Ok(_) => {} // 0 frames -- retry
439+
Err(ref e) if is_transient_ptrace_error(e) => {} // ETIMEDOUT -- retry
440+
Err(_) => return None, // permanent error
441+
}
433442

434-
if !should_retry {
435-
return None;
436-
}
443+
if attempt == MAX_RETRIES {
444+
break;
445+
}
437446

438-
let remaining = thread_deadline.saturating_duration_since(Instant::now());
439-
if remaining <= RETRY_DELAY {
440-
return None;
447+
let delay = RETRY_BASE_DELAY * 2u32.saturating_pow(attempt);
448+
if Instant::now() + delay >= overall_deadline {
449+
break;
450+
}
451+
std::thread::sleep(delay);
441452
}
442-
std::thread::sleep(RETRY_DELAY);
443453

444-
capture_thread_context(tid, resolve_frames, addr_space, thread_deadline).ok()
454+
// All attempts produced 0 frames or timed out; return None so the caller
455+
// records the thread with an incomplete stack rather than frames: [].
456+
None
445457
}
446458

447459
/// Stream thread contexts to a callback one at a time.

0 commit comments

Comments
 (0)