Is there a debugger or feature of a debugger that allows me to tell the debugger "if this breakpoint is not reached every X ms break the program wherever it is"?

I'm currently investigating a really confusing bug in bevy, I have ruled out just about everything (it's windowing related but not winit, the winit integration, or literally anything that responds to winit events).

whenever the bug occures the entire app freezes and/or the window disappears for ~250ms, and bevy runs in a event loop, which usually takes <10ms per iteration.

if I placed a breakpoint somewhere in the event loop bevy will run past it every <10ms unless the bug occures, at which point it will spend 250ms in a mysterious part of the codebase being stuck on something without reaching the breakpoint.

so if i could just tell a debugger to break the moment the app spends ~200ms without reaching the breakpoint (which would be in the middle of the bug occurring) I would instantly find out where the app is getting stuck.

No debugger I know of has that as a feature, but you can build it in two lines and get the same effect: a watchdog thread that raises a signal when the main loop stops ticking.

Put an AtomicU64 somewhere global, store Instant-style millis into it once per frame from a system, and spawn a thread that sleeps 50 ms in a loop and checks it. When the gap exceeds 200 ms, call libc::raise(libc::SIGTRAP). Run under gdb/lldb as usual. The moment the freeze happens the debugger stops on SIGTRAP, you switch to the main thread (thread 1 / bt) and you are looking at the exact stack that is stuck — which is what you actually wanted from the "inverse breakpoint".

If you would rather not touch the code, the other route that works well for 250 ms stalls in bevy specifically is Tracy. Bevy ships with it built in: run the Tracy UI, click connect, then cargo run --release --features bevy/trace_tracy (add bevy/debug to get system names). A 250 ms gap in a <10 ms frame stream is impossible to miss on the timeline, and every bevy system and winit event is already a span, so you see which one the stall sits inside — or that it sits between them, which would point at the window system rather than your code.

Put an AtomicU64 somewhere global, store Instant-style millis into it once per frame from a ...

thanks for the idea, that sounds amazing, imma try that.

the other route that works well for 250 ms stalls in bevy specifically is Tracy

that's the fun part, i used tracy and everything looked fine, unless i missed the vert obvious line as i have no experience with it.

The "everything looked fine in tracy" part is actually the clue.

Tracy only sees what happens inside your process. If the frame graph kept showing normal <10ms frames while the window was gone, then bevy wasn't stuck at all - the compositor / window server was. That would also explain why ruling out winit and everything event-related didn't help, because none of that code ever stopped running.

The watchdog will tell you which it is. If it never fires while the window disappears, it's not your code. If it does fire, the stack you get is the answer.

If you want to double check tracy: look at the frame time strip at the top zoomed all the way out (a single 250ms frame is one tall bar, easy to scroll past), check the render thread row not just main, and sort the statistics view by max time instead of total. A stall in the driver or OS shows up as an empty gap with no zone in it, not as a long zone, so "nothing looks long" doesn't mean nothing happened.

What OS / window system is this on, and did the frame graph show one long frame during the bug or not?