summaryrefslogtreecommitdiff
path: root/scripts/parallel_emerge.py
diff options
context:
space:
mode:
authorDavid James <davidjames@google.com>2014-04-24 09:40:05 -0700
committerchrome-internal-fetch <chrome-internal-fetch@google.com>2014-04-26 02:46:03 +0000
commitaaf49e45c81fb641eac0d73851dc996877f1158e (patch)
treee3a3baf498156fb1e04afb7936a1e3a55b7e4d04 /scripts/parallel_emerge.py
parent1c986f33b7302e1148ee14ef772746174fd6cec6 (diff)
downloadchromite-aaf49e45c81fb641eac0d73851dc996877f1158e.tar.gz
Re-land "Fix CTRL-C and CTRL-Z to work again with parallel_emerge."
This re-lands CL:196787 which was proven to be innocent and not the cause of chromium:366988. In CL:1185, we set up parallel_emerge to keep track of children using session groups. This is important and is needed to prevent hangs, in cases where old emerge processes are sticking around. That said, when parallel_emerge calls os.setsid(), it doesn't receive CTRL-C signals anymore, so that means that CTRL-C doesn't work. Move this call to a child process, and set up a parent process to watch for CTRL-C instead, so CTRL-C can work again. BUG=chromium:366480, chromium:366988 TEST=Combination of CTRL-C and CTRL-Z on parallel_emerge runs. TEST=Successful parallel_emerge run. TEST=8 sandybridge-release-group trybot runs Change-Id: Icfda2fdf1f377eb5177b3f3ab7651b9a217bbf9d Original-Change-Id: Ie84bb1addd41122360a2cd6ed655603877e9890a Originally-Reviewed-on: https://chromium-review.googlesource.com/196787 Reviewed-on: https://chromium-review.googlesource.com/197131 Tested-by: David James <davidjames@chromium.org> Reviewed-by: Mike Frysinger <vapier@chromium.org> Commit-Queue: David James <davidjames@chromium.org>
Diffstat (limited to 'scripts/parallel_emerge.py')
-rw-r--r--scripts/parallel_emerge.py70
1 files changed, 68 insertions, 2 deletions
diff --git a/scripts/parallel_emerge.py b/scripts/parallel_emerge.py
index 3fa2ee153..94b9881a4 100644
--- a/scripts/parallel_emerge.py
+++ b/scripts/parallel_emerge.py
@@ -36,6 +36,7 @@ import time
import traceback
from chromite.lib import cros_build_lib
+from chromite.lib import osutils
# If PORTAGE_USERNAME isn't specified, scrape it from the $HOME variable. On
# Chromium OS, the default "portage" user doesn't have the necessary
@@ -1272,8 +1273,8 @@ class EmergeQueue(object):
print "Skipping merge because of --pretend mode."
sys.exit(0)
- # Set a process group so we can easily terminate all children.
- os.setsid()
+ # Set up a session so we can easily terminate all children.
+ self._SetupSession()
# Setup scheduler graph object. This is used by the child processes
# to help schedule jobs.
@@ -1325,6 +1326,71 @@ class EmergeQueue(object):
(pkg, TargetState(pkg, data)) for pkg, data in deps_map.iteritems())
self._fetch_ready.multi_put(self._state_map.itervalues())
+ def _SetupSession(self):
+ """Set up a session so we can easily terminate all children."""
+ # When we call os.setsid(), this sets up a session / process group for this
+ # process and all children. These session groups are needed so that we can
+ # easily kill all children (including processes launched by emerge) before
+ # we exit.
+ #
+ # One unfortunate side effect of os.setsid() is that it blocks CTRL-C from
+ # being received. To work around this, we only call os.setsid() in a forked
+ # process, so that the parent can still watch for CTRL-C. The parent will
+ # just sit around, watching for signals and propagating them to the child,
+ # until the child exits.
+ #
+ # TODO(davidjames): It would be nice if we could replace this with cgroups.
+ pid = os.fork()
+ if pid == 0:
+ os.setsid()
+ else:
+ def PropagateToChildren(signum, _frame):
+ # Just propagate the signals down to the child. We'll exit when the
+ # child does.
+ try:
+ os.kill(pid, signum)
+ except OSError as ex:
+ if ex.errno != errno.ESRCH:
+ raise
+ signal.signal(signal.SIGINT, PropagateToChildren)
+ signal.signal(signal.SIGTERM, PropagateToChildren)
+
+ def StopGroup(_signum, _frame):
+ # When we get stopped, stop the children.
+ try:
+ os.killpg(pid, signal.SIGSTOP)
+ os.kill(0, signal.SIGSTOP)
+ except OSError as ex:
+ if ex.errno != errno.ESRCH:
+ raise
+ signal.signal(signal.SIGTSTP, StopGroup)
+
+ def ContinueGroup(_signum, _frame):
+ # Launch the children again after being stopped.
+ try:
+ os.killpg(pid, signal.SIGCONT)
+ except OSError as ex:
+ if ex.errno != errno.ESRCH:
+ raise
+ signal.signal(signal.SIGCONT, ContinueGroup)
+
+ # Loop until the children exit. We exit with os._exit to be sure we
+ # don't run any finalizers (those will be run by the child process.)
+ # pylint: disable=W0212
+ while True:
+ try:
+ # Wait for the process to exit. When it does, exit with the return
+ # value of the subprocess.
+ os._exit(osutils.GetExitStatus(os.waitpid(pid, 0)[1]))
+ except OSError as ex:
+ if ex.errno == errno.EINTR:
+ continue
+ traceback.print_exc()
+ os._exit(1)
+ except BaseException:
+ traceback.print_exc()
+ os._exit(1)
+
def _SetupExitHandler(self):
def ExitHandler(signum, _frame):