suspending external command with [ctrl-z] does not yield a shell prompt
Closed this issue · 7 comments
Using elvish built from HEAD run vim
then press [ctrl-z]. The vim process is apparently suspended but the elvish process doesn't provide a new prompt. Typing is echoed but commands aren't executed. Sending SIGUSR1 results in the following stack dumps. I can reproduce on macOS and Linux.
goroutine 5 [running]:
github.com/elves/elvish/pkg/sys.DumpStack(0xc0001922d0, 0x15d49c8)
/Users/krader/projects/3rd-party/elvish/pkg/sys/dumpstack.go:10 +0x9d
github.com/elves/elvish/pkg/program/shell.handleSignal(0x1672de0, 0xc000026660, 0xc0000a2010)
/Users/krader/projects/3rd-party/elvish/pkg/program/shell/signal_unix.go:19 +0x6b
github.com/elves/elvish/pkg/program/shell.setupShell.func1(0xc0001b6120, 0xc0000a2000, 0xc0000a2008, 0xc0000a2010)
/Users/krader/projects/3rd-party/elvish/pkg/program/shell/shell.go:28 +0xb0
created by github.com/elves/elvish/pkg/program/shell.setupShell
/Users/krader/projects/3rd-party/elvish/pkg/program/shell/shell.go:25 +0x12d
goroutine 1 [semacquire]:
sync.runtime_Semacquire(0xc000196c88)
/usr/local/Cellar/go/1.14.1/libexec/src/runtime/sema.go:56 +0x42
sync.(*WaitGroup).Wait(0xc000196c80)
/usr/local/Cellar/go/1.14.1/libexec/src/sync/waitgroup.go:130 +0x64
github.com/elves/elvish/pkg/eval.(*pipelineOp).invoke(0xc000697220, 0xc000697270, 0xc0007af5e8, 0x100e856)
/Users/krader/projects/3rd-party/elvish/pkg/eval/compile_effect.go:173 +0x4c9
github.com/elves/elvish/pkg/eval.effectOp.exec(0x166ba00, 0xc000697220, 0x0, 0x3, 0xc000697270, 0x0, 0xc000200050)
/Users/krader/projects/3rd-party/elvish/pkg/eval/op.go:32 +0x69
github.com/elves/elvish/pkg/eval.chunkOp.invoke(0xc000b101a0, 0x1, 0x1, 0xc000697270, 0xc000b102c0, 0xc0007af6b0)
/Users/krader/projects/3rd-party/elvish/pkg/eval/compile_effect.go:29 +0x7e
github.com/elves/elvish/pkg/eval.effectOp.exec(0x166c9a0, 0xc000b10200, 0x0, 0x3, 0xc000697270, 0xc000b10240, 0xc000b10260)
/Users/krader/projects/3rd-party/elvish/pkg/eval/op.go:32 +0x69
github.com/elves/elvish/pkg/eval.(*Evaler).Eval(0xc0001ec000, 0x166c9a0, 0xc000b10200, 0x0, 0x3, 0xc000316060, 0xc000b10220, 0x3, 0x3, 0x15d3748, ...)
/Users/krader/projects/3rd-party/elvish/pkg/eval/eval.go:281 +0x1e1
github.com/elves/elvish/pkg/program/shell.evalInTTY(0xc0001ec000, 0x166c9a0, 0xc000b10200, 0x0, 0x3, 0xc000316060, 0xc0000a2000, 0xc0000a2008, 0xc0000a2010, 0x0, ...)
/Users/krader/projects/3rd-party/elvish/pkg/program/shell/shell.go:43 +0x1b4
github.com/elves/elvish/pkg/program/shell.Interact(0xc0000a2000, 0xc0000a2008, 0xc0000a2010, 0xc00011bd88)
/Users/krader/projects/3rd-party/elvish/pkg/program/shell/interact.go:85 +0x965
github.com/elves/elvish/pkg/program.(*shellProgram).Main(0xc00018c840, 0xc0000a2000, 0xc0000a2008, 0xc0000a2010, 0xc000098190, 0x0, 0x0, 0xc00011bf18)
/Users/krader/projects/3rd-party/elvish/pkg/program/shell.go:31 +0x27c
github.com/elves/elvish/pkg/program.Main(0xc0000a2000, 0xc0000a2008, 0xc0000a2010, 0xc000098190, 0x1, 0x1, 0x0)
/Users/krader/projects/3rd-party/elvish/pkg/program/program.go:103 +0x1c8
main.main()
/Users/krader/projects/3rd-party/elvish/main.go:14 +0x69
goroutine 34 [chan receive]:
github.com/elves/elvish/pkg/eval.getBlackholeChan.func1(0xc0001b6060)
/Users/krader/projects/3rd-party/elvish/pkg/eval/port.go:59 +0x47
created by github.com/elves/elvish/pkg/eval.getBlackholeChan
/Users/krader/projects/3rd-party/elvish/pkg/eval/port.go:58 +0x58
goroutine 35 [syscall]:
os/signal.signal_recv(0x1672de0)
/usr/local/Cellar/go/1.14.1/libexec/src/runtime/sigqueue.go:144 +0x96
os/signal.loop()
/usr/local/Cellar/go/1.14.1/libexec/src/os/signal/signal_unix.go:23 +0x22
created by os/signal.Notify.func1
/usr/local/Cellar/go/1.14.1/libexec/src/os/signal/signal.go:127 +0x44
goroutine 6 [chan receive]:
github.com/elves/elvish/pkg/cli/prompt.(*Prompt).loop(0xc0002841c0)
/Users/krader/projects/3rd-party/elvish/pkg/cli/prompt/prompt.go:77 +0xc8
created by github.com/elves/elvish/pkg/cli/prompt.New
/Users/krader/projects/3rd-party/elvish/pkg/cli/prompt/prompt.go:70 +0x15f
goroutine 7 [chan receive]:
github.com/elves/elvish/pkg/cli/prompt.(*Prompt).loop(0xc0002842a0)
/Users/krader/projects/3rd-party/elvish/pkg/cli/prompt/prompt.go:77 +0xc8
created by github.com/elves/elvish/pkg/cli/prompt.New
/Users/krader/projects/3rd-party/elvish/pkg/cli/prompt/prompt.go:70 +0x15f
goroutine 15127 [chan receive]:
github.com/elves/elvish/pkg/eval.relayChanToFile(0xc0000a4e40, 0xc0000a2008, 0xc0005aa4c0, 0xc, 0xc000196bd4)
/Users/krader/projects/3rd-party/elvish/pkg/eval/port_helper.go:47 +0x1ae
created by github.com/elves/elvish/pkg/eval.portsFromFiles
/Users/krader/projects/3rd-party/elvish/pkg/eval/port_helper.go:31 +0xfd
goroutine 15129 [select]:
github.com/elves/elvish/pkg/eval.ListenInterrupts.func1(0xc00008e0c0, 0xc00008e180, 0xc00008e1e0, 0xc00008e240)
/Users/krader/projects/3rd-party/elvish/pkg/eval/interrupts.go:46 +0xad
created by github.com/elves/elvish/pkg/eval.ListenInterrupts
/Users/krader/projects/3rd-party/elvish/pkg/eval/interrupts.go:42 +0x143
goroutine 13879 [syscall]:
syscall.syscall6(0x107dbd0, 0xf99a, 0xc00025fa9c, 0x0, 0xc0008e8090, 0x0, 0x0, 0x0, 0x0, 0x0)
/usr/local/Cellar/go/1.14.1/libexec/src/runtime/sys_darwin.go:74 +0x2e
syscall.wait4(0xf99a, 0xc00025fa9c, 0x0, 0xc0008e8090, 0x90, 0x1593fa0, 0x1)
/usr/local/Cellar/go/1.14.1/libexec/src/syscall/zsyscall_darwin_amd64.go:44 +0x87
syscall.Wait4(0xf99a, 0xc00025faec, 0x0, 0xc0008e8090, 0x37, 0xc0008db560, 0x6)
/usr/local/Cellar/go/1.14.1/libexec/src/syscall/syscall_bsd.go:129 +0x51
os.(*Process).wait(0xc00018e330, 0x37, 0xc0008db560, 0x6)
/usr/local/Cellar/go/1.14.1/libexec/src/os/exec_unix.go:38 +0x7b
os.(*Process).Wait(...)
/usr/local/Cellar/go/1.14.1/libexec/src/os/exec.go:125
github.com/elves/elvish/pkg/eval.ExternalCmd.Call(0xc000230b80, 0x37, 0xc0008f4050, 0xc000281d80, 0x5, 0x8, 0xc0003df440, 0xc0002a5800, 0xc0004840a0)
/Users/krader/projects/3rd-party/elvish/pkg/eval/external_cmd.go:91 +0x37e
github.com/elves/elvish/pkg/eval.(*formOp).invoke(0xc0002fb5f0, 0xc0008f4050, 0x0, 0x0)
/Users/krader/projects/3rd-party/elvish/pkg/eval/compile_effect.go:380 +0x8be
github.com/elves/elvish/pkg/eval.effectOp.exec(0x166b960, 0xc0002fb5f0, 0xe93, 0x1003, 0xc0008f4050, 0xffffffffffffffff, 0xffffffffffffffff)
/Users/krader/projects/3rd-party/elvish/pkg/eval/op.go:32 +0x69
github.com/elves/elvish/pkg/eval.(*pipelineOp).invoke.func1(0x166b960, 0xc0002fb5f0, 0xe93, 0x1003, 0xc0008f4050, 0xc000010de0, 0xc0009df700, 0x0)
/Users/krader/projects/3rd-party/elvish/pkg/eval/compile_effect.go:136 +0x60
created by github.com/elves/elvish/pkg/eval.(*pipelineOp).invoke
/Users/krader/projects/3rd-party/elvish/pkg/eval/compile_effect.go:135 +0x424
goroutine 15128 [chan receive]:
github.com/elves/elvish/pkg/eval.relayChanToFile(0xc000169740, 0xc0000a2010, 0xc0005aa4c0, 0xc, 0xc000196bd4)
/Users/krader/projects/3rd-party/elvish/pkg/eval/port_helper.go:47 +0x1ae
created by github.com/elves/elvish/pkg/eval.portsFromFiles
/Users/krader/projects/3rd-party/elvish/pkg/eval/port_helper.go:32 +0x150
goroutine 15130 [syscall]:
syscall.syscall6(0x107dbd0, 0xf99e, 0xc00005fa9c, 0x0, 0xc0008e8000, 0x0, 0x0, 0x0, 0x0, 0x0)
/usr/local/Cellar/go/1.14.1/libexec/src/runtime/sys_darwin.go:74 +0x2e
syscall.wait4(0xf99e, 0xc00005fa9c, 0x0, 0xc0008e8000, 0x90, 0x1593fa0, 0x1)
/usr/local/Cellar/go/1.14.1/libexec/src/syscall/zsyscall_darwin_amd64.go:44 +0x87
syscall.Wait4(0xf99e, 0xc00005faec, 0x0, 0xc0008e8000, 0x12, 0xc0003ce0d0, 0x1)
/usr/local/Cellar/go/1.14.1/libexec/src/syscall/syscall_bsd.go:129 +0x51
os.(*Process).wait(0xc0006f6120, 0x12, 0xc0003ce0d0, 0x1)
/usr/local/Cellar/go/1.14.1/libexec/src/os/exec_unix.go:38 +0x7b
os.(*Process).Wait(...)
/usr/local/Cellar/go/1.14.1/libexec/src/os/exec.go:125
github.com/elves/elvish/pkg/eval.ExternalCmd.Call(0xc00060e0d8, 0x3, 0xc0006972c0, 0x0, 0x0, 0x0, 0xc000316180, 0xc00060e0d8, 0x15a6d71)
/Users/krader/projects/3rd-party/elvish/pkg/eval/external_cmd.go:91 +0x37e
github.com/elves/elvish/pkg/eval.(*formOp).invoke(0xc0007bb040, 0xc0006972c0, 0x0, 0x0)
/Users/krader/projects/3rd-party/elvish/pkg/eval/compile_effect.go:380 +0x8be
github.com/elves/elvish/pkg/eval.effectOp.exec(0x166b960, 0xc0007bb040, 0x0, 0x3, 0xc0006972c0, 0x14d9040, 0xc0008dd4d8)
/Users/krader/projects/3rd-party/elvish/pkg/eval/op.go:32 +0x69
github.com/elves/elvish/pkg/eval.(*pipelineOp).invoke.func1(0x166b960, 0xc0007bb040, 0x0, 0x3, 0xc0006972c0, 0xc0004dc030, 0xc000196c80, 0xc00060e000)
/Users/krader/projects/3rd-party/elvish/pkg/eval/compile_effect.go:136 +0x60
created by github.com/elves/elvish/pkg/eval.(*pipelineOp).invoke
/Users/krader/projects/3rd-party/elvish/pkg/eval/compile_effect.go:135 +0x424
goroutine 13880 [semacquire]:
sync.runtime_Semacquire(0xc0009df708)
/usr/local/Cellar/go/1.14.1/libexec/src/runtime/sema.go:56 +0x42
sync.(*WaitGroup).Wait(0xc0009df700)
/usr/local/Cellar/go/1.14.1/libexec/src/sync/waitgroup.go:130 +0x64
github.com/elves/elvish/pkg/eval.(*pipelineOp).invoke.func2(0xc0009df700, 0xc0008f4000, 0xc0002df630, 0xc000010de0, 0x1, 0x1)
/Users/krader/projects/3rd-party/elvish/pkg/eval/compile_effect.go:156 +0x40
created by github.com/elves/elvish/pkg/eval.(*pipelineOp).invoke
/Users/krader/projects/3rd-party/elvish/pkg/eval/compile_effect.go:155 +0x49d
Also, running commands like sleep 99
which do not handle SIGTSTP result in ^Z
being echoed to the terminal and no other action being taken. The command can still be killed with [ctrl-c] and a usable elvish prompt appears.
Since elvish does not support job control it should set SIGTTIN
, SIGTTOU
and SIGTSTP
to be ignored by all child processes.See "APUE", third edition, page 379.
I can't find any evidence it does so. In fact, looking at /proc/self/status we see that no signals are ignored: SigIgn: 0000000000000000
. And it's catching every signal that can be caught: SigCgt: fffffffffffbfeff
. This state of affairs is presumably the default behavior of the Go runtime. Looking at the signal disposition of a child process, such as sleep 99
, shows no signals ignored.
Argh! Even if elvish marks the three tty job control signals to be ignored vim unignores SIGTSTP, but leaves SIGTTIN and SIGTTOU ignored. That is wrong. I've opened issue vim/vim#5990.
The other problem is that Go's Process.Wait(), by design, doesn't return when the process is stopped by SIGSTOP
or SIGTSTP
. See golang/go#3138 and golang/go#3111. So there isn't any way to make this work without implementing fairly comprehensive support for job control. Which means using lower level APIs such as https://golang.org/pkg/syscall/#Wait4 or https://pkg.go.dev/golang.org/x/sys/unix?tab=doc#Wait4. Which will complicate working on MS Windows and other non-UNIX platforms.
The best we can do for now is ignore those three signals when launching an external command and hope the process doesn't subvert our intentions by handling any of them.
Hmm, this also seems to be related to how Elvish is started. I cannot reproduce this when my terminal emulator starts Elvish directly, but I can reproduce this if I launch Elvish from bash.
It would be nice if vim respects the ignored status of SIGTSTP, but Elvish should also defend itself against any unexpected behavior in external commands.
I cannot reproduce this when my terminal emulator starts Elvish directly, but I can reproduce this if I launch Elvish from bash.
That's due to bash implementing job control and setting up the controlling tty accordingly; which includes the appropriate setsid()
syscalls, etc. Your terminal emulator is unlikely to do any of that since it reasonably assumes the process it launches will take care of it.
Elvish should also defend itself against any unexpected behavior in external commands.
Short of implementing POSIX job control our only option is to modify the termios structure so there is no suspend or delayed suspend character in effect when running an external command.
@#$%"(*)!!! Vim not only ignores whether SIGTSTP is ignored it also ignores the termios suspend key value. It instead uses a hardcoded definition of ctrl-z that always calls the vim suspend function. My pull-request will still attempt to deal with this by nulling the suspend character but I don't expect that to have much value.