An timer wheel advances by a fixed time-step and contains a fixed
number of slots. If, when adding an event, we find that the event is
too far in the future, it gets added to an outer wheel, whose time
step is the inner wheel's time step, multiplied by the number of slots
in the inner wheel.
When the timer runs and an inner wheel's timer wraps around to 0, one
timestep's worth of events are moved from the outer wheel to the inner
wheel.
This process had a bug. Given that floating-point arithmetic is
inexact, it's possible that an event from the outer wheel may have a
timestamp that's either slightly before or slightly after the time
range covered by the inner wheel. We already protected against the
latter, but we were missing a check against the former.
An timer wheel advances by a fixed time-step and contains a fixed number of slots. If, when adding an event, we find that the event is too far in the future, it gets added to an outer wheel, whose time step is the inner wheel's time step, multiplied by the number of slots in the inner wheel.
When the timer runs and an inner wheel's timer wraps around to 0, one timestep's worth of events are moved from the outer wheel to the inner wheel.
This process had a bug. Given that floating-point arithmetic is inexact, it's possible that an event from the outer wheel may have a timestamp that's either slightly before or slightly after the time range covered by the inner wheel. We already protected against the latter, but we were missing a check against the former.
See e.g. https://gist.github.com/lwaftr-igalia/e9479e1d9ff04f008b86d040ed9c403e for a failure log containing this error:
Note that
idx
here is -1, indicating a value just before the time range for the inner wheel.