mirror of
https://github.com/tmux/tmux.git
synced 2026-07-11 11:59:32 +00:00
Checks that once a control client is marked for exit the server stops queueing notifications for it (PR 5357). The client's output is drained only slowly so a backlog holds the exit handshake open; the client is then detached and the window renamed, and the %window-renamed notifications must not appear in the client's output stream.
112 lines
3.7 KiB
Bash
112 lines
3.7 KiB
Bash
#!/bin/sh
|
|
|
|
# Once a control client is marked for exit (by detach-client here, or by the
|
|
# "too far behind" eviction in control_check_age), the server must stop
|
|
# queueing new output and notifications for it. The exit handshake only waits
|
|
# for already-queued blocks to drain (control_all_done), so anything queued
|
|
# after that point is written to a client that will never read it, and can hold
|
|
# the handshake - and the pane buffer it pins - open. This checks that a
|
|
# notification generated while the client is exiting never reaches its output.
|
|
#
|
|
# To see this in seconds rather than waiting out CONTROL_MAXIMUM_AGE (300s) the
|
|
# control client's output goes down a fifo that a helper drains only slowly.
|
|
# The slow drain keeps a backlog stuck in the server so the exit handshake
|
|
# cannot complete; while it is stuck we detach the client (which sets the exit
|
|
# flag) and rename the window to a marker. Draining the fifo must then not turn
|
|
# up the marker as a %window-renamed line.
|
|
|
|
PATH=/bin:/usr/bin
|
|
TERM=screen
|
|
|
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
TMUX="$TEST_TMUX -Ltest"
|
|
$TMUX kill-server 2>/dev/null
|
|
|
|
DIR=$(mktemp -d)
|
|
FIFO=$DIR/fifo
|
|
OUT=$DIR/out
|
|
MARKER=exitmarker
|
|
READER=
|
|
|
|
mkfifo "$FIFO" || exit 1
|
|
: >"$OUT"
|
|
|
|
cleanup() {
|
|
[ -n "$READER" ] && kill "$READER" 2>/dev/null
|
|
$TMUX kill-server 2>/dev/null
|
|
exec 8<&- 2>/dev/null
|
|
rm -rf "$DIR"
|
|
}
|
|
trap cleanup 0 1 15
|
|
|
|
# A detached session whose pane floods printable output forever.
|
|
$TMUX -f/dev/null new -d -x 80 -y 24 -s rt 'cat /dev/zero | tr "\000" x' || exit 1
|
|
$TMUX setw -g automatic-rename off || exit 1
|
|
|
|
# Attach a control client: output to the fifo, stdin held open by sleep so it
|
|
# stays attached while we drive the session from outside. Open the read end
|
|
# after the client has opened the write end.
|
|
( sleep 30 ) | $TMUX -f/dev/null -C attach -t rt >"$FIFO" 2>&1 &
|
|
exec 8<"$FIFO"
|
|
|
|
n=0
|
|
while [ $n -lt 50 ]; do
|
|
$TMUX lsc -F '#{client_name}' 2>/dev/null | grep -q . && break
|
|
sleep 0.1
|
|
n=$((n + 1))
|
|
done
|
|
$TMUX lsc -F '#{client_name}' 2>/dev/null | grep -q . ||
|
|
{ echo "control client did not attach"; exit 1; }
|
|
|
|
# Drain the control stream slowly (~2.5KB/s). The pane floods far faster, so a
|
|
# backlog stays stuck in the server and the fifo stays full, which is what
|
|
# keeps the exit handshake from completing below.
|
|
( while :; do
|
|
dd bs=256 count=1 <&8 >>"$OUT" 2>/dev/null || exit 0
|
|
sleep 0.1
|
|
done ) &
|
|
READER=$!
|
|
|
|
# Wait until the client is demonstrably backed up: the output only keeps
|
|
# growing at the slow-drain rate while the flood is outrunning the reader.
|
|
n=0
|
|
while [ $n -lt 100 ]; do
|
|
[ "$(wc -c <"$OUT")" -ge 8000 ] && break
|
|
sleep 0.1
|
|
n=$((n + 1))
|
|
done
|
|
[ "$(wc -c <"$OUT")" -ge 8000 ] || { echo "client output did not back up"; exit 1; }
|
|
|
|
# Detach the control client: this sets CLIENT_EXIT immediately. The stuck
|
|
# backlog holds the window between CLIENT_EXIT and the exit handshake open.
|
|
$TMUX detach-client -s rt
|
|
|
|
# While the client is exiting, rename the window to the marker. Vanilla queues
|
|
# these %window-renamed notifications for the exiting client; the fix drops them.
|
|
i=0
|
|
while [ $i -lt 5 ]; do
|
|
$TMUX rename-window -t rt:0 "$MARKER$i"
|
|
i=$((i + 1))
|
|
done
|
|
sleep 0.5
|
|
|
|
# Drain fast (bounded) so the backlog, and any notification queued behind it,
|
|
# reaches OUT. A watchdog keeps this from blocking.
|
|
kill "$READER" 2>/dev/null
|
|
READER=
|
|
( dd bs=4096 count=128 <&8 >>"$OUT" 2>/dev/null ) &
|
|
DRAIN=$!
|
|
( sleep 3; kill "$DRAIN" 2>/dev/null ) &
|
|
WATCHDOG=$!
|
|
wait "$DRAIN" 2>/dev/null
|
|
kill "$WATCHDOG" 2>/dev/null
|
|
|
|
# The marker must never appear as a notification to the exiting client.
|
|
if grep -q "%window-renamed .*$MARKER" "$OUT"; then
|
|
echo "notification queued to exiting control client:"
|
|
grep "%window-renamed .*$MARKER" "$OUT"
|
|
exit 1
|
|
fi
|
|
|
|
exit 0
|