Skip to content

Conversation

@winter-loo
Copy link

Motivation

According to the Linux eventfd doc:

Applications can use an eventfd file descriptor instead of a pipe
(see pipe(2)) in all cases where a pipe is used simply to signal
events. The kernel overhead of an eventfd file descriptor is much
lower than that of a pipe, and only one file descriptor is
required (versus the two required for a pipe).

So, I made an attempt to replace mio::UnixStream with libc::eventfd.

Solution

The benchmark(RUSTFLAGS="" cargo bench -p benches --bench signal) shows some improvements:

  1. On arch linux, the performance improvement is about 5%.
  2. On ubuntu inside WSL 2, the performance gain is about 15%
  3. On centos 7, the performance gain is about 10%
image

fallback to UnixStream on non-Linux platform
@ADD-SP ADD-SP added A-tokio Area: The main tokio crate M-signal Module: tokio/signal T-performance Topic: performance and benchmarks labels Jan 9, 2026
@Darksonn Darksonn requested a review from ipetkov January 9, 2026 09:02
Comment on lines 109 to 111
if fd < 0 {
panic!("eventfd failed: {}", io::Error::last_os_error());
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of panicking can we return an error here?

std::os::unix::net::UnixStream::from_raw_fd(receiver_fd)
});
let inner =
UnixStream::from_std(original.try_clone().expect("failed to clone UnixStream"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment, this is a fallible operation, we should return an error here, not panic

@winter-loo
Copy link
Author

Thanks for review. It's time to make it better.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comparing with what mio does, this seems to not have a bunch of logic to reset the eventfd and so on... Is it missing here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A read on fd returned by eventfd resets eventfd. Here's libc::eventfd_read in Receiver::read() function.

@winter-loo winter-loo requested a review from ipetkov January 20, 2026 18:22
OsStorage: 'static + Send + Sync + Default,
{
static GLOBALS: OnceLock<Globals> = OnceLock::new();
static GLOBALS: OnceLock<std::io::Result<Globals>> = OnceLock::new();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drive by review: I think you should store Result<Globals, i32> like I did for the per-signal result storage. You can then convert the i32 to an error via Error::from_raw_os_error and avoid allocating.

}
}

impl Sender {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be merged with the impl above.

if r == 0 {
Ok(0)
} else {
Err(std::io::Error::last_os_error())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it is non-blocking it may return EAGAIN (ErrorKind::WouldBlock) which is not an error.

    let err = std::io::Error::last_os_error();
    // On a non-blocking eventfd, it is expected to get an EAGAIN
    // when the counter is 0.
    if err.kind() == std::io::ErrorKind::WouldBlock {
        Ok(0)
    } else {
        Err(err)
    }

}
}

impl OsExtraData {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This impl could be merged with the one above

// SAFETY: it's ok to call libc API
let r = unsafe { libc::eventfd_write(self.fd.as_raw_fd(), 1) };
if r == 0 {
Ok(0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a small different between the eventfd and unixstream impls here.
The eventfd's Sender::write() returns 0 on success. The UnixStream returns the number of written bytes (1).
AFAIS the successful result is not used.
Maybe change eventfd to return 1 too or change both to return Result<()> ?!

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

Labels

A-tokio Area: The main tokio crate M-signal Module: tokio/signal T-performance Topic: performance and benchmarks

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants