Permalink
Browse files

Ctrl-C no longer exits OSH, at least under some circumstances.

We were using Python's default SIGINT handler, which sets a flag and
then throws KeyboardInterrupt at the current execution point.

Now we have our own signal handler.  It seems to work, although it's
hard to test automatically.

OSH does behave a little differently than other shells: it doesn't
reprint the prompt after Ctrl-C.  I'm not sure other shells do that.  I
looked at dash and it does have special handling for SIGINT.

Addresses issue #36.
  • Loading branch information...
Andy Chu
Andy Chu committed Sep 1, 2018
1 parent e0cdb4a commit a473594cbed1c65aaa19680f87857e78fc81f1a3
Showing with 61 additions and 21 deletions.
  1. +5 −12 bin/oil.py
  2. +24 −1 core/builtin.py
  3. +32 −8 core/process.py
View
@@ -103,13 +103,9 @@ def InteractiveLoop(opts, ex, c_parser, arena):
status = 0
while True:
# Why is this the way to handle Control-C?
# Also, we shouldn't exit the shell!
try:
w = c_parser.Peek()
except KeyboardInterrupt:
print('Ctrl-C')
continue
# NOTE: We no longer need to catch KeyboardInterrupt here, because we
# handle SIGINT ourselves.
w = c_parser.Peek()
c_id = word.CommandId(w)
if c_id == Id.Op_Newline:
@@ -195,6 +191,8 @@ def OshMain(argv0, argv, login_shell):
_ShowVersion()
return 0
builtin.RegisterSigIntHandler()
trace_state = util.TraceState()
if 'cmd-parse' == opts.trace:
util.WrapMethods(cmd_parse.CommandParser, trace_state)
@@ -208,11 +206,6 @@ def OshMain(argv0, argv, login_shell):
else:
dollar0 = argv[opt_index] # the script name, or the arg after -c
# TODO: Create a --parse action or 'osh parse' or 'oil osh-parse'
# osh-fix
# It uses a different memory-management model. It's a batch program and not
# an interactive program.
pool = alloc.Pool()
arena = pool.NewArena()
View
@@ -1033,6 +1033,26 @@ def UnAlias(argv, aliases):
import signal
def _SigIntHandler(unused, unused_frame):
"""
Either this handler is installed, or the user's handler is installed.
Python's default handler of raising KeyboardInterrupt should never be
installed.
"""
# TODO: It might be nice to write diagnostic messages when invokved with
# 'osh --debug-pipe=/path'.
#
# NOTE: I think dash and POSIX somehow set the exit code to 128 + exit code?
#print('Ctrl-C')
pass
def RegisterSigIntHandler():
#log('Registering')
signal.signal(signal.SIGINT, _SigIntHandler)
class _TrapHandler(object):
"""A function that is called by Python's signal module.
@@ -1143,7 +1163,10 @@ def Trap(argv, traps, nodes_to_run, ex):
pass
# Restore default
signal.signal(sig_val, signal.SIG_DFL)
if sig_val == signal.SIGINT:
RegisterSigIntHandler()
else:
signal.signal(sig_val, signal.SIG_DFL)
return 0
util.error("Can't remove invalid trap %r" % sig_spec)
View
@@ -454,6 +454,20 @@ def ClosePipe(self):
def Start(self):
"""Start this process with fork(), haandling redirects."""
# TODO: If OSH were a job control shell, we might need to call some of
# these here. They control the distribution of signals, some of which
# originate from a terminal. All the processes in a pipeline should be in
# a single process group.
#
# - os.setpgid()
# - os.setpgrp()
# - os.tcsetpgrp()
#
# NOTE: os.setsid() isn't called by the shell; it's should be called by the
# login program that starts the shell.
#
# The whole job control mechanism is complicated and hacky.
pid = os.fork()
if pid < 0:
# When does this happen?
@@ -666,15 +680,25 @@ def Register(self, pid, callback):
def Wait(self):
# This is a list of async jobs
try:
pid, status = os.wait()
except OSError as e:
if e.errno == errno.ECHILD:
#log('WAIT ECHILD')
return False # nothing to wait for caller should stop
while True:
try:
pid, status = os.wait()
except OSError as e:
#log('wait() error: %s', e)
if e.errno == errno.ECHILD:
return False # nothing to wait for caller should stop
elif e.errno == errno.EINTR:
# This happens when we register a handler for SIGINT, and thus never
# get the KeyboardInterrupt exception? Not sure why.
# Try
# $ cat # Now hit Ctrl-C
#log('Continuing')
continue # try again
else:
# An error we don't know about.
raise
else:
# What else can go wrong?
raise
break # no exception thrown, so no need to retry
#log('WAIT got %s %s', pid, status)

0 comments on commit a473594

Please sign in to comment.