From 1c373ce1e8b7cff25df2dec6e56d5923e6e307d2 Mon Sep 17 00:00:00 2001 From: Zac Medico Date: Tue, 7 Oct 2014 21:52:08 -0700 Subject: [PATCH] bin/ebuild-ipc.py: nonblocking fifo write Use nonblocking write instead of a fork for writing to the fifo. This has the advantage of avoiding a possible python futex deadlock following fork as reported in bug #524328. X-Gentoo-Bug: 524328 X-Gentoo-Bug-URL: https://bugs.gentoo.org/show_bug.cgi?id=524328 --- bin/ebuild-ipc.py | 64 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/bin/ebuild-ipc.py b/bin/ebuild-ipc.py index 3e6d90c..b47ee23 100755 --- a/bin/ebuild-ipc.py +++ b/bin/ebuild-ipc.py @@ -5,6 +5,7 @@ # This is a helper which ebuild processes can use # to communicate with portage's main python process. +import errno import logging import os import pickle @@ -44,19 +45,66 @@ import portage portage._internal_caller = True portage._disable_legacy_globals() -from portage.util._async.ForkProcess import ForkProcess from portage.util._eventloop.global_event_loop import global_event_loop +from _emerge.AbstractPollTask import AbstractPollTask from _emerge.PipeReader import PipeReader -class FifoWriter(ForkProcess): +RETURNCODE_WRITE_FAILED = 2 - __slots__ = ('buf', 'fifo',) +class FifoWriter(AbstractPollTask): - def _run(self): - # Atomically write the whole buffer into the fifo. - with open(self.fifo, 'wb', 0) as f: - f.write(self.buf) - return os.EX_OK + __slots__ = ('buf', 'fifo', '_fd', '_reg_id',) + + def _start(self): + try: + self._fd = os.open(self.fifo, os.O_WRONLY|os.O_NONBLOCK) + except OSError as e: + if e.errno == errno.ENXIO: + # This happens if the daemon has been killed. + self.returncode = RETURNCODE_WRITE_FAILED + self._unregister() + self._async_wait() + return + else: + raise + self._reg_id = self.scheduler.io_add_watch( + self._fd, + self.scheduler.IO_OUT | self.scheduler.IO_HUP | \ + self._exceptional_events, self._output_handler) + self._registered = True + + def _output_handler(self, fd, event): + if event & self.scheduler.IO_OUT: + # The whole buf should be able to fit in the fifo with + # a single write call, so there's no valid reason for + # os.write to raise EAGAIN here. + buf = self.buf + while buf: + buf = buf[os.write(fd, buf):] + self.returncode = os.EX_OK + self._unregister() + self.wait() + return False + else: + self._unregister_if_appropriate(event) + if not self._registered: + self.returncode = RETURNCODE_WRITE_FAILED + self.wait() + return False + return True + + def _cancel(self): + self.returncode = self._cancelled_returncode + self._unregister() + + def _unregister(self): + self._registered = False + if self._reg_id is not None: + self.scheduler.source_remove(self._reg_id) + self._reg_id = None + if self._fd is not None: + os.close(self._fd) + self._fd = None class EbuildIpc(object): -- 1.8.5.5