aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Johnston <markj@FreeBSD.org>2022-06-14 14:52:03 +0000
committerMark Johnston <markj@FreeBSD.org>2022-07-14 13:50:10 +0000
commita70c03b2d383011dece503716faf504598d5fe29 (patch)
treefdef17004df1b13b4b278599bfb0a0051eee8bdb
parenteedf9f1e93bd1ee9448550b140dbbd2ab5926055 (diff)
downloadsrc-a70c03b2d383011dece503716faf504598d5fe29.tar.gz
src-a70c03b2d383011dece503716faf504598d5fe29.zip
pipe: Use a distinct wait channel for I/O serialization
Suppose a thread tries to read from an empty pipe. pipe_read() does the following: 1. pipelock(), possibly sleeping 2. check for buffered data 3. pipeunlock() 4. set PIPE_WANTR and sleep 5. goto 1 pipelock() is an open-coded mutex; if a thread blocks in pipelock(), it sleeps until the lock holder calls pipeunlock(). Both sleeps use the same wait channel. So if there are multiple threads in pipe_read(), a thread T1 in step 3 can wake up a thread T2 sleeping in step 4. Then T1 goes to sleep in step 4, and T2 acquires and releases the pipelock, waking up T1 again. This can go on indefinitely, livelocking the process (and potentially starving a would-be writer). Fix the problem by using a separate wait channel for pipelock(). Reported by: Paul Floyd <paulf2718@gmail.com> Reviewed by: mjg, kib PR: 264441 Sponsored by: The FreeBSD Foundation (cherry picked from commit e8955bd643ee852d70a0b065f2a0d1bb3fa99df2)
-rw-r--r--sys/kern/sys_pipe.c9
1 files changed, 4 insertions, 5 deletions
diff --git a/sys/kern/sys_pipe.c b/sys/kern/sys_pipe.c
index 0e19bf8ae7b4..ad166ee992e9 100644
--- a/sys/kern/sys_pipe.c
+++ b/sys/kern/sys_pipe.c
@@ -627,8 +627,8 @@ pipelock(struct pipe *cpipe, int catch)
("%s: bad waiter count %d", __func__,
cpipe->pipe_waiters));
cpipe->pipe_waiters++;
- error = msleep(cpipe, PIPE_MTX(cpipe),
- prio, "pipelk", 0);
+ error = msleep(&cpipe->pipe_waiters, PIPE_MTX(cpipe), prio,
+ "pipelk", 0);
cpipe->pipe_waiters--;
if (error != 0)
return (error);
@@ -651,9 +651,8 @@ pipeunlock(struct pipe *cpipe)
("%s: bad waiter count %d", __func__,
cpipe->pipe_waiters));
cpipe->pipe_state &= ~PIPE_LOCKFL;
- if (cpipe->pipe_waiters > 0) {
- wakeup_one(cpipe);
- }
+ if (cpipe->pipe_waiters > 0)
+ wakeup_one(&cpipe->pipe_waiters);
}
void