Skip to content

Additional single byte write syscall when future is not spawned before block_on with current_thread runtime #7570

@lrowe

Description

@lrowe

Version
tokio v1.47.1
tokio-macros v2.5.0 (proc-macro)

Platform
Linux hostname 6.11.0-25-generic #25~24.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue Apr 15 17:20:50 UTC 2 x86_64 x86_64 x86_64 GNU/Linux

Description

When the following rust program is run under strace there is an additional single byte write(4, "\1\0\0\0\0\0\0\0", 8) syscall when a request is made with curl http://localhost:8000

#!/usr/bin/env -S cargo +nightly -Zscript
---
[dependencies]
tokio = { version = "1.47.1", features = ["full"] }
---
use tokio::io::AsyncReadExt;
use tokio::io::AsyncWriteExt;
use tokio::net::TcpListener;

#[tokio::main(flavor = "current_thread")]
async fn main() {
    let addr = "127.0.0.1:8000".to_string();
    let server = TcpListener::bind(&addr).await.unwrap();
    loop {
        let (mut stream, _) = server.accept().await.unwrap();
        tokio::spawn(async move {
            let mut req = [0; 4096];
            let _bytes_read = stream.read(&mut req).await.unwrap();
            stream
                .write_all(
                    b"HTTP/1.1 200 OK\r\n\
                    Connection: close\r\n\
                    Content-Type: text/plain; charset=utf-8\r\n\
                    \r\n\
                    Hello, World!",
                )
                .await
                .unwrap();
            stream.shutdown().await.unwrap_or_default();
        });
    }
}

strace output for a single curl:

epoll_wait(3, [{events=EPOLLIN, data={u32=1061629696, u64=99558403550976}}], 1024, -1) = 1
write(4, "\1\0\0\0\0\0\0\0", 8)         = 8
accept4(9, {sa_family=AF_INET, sin_port=htons(50978), sin_addr=inet_addr("127.0.0.1")}, [128 => 16], SOCK_CLOEXEC|SOCK_NONBLOCK) = 10
epoll_ctl(5, EPOLL_CTL_ADD, 10, {events=EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, data={u32=1061630080, u64=99558403551360}}) = 0
accept4(9, 0x7ffc5e4a3c70, [128], SOCK_CLOEXEC|SOCK_NONBLOCK) = -1 EAGAIN (Resource temporarily unavailable)
epoll_wait(3, [{events=EPOLLIN, data={u32=0, u64=0}}, {events=EPOLLIN|EPOLLOUT, data={u32=1061630080, u64=99558403551360}}], 1024, -1) = 2
recvfrom(10, "GET / HTTP/1.1\r\nHost: localhost:"..., 4096, 0, NULL, NULL) = 77
sendto(10, "HTTP/1.1 200 OK\r\nConnection: clo"..., 92, MSG_NOSIGNAL, NULL, 0) = 92
shutdown(10, SHUT_WR)                   = 0
epoll_ctl(5, EPOLL_CTL_DEL, 10, NULL)   = 0
fcntl(10, F_GETFD)                      = 0x1 (flags FD_CLOEXEC)
close(10)                               = 0

This does not happen when I rt.spawn the future before calling rt.block_on.

#!/usr/bin/env -S cargo +nightly -Zscript
---
[dependencies]
tokio = { version = "1.47.1", features = ["full"] }
---
use tokio::io::AsyncReadExt;
use tokio::io::AsyncWriteExt;
use tokio::net::TcpListener;

fn main() {
    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();
    // Spawn future to avoid extra single byte write syscall per loop.
    let future = rt.spawn(async {
        let addr = "127.0.0.1:8000".to_string();
        let server = TcpListener::bind(&addr).await.unwrap();
        loop {
            let (mut stream, _) = server.accept().await.unwrap();
            tokio::spawn(async move {
                let mut req = [0; 4096];
                let _bytes_read = stream.read(&mut req).await.unwrap();
                stream
                    .write_all(
                        b"HTTP/1.1 200 OK\r\n\
                        Connection: close\r\n\
                        Content-Type: text/plain; charset=utf-8\r\n\
                        \r\n\
                        Hello, World!",
                    )
                    .await
                    .unwrap();
                stream.shutdown().await.unwrap_or_default();
            });
        }
    });
    rt.block_on(future).unwrap();
}

strace output for a single curl without additional single byte write syscall:

epoll_wait(3, [{events=EPOLLIN, data={u32=3591807232, u64=102004770119936}}], 1024, -1) = 1
accept4(9, {sa_family=AF_INET, sin_port=htons(52860), sin_addr=inet_addr("127.0.0.1")}, [128 => 16], SOCK_CLOEXEC|SOCK_NONBLOCK) = 10
epoll_ctl(5, EPOLL_CTL_ADD, 10, {events=EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, data={u32=3591807616, u64=102004770120320}}) = 0
accept4(9, 0x7ffcf2c12a30, [128], SOCK_CLOEXEC|SOCK_NONBLOCK) = -1 EAGAIN (Resource temporarily unavailable)
epoll_wait(3, [{events=EPOLLIN|EPOLLOUT, data={u32=3591807616, u64=102004770120320}}], 1024, -1) = 1
recvfrom(10, "GET / HTTP/1.1\r\nHost: localhost:"..., 4096, 0, NULL, NULL) = 77
sendto(10, "HTTP/1.1 200 OK\r\nConnection: clo"..., 92, MSG_NOSIGNAL, NULL, 0) = 92
shutdown(10, SHUT_WR)                   = 0
epoll_ctl(5, EPOLL_CTL_DEL, 10, NULL)   = 0
fcntl(10, F_GETFD)                      = 0x1 (flags FD_CLOEXEC)
close(10)                               = 0

Possibly related to #2287 but here I'm using the current_thread runtime so there seems little reason to wake thread.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-tokioArea: The main tokio crate

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions