Sporadic EINVAL on writev syscall with nonblocking TCP socket

I'm experiencing sporadic EINVAL result when I call writev syscall with a nonblocking TCP socket.

The error happens at this line of doctest

I don't see any errors when I run this test in Linux container.

The test sends a static string "test" compiled into binary.

The tested runtime allocates io_vec structure with a single buffer inside a loop until the whole buffer is fully sent.

The io slice is captured in a task future (the only one in the test) that is allocated on the heap by async executor. The future is desugared into a state machine that has scopes corresponding to async functions nested in the task future. Rust compiler drops the io slice in the end of send function scope. This happens after each successfully finished write (when syscall result is not EAGAIN).

The runtime retries writev syscall when kqueue driver signals that the socket file descriptor is readable.

I get the EINVAL error in about 25% of test runs.

Environment: macOS Ventura 13.5.2, Intel CPU.

writev can fail with EINVAL for a variety of documented reasons. See the writev man page for the full list. It’s also possible that you’re hitting some other, undocumented reason. If I were debugging this I’d wrap writev like this [1]:

static ssize_t my_writev(int fildes, const struct iovec * iov, size_t iovcnt) {
    ssize_t result = write(fildes, iov, iovcnt);
    if ((result < 0) && (errno == EINVAL)) {
        __builtin_trap();
    }
    return result;
}

and then, when you trap, look at iov and iovcnt to see if they match any of the documented reasons for EINVAL.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Sorry this is C. I don’t know how to do something like that in Rust.

Sporadic EINVAL on writev syscall with nonblocking TCP socket
 
 
Q