ebuild-ipc: use non-blocking read
authorZac Medico <zmedico@gentoo.org>
Thu, 17 Mar 2011 21:12:13 +0000 (14:12 -0700)
committerZac Medico <zmedico@gentoo.org>
Thu, 17 Mar 2011 21:12:13 +0000 (14:12 -0700)
This makes it possible for the daemon to send a reply without blocking,
thereby improving performance and also making it possible for the
daemon to do a non-blocking write without a race condition.

This reverts part of commit 81fc303212b8379219cf5d463c8717359b972dba,
which probably didn't help portability anyway. Now, ebuild-ipc is using
non-blocking read with os.read and EAGAIN handling, just like
EbuildIpcDaemon since commit 7e5b81da12dd7bd59f6620840dc0d824e3f4d69a
(known compatible with FreeBSD).

bin/ebuild-ipc.py

index d8e7e55129c24cb2899891ac312dc046adee26bf..cb77171ece56a37038b122b50f55659057ff54a7 100755 (executable)
@@ -134,23 +134,43 @@ class EbuildIpc(object):
 
                return os.WEXITSTATUS(wait_retval[1])
 
-       def _receive_reply(self):
+       def _receive_reply(self, input_fd):
 
-               # File streams are in unbuffered mode since we do atomic
-               # read and write of whole pickles.
-               input_file = open(self.ipc_out_fifo, 'rb', 0)
-
-               # For maximum portability, use a single atomic read.
+               # Timeouts are handled by the parent process, so just
+               # block until input is available. For maximum portability,
+               # use a single atomic read.
                buf = None
-               try:
-                       buf = input_file.read(self._BUFSIZE)
-               except IOError as e:
-                       if not buf:
+               while True:
+                       try:
+                               events = select.select([input_fd], [], [])
+                       except select.error as e:
                                portage.util.writemsg_level(
-                                       "ebuild-ipc: %s\n" % (e,),
+                                       "ebuild-ipc: %s: %s\n" % \
+                                       (portage.localization._('during select for read'), e),
                                        level=logging.ERROR, noiselevel=-1)
+                               continue
 
-               input_file.close()
+                       if events[0]:
+                               # For maximum portability, use os.read() here since
+                               # array.fromfile() and file.read() are both known to
+                               # erroneously return an empty string from this
+                               # non-blocking fifo stream on FreeBSD (bug #337465).
+                               try:
+                                       buf = os.read(input_fd, self._BUFSIZE)
+                               except OSError as e:
+                                       if e.errno != errno.EAGAIN:
+                                               portage.util.writemsg_level(
+                                                       "ebuild-ipc: %s: %s\n" % \
+                                                       (portage.localization._('read error'), e),
+                                                       level=logging.ERROR, noiselevel=-1)
+                                               break
+                                       # Assume that another event will be generated
+                                       # if there's any relevant data.
+                                       continue
+
+                               # Only one (atomic) read should be necessary.
+                               if buf:
+                                       break
 
                retval = 2
 
@@ -192,6 +212,13 @@ class EbuildIpc(object):
                        self._no_daemon_msg()
                        return 2
 
+               # Open the input fifo before the output fifo, in order to make it
+               # possible for the daemon to send a reply without blocking. This
+               # improves performance, and also makes it possible for the daemon
+               # to do a non-blocking write without a race condition.
+               input_fd = os.open(self.ipc_out_fifo,
+                       os.O_RDONLY|os.O_NONBLOCK)
+
                # Use forks so that the child process can handle blocking IO
                # un-interrupted, while the parent handles all timeout
                # considerations. This helps to avoid possible race conditions
@@ -231,12 +258,13 @@ class EbuildIpc(object):
 
                if pid == 0:
                        os.close(pr)
-                       retval = self._receive_reply()
+                       retval = self._receive_reply(input_fd)
                        os._exit(retval)
 
                os.close(pw)
                retval = self._wait(pid, pr, portage.localization._('during read'))
                os.close(pr)
+               os.close(input_fd)
                return retval
 
 def ebuild_ipc_main(args):