Skip to content

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

Open
HalFrgrd wants to merge 5 commits into
bytecodealliance:mainfrom
HalFrgrd:main
Open

fix(bug): ns->us overflow handling#1655
HalFrgrd wants to merge 5 commits into
bytecodealliance:mainfrom
HalFrgrd:main

Conversation

@HalFrgrd

@HalFrgrd HalFrgrd commented Aug 7, 2026

Copy link
Copy Markdown

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
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.

1 participant