From 5581b00d71b6ed32fdd6919ed7557c843887728c Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Wed, 11 Nov 2026 21:40:52 -0800 Subject: [PATCH] Submit the rest of a short Linux AIO batch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit io_submit returns the number of requests it accepted. If a later request fails, a positive return hides that error. LinuxQueue discarded the entire batch, so a caller waiting for the remaining completions could block forever. Advance by the accepted count and submit the remaining requests before waiting. If the next request fails, return its error to the caller. Auto-fixed-by: [🤖] @EtiennePerot FUTURE_COPYBARA_INTEGRATE_REVIEW=https://github.com/google/gvisor/pull/16816 from tamird:aio-short-submit 1b27c4020492a36a499db03f9bbf7cbff116d7e9 PiperOrigin-RevId: 1019766675 --- pkg/aio/aio_linux_test.go | 27 +++++++++++++++++++++++++++ pkg/aio/aio_linux_unsafe.go | 9 ++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/pkg/aio/aio_linux_test.go b/pkg/aio/aio_linux_test.go index f438b0ec956..b54c06c7f16 100644 --- a/pkg/aio/aio_linux_test.go +++ b/pkg/aio/aio_linux_test.go @@ -18,7 +18,11 @@ package aio import ( + "errors" + "os" "testing" + + "golang.org/x/sys/unix" ) func TestLinuxQueue(t *testing.T) { @@ -27,3 +31,26 @@ func TestLinuxQueue(t *testing.T) { return q, err }) } + +func TestLinuxQueueShortSubmit(t *testing.T) { + f, err := os.Open(os.DevNull) + if err != nil { + t.Fatal(err) + } + defer f.Close() + q, err := NewLinuxQueue(2) + if err != nil { + t.Fatal(err) + } + defer q.Destroy() + + // Linux accepts the read, then rejects the write to the same read-only + // descriptor. Waiting for one completion lets a lost write fail the test + // without blocking on a completion the kernel will never produce. + fd := int32(f.Fd()) + Read(q, 0, fd, 0, nil) + Write(q, 1, fd, 0, nil) + if _, err := q.Wait(nil, 1); !errors.Is(err, unix.EBADF) { + t.Errorf("submitting a write to a read-only descriptor: got %v, want %v", err, unix.EBADF) + } +} diff --git a/pkg/aio/aio_linux_unsafe.go b/pkg/aio/aio_linux_unsafe.go index deb4848814f..5ebe6539b1c 100644 --- a/pkg/aio/aio_linux_unsafe.go +++ b/pkg/aio/aio_linux_unsafe.go @@ -112,13 +112,16 @@ func (q *LinuxQueue) Add(r Request) { // Wait implements Queue.Wait. func (q *LinuxQueue) Wait(cs []Completion, minCompletions int) ([]Completion, error) { - if len(q.iocbs) != 0 { - _, _, e := unix.Syscall(unix.SYS_IO_SUBMIT, q.ctxID, uintptr(len(q.iocbs)), uintptr(unsafe.Pointer(unsafe.SliceData(q.iocbPtrs)))) - q.iocbs = q.iocbs[:0] + // io_submit returns the number of accepted requests, even if a later + // request fails. Submit the remainder before waiting for its completions. + for pending := q.iocbPtrs[:len(q.iocbs)]; len(pending) > 0; { + n, _, e := unix.Syscall(unix.SYS_IO_SUBMIT, q.ctxID, uintptr(len(pending)), uintptr(unsafe.Pointer(unsafe.SliceData(pending)))) if e != 0 { return cs, e } + pending = pending[n:] } + q.iocbs = q.iocbs[:0] for { if ring := q.ring(); ring.Magic == linux.AIO_RING_MAGIC { nr := ring.Nr