Skip to content

fix(bug): ns->us overflow handling - #1655

Merged
sunfishcode merged 5 commits into
bytecodealliance:mainfrom
HalFrgrd:main
Sep 16, 2026
Merged

sunfishcode merged 5 commits into
bytecodealliance:mainfrom
HalFrgrd:main

Conversation

@HalFrgrd

@HalFrgrd HalFrgrd commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

I ran into an odd bug on macos. If my code polls for just under 5s (say 4.999_999_123 seconds), I immediately receive an EINVAL error.

This is due to the rounding logic. With the current logic, we set tv_usec to 1_000_000 when the nanosecond remainder is >= 999_999_001. This creates an invalid timeval on macOS and the OS returns EINVAL.

tv_usec=1_000_000 is considered invalid and instead we should increase tv_sec by 1 and set tv_usec=0.

Solution

Checking if tv_usec >= 1_000_000 https://github.com/apple-oss-distributions/xnu/blob/f6217f891ac0bb64f3d375211650a4c1ff8ca1ea/bsd/kern/kern_time.c#L670

XNU handling this issue in the same way that I propose: https://github.com/apple-oss-distributions/xnu/blob/f6217f891ac0bb64f3d375211650a4c1ff8ca1ea/bsd/sys/time.h#L175-L177

Linux kernel is more forgiving if you set usec to 1_000_000: https://github.com/torvalds/linux/blob/master/fs/select.c#L723-L725. So I could revert the changes here for linux code and only keep the macos fix. Please let me know what is best.

Reproducing

This is a small example that I have tested on macOS. select with a timeout of 999_999_123 fails using rustix master but is fixed when using my branch.

use rustix::event::{fd_set_insert, fd_set_num_elements, select, FdSetElement, Timespec};
use rustix::fd::AsRawFd;
use rustix::pipe::pipe;

fn main() {
    // Create a pipe so we have a valid file descriptor to pass to select()
    let (reader, _writer) = pipe().unwrap();
    let raw_fd = reader.as_raw_fd();
    let nfds = raw_fd + 1;
    let num_elems = fd_set_num_elements(1, nfds);

    // 1. 999_999_000 ns -> (999_999_000 + 999)/1000 = 999_999 μs (< 1,000,000 μs)
    let mut readfds1 = vec![FdSetElement::default(); num_elems];
    fd_set_insert(&mut readfds1, raw_fd);
    let res1 = unsafe {
        select(
            nfds,
            Some(&mut readfds1),
            None,
            None,
            Some(&Timespec {
                tv_sec: 0,
                tv_nsec: 999_999_000,
            }),
        )
    };
    println!("999_999_000 ns result: {:?}", res1);

    // 2. 999_999_123 ns -> (999_999_123 + 999)/1000 = 1_000_000 μs
    let mut readfds2 = vec![FdSetElement::default(); num_elems];
    fd_set_insert(&mut readfds2, raw_fd);
    let res2 = unsafe {
        select(
            nfds,
            Some(&mut readfds2),
            None,
            None,
            Some(&Timespec {
                tv_sec: 0,
                tv_nsec: 999_999_123,
            }),
        )
    };
    println!("999_999_123 ns result: {:?}", res2);
}

Testing

invalid_offset::invalid_offset_fadvise library test was failing on main. All other cargo test --features=all-apis tests pass. All cargo test --test event --features all-apis tests pass.

@HalFrgrd
HalFrgrd marked this pull request as ready for review August 16, 2026 14:31
@sunfishcode

Copy link
Copy Markdown
Member

Thanks!

@sunfishcode
sunfishcode merged commit 0cea188 into bytecodealliance:main Sep 16, 2026
18 of 51 checks passed
@sunfishcode

Copy link
Copy Markdown
Member

This is now released in rustix 1.1.5.

@ogoffart

Copy link
Copy Markdown

Looks like this broke the build on android.

This was catched in our CI:

error[E0433]: cannot find `timespec` in `crate`
   --> /home/runner/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rustix-1.1.5/src/backend/libc/net/sockopt.rs:237:29
    |
237 |             let ts = crate::timespec::Timespec {
    |                             ^^^^^^^^ could not find `timespec` in the crate root
    |
note: found an item that was configured out
   --> /home/runner/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rustix-1.1.5/src/lib.rs:410:5
    |
389 |   #[cfg(any(
    |  __________-
390 | |     feature = "fs",
391 | |     feature = "event",
392 | |     feature = "process",
...   |
409 | | ))]
    | |_- the item is gated here
410 |   mod timespec;
    |       ^^^^^^^^

error[E0433]: cannot find `timespec` in `crate`
   --> /home/runner/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rustix-1.1.5/src/backend/libc/net/sockopt.rs:241:39
    |
241 |                     .unwrap_or(crate::timespec::Secs::MAX),
    |                                       ^^^^^^^^ could not find `timespec` in the crate root
    |
note: found an item that was configured out
   --> /home/runner/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rustix-1.1.5/src/lib.rs:410:5
    |
389 |   #[cfg(any(
    |  __________-
390 | |     feature = "fs",
391 | |     feature = "event",
392 | |     feature = "process",
...   |
409 | | ))]
    | |_- the item is gated here
410 |   mod timespec;
    |       ^^^^^^^^

error[E0433]: cannot find `timespec` in `crate`
   --> /home/runner/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rustix-1.1.5/src/backend/libc/net/sockopt.rs:244:66
    |
244 |             let (sec, usec) = ts.to_sec_usec().unwrap_or((crate::timespec::Secs::MAX, 0));
    |                                                                  ^^^^^^^^ could not find `timespec` in the crate root
    |
note: found an item that was configured out
   --> /home/runner/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rustix-1.1.5/src/lib.rs:410:5
    |
389 |   #[cfg(any(
    |  __________-
390 | |     feature = "fs",
391 | |     feature = "event",
392 | |     feature = "process",
...   |
409 | | ))]
    | |_- the item is gated here
410 |   mod timespec;
    |       ^^^^^^^^

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants