Skip to content

Commit 1f4e6b6

Browse files
authored
fix(pipe): correct waitqueue wakeup semantics (#2148)
* fix(pipe): correct waitqueue wakeup semantics Track blocked writer intents while holding the pipe state lock and choose single or broadcast wakeups according to whether heterogeneous write predicates may be waiting. Reuse the lock guard returned by the wait path so predicate publication, sleep, and revalidation remain ordered. Move waitqueue, epoll, async I/O, and SIGPIPE notifications outside pipe spinlocks. Preserve partial-write results and observer notifications when readers close, and align zero-length, nonblocking EPIPE, resize, splice, and tee behavior with Linux semantics. Prevent splice-held data from being consumed twice and keep tee source data intact while preserving partial progress. Add deterministic dunitest coverage for endpoint side effects, writer eligibility, partial-write notifications, splice and tee wakeups, reader baton passing, and resize threshold changes. Signed-off-by: longjin <longjin@dragonos.org> * fix(pipe): select eligible writer waiters Replace unconditional multi-writer broadcasts with predicate-tagged waits. Track the free-space requirement of each blocked writer, choose one eligible class in round-robin order, and wake only that class to avoid thundering-herd retries. Preserve progress with bounded fallback across the register-before-enqueue window. Pass the writer baton only after the selected write or splice operation completes or aborts, and pass the reader baton when an interrupted reader leaves consumable data behind. Keep tagged waitqueue lookup and cleanup bounded, reserve the ordinary waiter tag, and move wake-all destruction outside the IRQ-disabled critical section. Add homogeneous and heterogeneous writer, splice, tee, signal, and resize regression coverage. Tests: make fmt Tests: make kernel Tests: pipe_waitqueue_wakeup_test (11/11) Tests: pipe_release_test (8/8) Tests: splice_concurrent_io_test (7/7) Tests: epoll_timeout_budget_test (1/1) Tests: TCPResetDuringClose (12/12, twice) Signed-off-by: longjin <longjin@dragonos.org> * fix(pipe): serialize splice writer transactions Add a per-pipe writer transaction that coordinates writes, output-side splice operations, endpoint close, and pipe resizing. Blocking writers publish their tagged wait predicate before releasing the transaction and pass an eligible writer baton only when another published waiter remains. Keep file-to-pipe splice ownership across the input read and commit so competing writers cannot steal previously observed capacity. Validate readers before consuming stream input, including nonblocking EPIPE handling, and retain destination-only transaction locking for pipe-to-pipe splice and tee to avoid ABBA. Add Dunitest regressions for no-reader stream preservation and competing writer serialization. Bound regression reads with poll deadlines so incorrect behavior fails deterministically instead of timing out. Signed-off-by: longjin <longjin@dragonos.org> --------- Signed-off-by: longjin <longjin@dragonos.org>
1 parent 0f4e53c commit 1f4e6b6

4 files changed

Lines changed: 1942 additions & 324 deletions

File tree

kernel/src/filesystem/vfs/syscall/sys_splice.rs

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::filesystem::vfs::file::FileMode;
33
use crate::filesystem::vfs::FileFlags;
44
use crate::filesystem::vfs::{file::File, syscall::SpliceFlags, FileType};
55
use crate::ipc::kill::send_signal_to_pid;
6-
use crate::ipc::pipe::{LockedPipeInode, PIPE_BUF};
6+
use crate::ipc::pipe::LockedPipeInode;
77
use crate::process::resource::RLimitID;
88
use crate::process::ProcessManager;
99
use crate::syscall::table::Syscall;
@@ -256,28 +256,19 @@ fn splice_file_to_pipe(
256256

257257
let limit = len.min(4096);
258258
let trusted_read_limit = splice_trusted_file_read_limit(file, offset, limit);
259+
let mut transaction = pipe_inode.begin_writer_transaction();
260+
pipe_inode.ensure_splice_readers(&transaction)?;
259261
if trusted_read_limit == Some(0) {
260262
return Ok(0);
261263
}
262264
let wanted = trusted_read_limit.unwrap_or(limit);
263265

264266
let space = if flags.contains(SpliceFlags::SPLICE_F_NONBLOCK) {
265-
let space = pipe_inode.writable_len();
266-
if space == 0 && pipe_inode.has_readers() {
267-
return Err(SystemError::EAGAIN_OR_EWOULDBLOCK);
268-
}
269-
if trusted_read_limit.is_some()
270-
&& wanted <= PIPE_BUF
271-
&& space < wanted
272-
&& pipe_inode.has_readers()
273-
{
274-
return Err(SystemError::EAGAIN_OR_EWOULDBLOCK);
275-
}
276-
space
267+
pipe_inode.writable_for_splice_nonblock(&transaction, trusted_read_limit.map(|_| wanted))?
277268
} else if trusted_read_limit.is_some() {
278-
pipe_inode.wait_writable_for_splice(wanted)?
269+
pipe_inode.wait_writable_for_splice(&mut transaction, wanted)?
279270
} else {
280-
pipe_inode.wait_writable_any_for_splice()?
271+
pipe_inode.wait_writable_any_for_splice(&mut transaction)?
281272
};
282273

283274
let buf_size = if trusted_read_limit.is_some() && space == 0 {
@@ -290,11 +281,14 @@ fn splice_file_to_pipe(
290281
// 从文件读取
291282
// 为了满足 Linux 语义:若后续写入 pipe 被信号中断且未写入任何字节,
292283
// 则不应推进输入文件的 file position。
293-
let (read_len, advance_file_pos) = if let Some(off) = offset {
294-
(file.pread(off, buf_size, &mut buffer)?, false)
284+
let read_result = if let Some(off) = offset {
285+
file.pread(off, buf_size, &mut buffer)
286+
.map(|read_len| (read_len, false))
295287
} else {
296-
(file.read_noadv(buf_size, &mut buffer)?, true)
288+
file.read_noadv(buf_size, &mut buffer)
289+
.map(|read_len| (read_len, true))
297290
};
291+
let (read_len, advance_file_pos) = read_result?;
298292

299293
if read_len == 0 {
300294
return Ok(0);
@@ -303,11 +297,7 @@ fn splice_file_to_pipe(
303297
buffer.truncate(read_len);
304298

305299
// 写入 pipe
306-
let written = if flags.contains(SpliceFlags::SPLICE_F_NONBLOCK) {
307-
pipe_inode.write_from_splice_nonblock(&buffer)
308-
} else {
309-
pipe.write(buffer.len(), &buffer)
310-
};
300+
let written = pipe_inode.write_from_splice_locked(&mut transaction, &buffer);
311301

312302
match written {
313303
Ok(write_len) => {

0 commit comments

Comments
 (0)