mirror of
https://github.com/fish-shell/fish-shell
synced 2025-01-04 00:58:46 +00:00
ed51e2baac
When fish starts, it notices which pgroup owns the tty, and then it restores that pgroup's tty ownership when it exits. However if fish does not own the tty, then (on Mac at least) the tcsetpgrp call triggers a SIGSTOP and fish will hang while trying to exit. The first change is to ignore SIGTTOU instead of defaulting it. This prevents the hang; however it risks re-introducing #7060. The second change somewhat mitigates the risk of the first: only do the restore if the initial pgroup is different than fish's pgroup. This prevents some useless calls which might potentially steal the tty from another process (e.g. in #7060).
82 lines
2.4 KiB
Python
82 lines
2.4 KiB
Python
#!/usr/bin/env python3
|
|
from pexpect_helper import SpawnedProc
|
|
import subprocess
|
|
import sys
|
|
import signal
|
|
import time
|
|
import os
|
|
|
|
sp = SpawnedProc()
|
|
send, sendline, sleep, expect_prompt, expect_re = (
|
|
sp.send,
|
|
sp.sendline,
|
|
sp.sleep,
|
|
sp.expect_prompt,
|
|
sp.expect_re,
|
|
)
|
|
|
|
# Helper to print an error and exit.
|
|
def error_and_exit(text):
|
|
keys = sp.colors()
|
|
print("{RED}Test failed: {NORMAL}{TEXT}".format(TEXT=text, **keys))
|
|
sys.exit(1)
|
|
|
|
|
|
fish_pid = sp.spawn.pid
|
|
|
|
# Launch fish_test_helper.
|
|
expect_prompt()
|
|
exe_path = os.environ.get("fish_test_helper")
|
|
sp.sendline(exe_path + " nohup_wait")
|
|
|
|
# We expect it to transfer tty ownership to fish_test_helper.
|
|
sleep(0.1)
|
|
tty_owner = os.tcgetpgrp(sp.spawn.child_fd)
|
|
if fish_pid == tty_owner:
|
|
os.kill(fish_pid, signal.SIGKILL)
|
|
error_and_exit(
|
|
"Test failed: outer fish %d did not transfer tty owner to fish_test_helper"
|
|
% (fish_pid)
|
|
)
|
|
|
|
|
|
# Now we are going to tell fish to exit.
|
|
# It must not hang. But it might hang when trying to restore the tty.
|
|
os.kill(fish_pid, signal.SIGTERM)
|
|
|
|
# Loop a bit until the process exits (correct) or stops (incorrrect).
|
|
# When it exits it should be due to the SIGTERM that we sent it.
|
|
for i in range(10):
|
|
pid, status = os.waitpid(fish_pid, os.WUNTRACED | os.WNOHANG)
|
|
if pid == 0:
|
|
# No process ready yet, loop again.
|
|
sleep(0.1)
|
|
elif pid != fish_pid:
|
|
# Some other process exited (??)
|
|
os.kill(fish_pid, signal.SIGKILL)
|
|
error_and_exit(
|
|
"unexpected pid returned from waitpid. Got %d, expected %d"
|
|
% (pid, fish_pid)
|
|
)
|
|
elif os.WIFSTOPPED(status):
|
|
# Our pid stopped instead of exiting.
|
|
# Probably it stopped because of its tcsetpgrp call during exit.
|
|
# Kill it and report a failure.
|
|
os.kill(fish_pid, signal.SIGKILL)
|
|
error_and_exit("pid %d stopped instead of exiting on SIGTERM" % pid)
|
|
elif not os.WIFSIGNALED(status):
|
|
error_and_exit("pid %d did not signal-exit" % pid)
|
|
elif os.WTERMSIG(status) != signal.SIGTERM:
|
|
error_and_exit(
|
|
"pid %d signal-exited with %d instead of %d (SIGTERM)"
|
|
% (os.WTERMSIG(status), signal.SIGTERM)
|
|
)
|
|
else:
|
|
# Success!
|
|
sys.exit(0)
|
|
else:
|
|
# Our loop completed without the process being returned.
|
|
error_and_exit("fish with pid %d hung after SIGTERM" % fish_pid)
|
|
|
|
# Should never get here.
|
|
error_and_exit("unknown, should be unreachable")
|