Skip to content

channel: avoid panic when re-arming a tick channel with a huge duration - #1329

Open
shoemoney wants to merge 1 commit into
crossbeam-rs:mainfrom
shoemoney:fix/tick-saturate-next-delivery
Open

shoemoney wants to merge 1 commit into
crossbeam-rs:mainfrom
shoemoney:fix/tick-saturate-next-delivery

Conversation

@shoemoney

Copy link
Copy Markdown

Bug

tick() guards only the first delivery time:

// crossbeam-channel/src/channel.rs:338
match Instant::now().checked_add(duration) {
    Some(delivery_time) => Receiver { ... },
    None => never(),
}

The two re-arm sites inside tick.rs use unchecked +, so a duration that passes that
guard can still overflow when the next tick is computed:

  • crossbeam-channel/src/flavors/tick.rs:92 (try_recv): Align(now + self.duration)
  • crossbeam-channel/src/flavors/tick.rs:120 (recv): Align(delivery_time.0.max(now) + self.duration)

Any duration larger than half the representable Instant range but under the whole range
lands in that window. crossbeam_channel::tick(Duration::from_secs(1 << 62)) followed by
recv() panics instantly, because the CAS that computes the next delivery time runs before
the sleep. That is the opposite of the "wait forever on very large timeout" behavior that
commit 6951bc9 intended: that commit moved the saturating deadline conversion out of
tick::Channel::new into the checked_add in channel.rs, and the two re-arm sites were
left unchecked.

Fix

A small saturating helper in tick.rs, used at both sites. An unrepresentable next tick
means "effectively never", matching the None => never() arm in tick(). Halving rather
than unwrap_or(base) avoids re-arming in the past, which would make the channel fire in a
tight loop.

Evidence

Before, on current master (38dacb4):

running 1 test
test huge_duration_does_not_panic ... FAILED

---- huge_duration_does_not_panic stdout ----
thread '<unnamed>' panicked at crossbeam-channel/src/flavors/tick.rs:120:27:
overflow when adding duration to instant
thread 'huge_duration_does_not_panic' panicked at crossbeam-channel/tests/tick.rs:372:5:
recv() returned or panicked immediately

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 14 filtered out; finished in 0.20s

After:

running 15 tests
test fire ... ok
test huge_duration_does_not_panic ... ok
test intervals ... ok
test recv ... ok
test recv_timeout ... ok
test try_recv ... ok
...
test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.33s

cargo fmt --check and cargo clippy -p crossbeam-channel --lib --tests are clean.

Not fixed here

at()/after() in the at flavor are single-shot and do no re-arm arithmetic, so they are
untouched. I also left the never() fallback in channel.rs alone rather than widening it,
to keep the change minimal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant