Add handling for POLLERR, POLLNVAL, and POLLHUP. Hopefully this solves hung poll...
authorZac Medico <zmedico@gentoo.org>
Thu, 25 Dec 2008 01:22:12 +0000 (01:22 -0000)
committerZac Medico <zmedico@gentoo.org>
Thu, 25 Dec 2008 01:22:12 +0000 (01:22 -0000)
with defunct ebuild.sh processes, reported on solaris systems by Fabian Groffen
<grobian@g.o>. (trunk r12287)

svn path=/main/branches/2.1.6/; revision=12317

pym/_emerge/__init__.py

index c82bd2062c868f34bc6867b9d46ef87c022373f1..e2cc10626bae5a7b2f25079f39c2d964c99e23d3 100644 (file)
@@ -1620,6 +1620,20 @@ class EbuildFetchonly(SlotObject):
                portage.elog.elog_process(self.pkg.cpv, self.settings)
                return retval
 
+class PollConstants(object):
+
+       """
+       Provides POLL* constants that are equivalent to those from the
+       select module, for use by PollSelectAdapter.
+       """
+
+       names = ("POLLIN", "POLLPRI", "POLLOUT", "POLLERR", "POLLHUP", "POLLNVAL")
+       v = 1
+       for k in names:
+               locals()[k] = getattr(select, k, v)
+               v *= 2
+       del k, v
+
 class AsynchronousTask(SlotObject):
        """
        Subclasses override _wait() and _poll() so that calls
@@ -1731,7 +1745,26 @@ class AsynchronousTask(SlotObject):
                        while self._exit_listener_stack:
                                self._exit_listener_stack.pop()(self)
 
-class PipeReader(AsynchronousTask):
+class PollTask(AsynchronousTask):
+
+       __slots__ = ("scheduler",) + \
+               ("_registered",)
+
+       _bufsize = 4096
+       _exceptional_events = PollConstants.POLLERR | PollConstants.POLLNVAL
+       _registered_events = PollConstants.POLLIN | PollConstants.POLLHUP | \
+               _exceptional_events
+
+       def _unregister_if_appropriate(self, event):
+               if self._registered:
+                       if event & self._exceptional_events:
+                               self._unregister()
+                               self.cancel()
+                       elif event & PollConstants.POLLHUP:
+                               self._unregister()
+                               self.wait()
+
+class PipeReader(PollTask):
 
        """
        Reads output from one or more files and saves it in memory,
@@ -1740,10 +1773,8 @@ class PipeReader(AsynchronousTask):
        current process.
        """
 
-       __slots__ = ("input_files", "scheduler",) + \
-               ("pid", "_read_data", "_registered", "_reg_ids")
-
-       _bufsize = 4096
+       __slots__ = ("input_files",) + \
+               ("_read_data", "_reg_ids")
 
        def _start(self):
                self._reg_ids = set()
@@ -1752,12 +1783,18 @@ class PipeReader(AsynchronousTask):
                        fcntl.fcntl(f.fileno(), fcntl.F_SETFL,
                                fcntl.fcntl(f.fileno(), fcntl.F_GETFL) | os.O_NONBLOCK)
                        self._reg_ids.add(self.scheduler.register(f.fileno(),
-                               PollConstants.POLLIN, self._output_handler))
+                               self._registered_events, self._output_handler))
                self._registered = True
 
        def isAlive(self):
                return self._registered
 
+       def cancel(self):
+               if self.returncode is None:
+                       self.returncode = 1
+                       self.cancelled = True
+               self.wait()
+
        def _wait(self):
                if self.returncode is not None:
                        return self.returncode
@@ -1784,10 +1821,11 @@ class PipeReader(AsynchronousTask):
                                break
 
                buf = array.array('B')
-               try:
-                       buf.fromfile(f, self._bufsize)
-               except EOFError:
-                       pass
+               if event & PollConstants.POLLIN:
+                       try:
+                               buf.fromfile(f, self._bufsize)
+                       except EOFError:
+                               pass
 
                if buf:
                        self._read_data.append(buf.tostring())
@@ -1795,6 +1833,7 @@ class PipeReader(AsynchronousTask):
                        self._unregister()
                        self.wait()
 
+               self._unregister_if_appropriate(event)
                return self._registered
 
        def _unregister(self):
@@ -1963,9 +2002,10 @@ class TaskSequence(CompositeTask):
                        self._final_exit(task)
                        self.wait()
 
-class SubProcess(AsynchronousTask):
+class SubProcess(PollTask):
 
-       __slots__ = ("scheduler",) + ("pid", "_files", "_registered", "_reg_id")
+       __slots__ = ("pid",) + \
+               ("_files", "_reg_id")
 
        # A file descriptor is required for the scheduler to monitor changes from
        # inside a poll() loop. When logging is not enabled, create a pipe just to
@@ -2079,7 +2119,6 @@ class SpawnProcess(SubProcess):
 
        _file_names = ("log", "process", "stdout")
        _files_dict = slot_dict_class(_file_names, prefix="")
-       _bufsize = 4096
 
        def _start(self):
 
@@ -2157,7 +2196,7 @@ class SpawnProcess(SubProcess):
                kwargs.pop("logfile", None)
 
                self._reg_id = self.scheduler.register(files.process.fileno(),
-                       PollConstants.POLLIN, output_handler)
+                       self._registered_events, output_handler)
                self._registered = True
 
                retval = self._spawn(self.args, **kwargs)
@@ -2189,10 +2228,11 @@ class SpawnProcess(SubProcess):
        def _output_handler(self, fd, event):
                files = self._files
                buf = array.array('B')
-               try:
-                       buf.fromfile(files.process, self._bufsize)
-               except EOFError:
-                       pass
+               if event & PollConstants.POLLIN:
+                       try:
+                               buf.fromfile(files.process, self._bufsize)
+                       except EOFError:
+                               pass
                if buf:
                        if not self.background:
                                buf.tofile(files.stdout)
@@ -2202,6 +2242,8 @@ class SpawnProcess(SubProcess):
                else:
                        self._unregister()
                        self.wait()
+
+               self._unregister_if_appropriate(event)
                return self._registered
 
        def _dummy_handler(self, fd, event):
@@ -2212,15 +2254,18 @@ class SpawnProcess(SubProcess):
                """
                files = self._files
                buf = array.array('B')
-               try:
-                       buf.fromfile(files.process, self._bufsize)
-               except EOFError:
-                       pass
+               if event & PollConstants.POLLIN:
+                       try:
+                               buf.fromfile(files.process, self._bufsize)
+                       except EOFError:
+                               pass
                if buf:
                        pass
                else:
                        self._unregister()
                        self.wait()
+
+               self._unregister_if_appropriate(event)
                return self._registered
 
 class MiscFunctionsProcess(SpawnProcess):
@@ -2783,7 +2828,6 @@ class EbuildMetadataPhase(SubProcess):
 
        _file_names = ("ebuild",)
        _files_dict = slot_dict_class(_file_names, prefix="")
-       _bufsize = SpawnProcess._bufsize
        _metadata_fd = 9
 
        def _start(self):
@@ -2823,7 +2867,7 @@ class EbuildMetadataPhase(SubProcess):
                self._raw_metadata = []
                files.ebuild = os.fdopen(master_fd, 'r')
                self._reg_id = self.scheduler.register(files.ebuild.fileno(),
-                       PollConstants.POLLIN, self._output_handler)
+                       self._registered_events, self._output_handler)
                self._registered = True
 
                retval = portage.doebuild(ebuild_path, "depend",
@@ -2845,8 +2889,9 @@ class EbuildMetadataPhase(SubProcess):
 
        def _output_handler(self, fd, event):
                files = self._files
-               self._raw_metadata.append(files.ebuild.read())
-               if not self._raw_metadata[-1]:
+               if event & PollConstants.POLLIN:
+                       self._raw_metadata.append(files.ebuild.read())
+               if not self._raw_metadata[-1] or event & PollConstants.POLLHUP:
                        # Split lines here so they can be counted inside _set_returncode().
                        self._raw_metadata = "".join(self._raw_metadata).splitlines()
                        self._unregister()
@@ -2857,6 +2902,7 @@ class EbuildMetadataPhase(SubProcess):
                                self.metadata_callback(self.cpv, self.ebuild_path,
                                        self.repo_path, metadata, self.ebuild_mtime)
 
+               self._unregister_if_appropriate(event)
                return self._registered
 
        def _set_returncode(self, wait_retval):
@@ -8576,20 +8622,6 @@ class PackageCounters(object):
                                        (self.blocks - self.blocks_satisfied))
                return "".join(myoutput)
 
-class PollConstants(object):
-
-       """
-       Provides POLL* constants that are equivalent to those from the
-       select module, for use by PollSelectAdapter.
-       """
-
-       names = ("POLLIN", "POLLPRI", "POLLOUT", "POLLERR", "POLLHUP", "POLLNVAL")
-       v = 1
-       for k in names:
-               locals()[k] = getattr(select, k, v)
-               v *= 2
-       del k, v
-
 class PollSelectAdapter(PollConstants):
 
        """