From 4017446d08cf4cae51f503849346a3070095f74d Mon Sep 17 00:00:00 2001 From: Vincent Thomas Date: Fri, 24 Apr 2026 11:51:01 +0200 Subject: [PATCH] clarified contract docs and added flush-related contract tests --- lio-test/src/lib.rs | 59 ++++++++++++++++++++++++++++++++++++++++++ lio/src/backend/mod.rs | 13 ++++++++-- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/lio-test/src/lib.rs b/lio-test/src/lib.rs index 68d5c59e..944c31d1 100644 --- a/lio-test/src/lib.rs +++ b/lio-test/src/lib.rs @@ -341,6 +341,36 @@ macro_rules! test_io_backend { ); } + #[cfg(unix)] + #[test] + fn pushed_work_is_not_observable_until_flush() { + let mut backend = new_backend(); + backend.init(64).unwrap(); + + let path = unix_socket_path("not-flushed"); + let storage = unix_sockaddr_un(&path); + + push_op(&mut backend, + 90, + Op::Connect { + fd: invalid_fd_resource(), + addr: storage, + }, + ); + + let completed = wait_completions(&mut backend, Some(Duration::ZERO)); + assert!( + completed.is_empty(), + "queued work must not become observable before flush()" + ); + + backend.flush().unwrap(); + + let completed = wait_completions(&mut backend, Some(Duration::ZERO)); + assert_eq!(completed.len(), 1); + assert_exact_result(&completed[0], 90, -(libc::EBADF as isize)); + } + #[cfg(unix)] #[test] fn flush_can_produce_immediate_completions_without_pending_work() { @@ -2585,6 +2615,35 @@ macro_rules! test_io_backend { ); } + #[test] + fn second_flush_does_not_replay_already_submitted_work() { + let mut backend = new_backend(); + backend.init(64).unwrap(); + + let path = unix_socket_path("flush-idempotent"); + let storage = unix_sockaddr_un(&path); + + push_op(&mut backend, + 63, + Op::Connect { + fd: invalid_fd_resource(), + addr: storage, + }, + ); + backend.flush().unwrap(); + backend.flush().unwrap(); + + let completed = wait_completions(&mut backend, Some(Duration::ZERO)); + assert_eq!(completed.len(), 1); + assert_exact_result(&completed[0], 63, -(libc::EBADF as isize)); + + let completed = wait_completions(&mut backend, Some(Duration::ZERO)); + assert!( + completed.is_empty(), + "a second flush() must not replay already-submitted work" + ); + } + #[test] fn completions_are_not_duplicated_across_waits() { let mut backend = new_backend(); diff --git a/lio/src/backend/mod.rs b/lio/src/backend/mod.rs index 2f7ccb44..83ee8f7c 100644 --- a/lio/src/backend/mod.rs +++ b/lio/src/backend/mod.rs @@ -133,6 +133,15 @@ impl OpCompleted { /// Designed for single-thread ownership (`&mut self`), dyn-compatible for /// runtime backend selection via `Box`. /// +/// Contract: +/// - `init()` must be called before `push()`, `flush()`, or `wait()` +/// - `push()` only queues work locally; queued operations are not observable +/// until `flush()` submits them +/// - `flush()` submits all currently queued operations and may also make +/// immediate completions observable on the next `wait()` +/// - `wait()` writes zero or more completions into the caller-provided +/// `completed` vector for that call only +/// /// # Usage /// /// ```ignore @@ -162,8 +171,8 @@ pub trait IoBackend { /// - `None` = block until at least one completion /// - `Some(ZERO)` = non-blocking poll /// - `Some(duration)` = wait up to duration - /// - /// Slice valid until next `wait()` or `push()`. + /// - `completed` is caller-owned output storage; implementations may clear + /// and rewrite it on each call fn wait( &mut self, timeout: Option,