EventLoop: implement child_watch_add
authorZac Medico <zmedico@gentoo.org>
Thu, 16 Feb 2012 20:56:50 +0000 (12:56 -0800)
committerZac Medico <zmedico@gentoo.org>
Thu, 16 Feb 2012 20:59:17 +0000 (12:59 -0800)
pym/_emerge/PollScheduler.py
pym/portage/util/_eventloop/EventLoop.py
pym/portage/util/_eventloop/GlibEventLoop.py

index 6e416c300f73dd57e127d5b8fc19f66822835076..be41565ddb7ac34b56cbfb4854bf2a5ef769f92a 100644 (file)
@@ -21,7 +21,8 @@ class PollScheduler(object):
 
        class _sched_iface_class(SlotObject):
                __slots__ = ("IO_ERR", "IO_HUP", "IO_IN", "IO_NVAL", "IO_OUT",
-                       "IO_PRI", "idle_add", "io_add_watch", "iteration",
+                       "IO_PRI", "child_watch_add",
+                       "idle_add", "io_add_watch", "iteration",
                        "output", "register", "run",
                        "source_remove", "timeout_add", "unregister")
 
@@ -41,6 +42,7 @@ class PollScheduler(object):
                        IO_NVAL=self._event_loop.IO_NVAL,
                        IO_OUT=self._event_loop.IO_OUT,
                        IO_PRI=self._event_loop.IO_PRI,
+                       child_watch_add=self._event_loop.child_watch_add,
                        idle_add=self._event_loop.idle_add,
                        io_add_watch=self._event_loop.io_add_watch,
                        iteration=self._event_loop.iteration,
index 24ba14077318c4353942db8ee32a382a8e3d5ca0..994952e198a3cae0ce1430dd30ce27ca4dd609d4 100644 (file)
@@ -2,8 +2,11 @@
 # Distributed under the terms of the GNU General Public License v2
 
 import errno
+import fcntl
 import logging
+import os
 import select
+import signal
 import time
 
 from portage.util import writemsg_level
@@ -16,6 +19,9 @@ class EventLoop(object):
 
        supports_multiprocessing = True
 
+       class _child_callback_class(SlotObject):
+               __slots__ = ("callback", "data", "pid", "source_id")
+
        class _idle_callback_class(SlotObject):
                __slots__ = ("args", "callback", "calling", "source_id")
 
@@ -44,6 +50,11 @@ class EventLoop(object):
                self.IO_OUT = PollConstants.POLLOUT
                self.IO_PRI = PollConstants.POLLPRI
 
+               self._child_handlers = {}
+               self._sigchld_read = None
+               self._sigchld_write = None
+               self._pid = os.getpid()
+
        def _poll(self, timeout=None):
                if self._timeout_interval is None:
                        self._run_timeouts()
@@ -200,6 +211,87 @@ class EventLoop(object):
 
                return bool(events_handled)
 
+       def child_watch_add(self, pid, callback, data=None):
+               """
+               Like glib.child_watch_add(), sets callback to be called with the
+               user data specified by data when the child indicated by pid exits.
+               The signature for the callback is:
+
+                       def callback(pid, condition, user_data)
+
+               where pid is is the child process id, condition is the status
+               information about the child process and user_data is data.
+
+               @type int
+               @param pid: process id of a child process to watch
+               @type callback: callable
+               @param callback: a function to call
+               @type data: object
+               @param data: the optional data to pass to function
+               @rtype: int
+               @return: an integer ID
+               """
+               self._event_handler_id += 1
+               source_id = self._event_handler_id
+               self._child_handlers[source_id] = self._child_callback_class(
+                       callback=callback, data=data, pid=pid, source_id=source_id)
+               if self._sigchld_read is None:
+                       self._sigchld_init()
+               # poll now, in case the SIGCHLD has already arrived
+               self._poll_child_processes()
+               return source_id
+
+       def _sigchld_init(self):
+               signal.signal(signal.SIGCHLD, self._sigchld_sig_cb)
+               self._sigchld_read, self._sigchld_write = os.pipe()
+               fcntl.fcntl(self._sigchld_read, fcntl.F_SETFL,
+                       fcntl.fcntl(self._sigchld_read, fcntl.F_GETFL) | os.O_NONBLOCK)
+               self.io_add_watch(self._sigchld_read, self.IO_IN, self._sigchld_io_cb)
+
+       def _sigchld_sig_cb(self, signum, frame):
+               # If this signal handler was not installed by the
+               # current process then the signal doesn't belong to
+               # this EventLoop instance.
+               if os.getpid() == self._pid:
+                       os.write(self._sigchld_write, b'\0')
+
+       def _sigchld_io_cb(self, fd, events):
+               try:
+                       while True:
+                               os.read(self._sigchld_read, 4096)
+               except OSError:
+                       # read until EAGAIN
+                       pass
+               self._poll_child_processes()
+
+       def _poll_child_processes(self):
+               if not self._child_handlers:
+                       return False
+
+               calls = 0
+
+               for x in list(self._child_handlers.values()):
+                       if x.source_id not in self._child_handlers:
+                               # it's already been called via re-entrance
+                               continue
+                       try:
+                               wait_retval = os.waitpid(x.pid, os.WNOHANG)
+                       except OSError as e:
+                               if e.errno != errno.ECHILD:
+                                       raise
+                               del e
+                               self._child_handlers.pop(x.source_id, None)
+                       else:
+                               # With waitpid and WNOHANG, only check the
+                               # first element of the tuple since the second
+                               # element may vary (bug #337465).
+                               if wait_retval[0] != 0:
+                                       calls += 1
+                                       self._child_handlers.pop(x.source_id, None)
+                                       x.callback(x.pid, wait_retval[1], x.data)
+
+               return bool(calls)
+
        def idle_add(self, callback, *args):
                """
                Like glib.idle_add(), if callback returns False it is
@@ -323,6 +415,9 @@ class EventLoop(object):
                is found and removed, and False if the reg_id is invalid or has
                already been removed.
                """
+               x = self._child_handlers.pop(reg_id, None)
+               if x is not None:
+                       return True
                idle_callback = self._idle_callbacks.pop(reg_id, None)
                if idle_callback is not None:
                        return True
index 0d33175ea3dd36b170d52ac88d369ffbc4f2caac..f2f5c5e64a06da5ba43ac16cf4b422ea368f655b 100644 (file)
@@ -16,6 +16,7 @@ class GlibEventLoop(object):
                self.IO_OUT = glib.IO_OUT
                self.IO_PRI = glib.IO_PRI
                self.iteration = glib.main_context_default().iteration
+               self.child_watch_add = glib.child_watch_add
                self.idle_add = glib.idle_add
                self.io_add_watch = glib.io_add_watch
                self.timeout_add = glib.timeout_add