Skip to content

Commit

Permalink
allow killing and signalling entire process groups
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrew Moffat committed Oct 23, 2016
1 parent 1e7cdc5 commit 4e8991e
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## 1.2 -

* added `.kill_group()` and `.signal_group()` methods for better process control [#237](https://github.com/amoffat/sh/pull/237)
* added `new_session` special keyword argument for controlling spawned process session [#266](https://github.com/amoffat/sh/issues/266)
* bugfix better handling for EINTR on system calls [#292](https://github.com/amoffat/sh/pull/292)
* bugfix where with-contexts were not threadsafe [#247](https://github.com/amoffat/sh/issues/195)
Expand Down
9 changes: 9 additions & 0 deletions sh.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,8 @@ class RunningCommand(object):
"signal",
"terminate",
"kill",
"kill_group",
"signal_group",
"pid",
"sid",
"pgid",
Expand Down Expand Up @@ -1763,10 +1765,17 @@ def get_sid(self):
self.sid is the session id at launch """
return os.getsid(self.pid)

def signal_group(self, sig):
self.log.debug("sending signal %d to group", sig)
os.killpg(self.get_pgid(), sig)

def signal(self, sig):
self.log.debug("sending signal %d", sig)
os.kill(self.pid, sig)

def kill_group(self):
self.log.debug("killing group")
self.signal_group(signal.SIGKILL)

def kill(self):
self.log.debug("killing")
Expand Down
60 changes: 60 additions & 0 deletions test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1736,6 +1736,66 @@ def throw_terminate_signal():
self.assertRaises(SignalException_15, throw_terminate_signal)


def test_signal_group(self):
child = create_tmp_test("""
import time
time.sleep(3)
""")

parent = create_tmp_test("""
import sh
p = sh.python("{child_file}", _bg=True, _new_session=False)
print(p.pid)
print(p.process.pgid)
p.wait()
""", child_file=child.name)

def launch():
p = python(parent.name, _bg=True, _iter=True)
child_pid = int(next(p).strip())
child_pgid = int(next(p).strip())
parent_pid = p.pid
parent_pgid = p.process.pgid

return p, child_pid, child_pgid, parent_pid, parent_pgid

def assert_alive(pid):
os.kill(pid, 0)

def assert_dead(pid):
self.assert_oserror(errno.ESRCH, os.kill, pid, 0)


# first let's prove that calling regular SIGKILL on the parent does
# nothing to the child, since the child was launched in the same process
# group (_new_session=False) and the parent is not a controlling process
p, child_pid, child_pgid, parent_pid, parent_pgid = launch()

assert_alive(parent_pid)
assert_alive(child_pid)

p.kill()
time.sleep(0.1)
assert_dead(parent_pid)
assert_alive(child_pid)

self.assertRaises(sh.SignalException_SIGKILL, p.wait)
assert_dead(child_pid)


# now let's prove that killing the process group kills both the parent
# and the child
p, child_pid, child_pgid, parent_pid, parent_pgid = launch()

assert_alive(parent_pid)
assert_alive(child_pid)

p.kill_group()
time.sleep(0.1)
assert_dead(parent_pid)
assert_dead(child_pid)


def test_file_output_isnt_buffered(self):
# https://github.com/amoffat/sh/issues/147

Expand Down

0 comments on commit 4e8991e

Please sign in to comment.