Skip to content

โฒ๏ธ Timing Precision & the 10ms Burnout Window

๐Ÿ“– Why timing is tricky here

Codexion has a hard, testable constraint: a burnout log must appear within 10ms of the actual burnout moment. That's tight enough that sloppy timing choices (wrong clock, coarse sleep granularity, a monitor thread that polls too slowly) will fail the defense even if the logic is otherwise correct.

๐Ÿ• gettimeofday() vs clock_gettime()

gettimeofday() clock_gettime()
Resolution Microseconds (usually) Nanoseconds (usually)
Clock source Wall-clock (CLOCK_REALTIME equivalent) Selectable: CLOCK_REALTIME, CLOCK_MONOTONIC, etc.
Affected by system time changes (NTP sync, manual clock changes) Yes Only if using CLOCK_REALTIME; CLOCK_MONOTONIC is immune
Status Considered legacy, but explicitly allowed and recommended for this project More modern/precise, needed for pthread_cond_timedwait's timespec

The subject explicitly says real-time measurement via gettimeofday() is fine here for simplicity โ€” this isn't a project about squeezing out nanosecond accuracy, it's about correct logic with reasonable timing.

What gettimeofday() actually gives you

It doesn't hand you a single number โ€” it fills a struct timeval with two separate fields:

struct timeval
{
    time_t      tv_sec;    // whole seconds since Jan 1, 1970
    suseconds_t tv_usec;   // microseconds within that second (0-999999)
};
struct timeval tv;

gettimeofday(&tv, NULL);

You can't subtract two struct timeval directly, and you can't log tv_sec and tv_usec separately and expect it to match the subject's timestamp_in_ms format โ€” you need to convert both fields into a single number of milliseconds first:

long    now_ms;

now_ms = tv.tv_sec * 1000 + tv.tv_usec / 1000;
  • tv_sec * 1000 โ€” turns whole seconds into milliseconds
  • tv_usec / 1000 โ€” turns the leftover microseconds into milliseconds (integer division drops anything below 1ms, which is fine at this precision)

Getting a timestamp relative to simulation start

The subject's example log starts at 0, not at some huge epoch number like 1754582400000 โ€” that means every timestamp you print is relative to when the simulation began, not the absolute system clock.

The pattern is always the same, everywhere in the program: 1. Once, at the very start of main (or wherever you initialize t_simulation), call gettimeofday() and convert it to start_time_ms using the formula above โ€” store that value in the shared struct 2. Every time you need "the current timestamp" for a log line, call gettimeofday() again, convert it the same way, and subtract the stored start_time_ms

long    elapsed_ms;

elapsed_ms = now_ms - simul->start_time_ms;

That elapsed_ms is exactly the timestamp_in_ms the subject wants printed โ€” 0 right at the start, 201 two hundred-and-one milliseconds later, and so on.

Common mistake: forgetting to convert tv_sec/tv_usec into the same unit before subtracting, or mixing a value computed with gettimeofday() against one computed with clock_gettime() somewhere else โ€” the two clocks aren't guaranteed to agree on the same "zero point," so subtracting one from the other gives garbage. Pick one function, use it everywhere, always convert to milliseconds the same way.

๐Ÿ’ค usleep() โ€” the basics

usleep(useconds_t usec) pauses the calling thread for at least usec microseconds, then lets it continue. Nothing else happens automatically โ€” it doesn't return a value you need to check in normal use, and it only affects the thread that called it, not the whole program.

usleep(200000);   // pause this thread for at least 200,000 microseconds (200ms)

The unit trips people up constantly: microseconds, not milliseconds. Since this project's arguments (time_to_compile, time_to_debug, etc.) are given in milliseconds, you need to convert before calling usleep:

usleep(simul->time_to_compile * 1000);   // ms -> us, multiply by 1000

That's genuinely the whole basic API โ€” no struct to fill in, no flags, just a number of microseconds to wait. Where it gets subtle is in how precisely it actually wakes up, covered below.

๐Ÿ’ค usleep() and its limits

usleep(microseconds) suspends a thread for at least the given time โ€” the OS scheduler doesn't guarantee it wakes up exactly on time. Sleeping in long fixed chunks (e.g., usleep(time_to_burnout * 1000)) means your wakeup could be anywhere up to that long late, which risks blowing the 10ms window.

Practical fix: don't sleep for the entire duration in one shot. Either: - Sleep in small increments (e.g., 1โ€“5ms) and re-check the elapsed time each loop, or - Use pthread_cond_timedwait with a precise deadline, which wakes up close to the actual target time rather than "whenever the sleep happens to end"

pthread_cond_wait vs pthread_cond_timedwait โ€” a different kind of "waking up late"

pthread_cond_wait only wakes a thread when someone else calls signal or broadcast on that condition variable. Nothing else will ever wake it โ€” not a timeout, not a change elsewhere in the program. Now think about the dongle_cooldown requirement: a dongle only becomes usable again once its cooldown has elapsed, which is a passage of time, not an event anyone signals. If a thread is asleep in pthread_cond_wait waiting for a dongle, and the only thing that will ever call signal/broadcast on it is "someone releases this dongle" โ€” what wakes that thread up once the cooldown itself expires, if nobody releases (or re-releases) anything in the meantime?

pthread_cond_timedwait adds a deadline to the wait: the thread wakes up either when signaled or when the deadline passes, whichever comes first โ€” without anyone needing to signal it. Think about which of your waiting conditions are event-driven ("someone freed a dongle") versus time-driven ("the cooldown window closes") before deciding which wait primitive fits which situation โ€” and what happens if you use the wrong one for a time-driven condition.

๐Ÿ›ฐ๏ธ The monitor thread's job

A separate thread (not one of the coder threads) is responsible for watching everyone's last-compile-start timestamp and comparing it against time_to_burnout. It needs to: 1. Check frequently enough that it never misses the 10ms window 2. Log the burnout message immediately upon detection 3. Signal the rest of the program to stop (all coder threads, cleanly)

A polling loop with a short sleep interval (a few ms) is a simple, defensible approach here โ€” precise enough to hit the deadline, cheap enough not to burn CPU.

When exactly does a coder notice the simulation should stop?

A shared "should stop" flag only helps if something actually checks it. Think through a coder's full cycle โ€” compile, debug, refactor, repeat โ€” and ask: at which points does the coder thread actually look at that flag? If it's only checked once, at the very top of the loop, what happens to a coder that's already asleep inside a long usleep for time_to_debug when another coder burns out? Does it finish that sleep and print is debugging anyway (and possibly start is refactoring too) after the burnout was already logged? Is that acceptable, or does it contradict "the simulation stops when a coder burns out"? There's no single universally-agreed answer baked into the subject here โ€” but it's a decision worth making on purpose, not by accident of where you happened to put the check.

๐Ÿง  Practical checklist

  • [ ] Pick one clock function and use it consistently everywhere (mixing gettimeofday() and clock_gettime() invites subtle bugs)
  • [ ] Never sleep for a whole phase duration in one blocking call if precision matters
  • [ ] Monitor thread polls at a granularity well under 10ms (e.g., 1โ€“2ms)
  • [ ] Timestamps in logs are relative to the simulation start, not absolute epoch time

๐Ÿ”— Why this matters for Codexion

  • The whole grading of the "hardest" behavioral requirement (precise burnout detection) hinges on timing discipline, not clever locking
  • This is also where test flakiness usually comes from โ€” allow yourself a small tolerance when testing locally, since hardware/OS scheduling adds jitter, but don't design the logic around imprecision

๐Ÿ“š Further reading

  • man gettimeofday, man clock_gettime, man usleep
  • POSIX real-time clocks documentation