diff --git a/lib/daemon.py b/lib/daemon.py index 185f2e7be53aba5f124c803a17de2e983b58aa05..1f210b9893655773921cce4a0829d8139ee5679f 100644 --- a/lib/daemon.py +++ b/lib/daemon.py @@ -94,15 +94,8 @@ class AsyncUDPSocket(asyncore.dispatcher): # this method is overriding an asyncore.dispatcher method def handle_read(self): - try: - payload, address = self.recvfrom(constants.MAX_UDP_DATA_SIZE) - except socket.error, err: - if err.errno == errno.EINTR: - # we got a signal while trying to read. no need to do anything, - # handle_read will be called again if there is data on the socket. - return - else: - raise + payload, address = utils.IgnoreSignals(self.recvfrom, + constants.MAX_UDP_DATA_SIZE) ip, port = address self.handle_datagram(payload, ip, port) @@ -124,16 +117,7 @@ class AsyncUDPSocket(asyncore.dispatcher): logging.error("handle_write called with empty output queue") return (ip, port, payload) = self._out_queue[0] - try: - self.sendto(payload, 0, (ip, port)) - except socket.error, err: - if err.errno == errno.EINTR: - # we got a signal while trying to write. no need to do anything, - # handle_write will be called again because we haven't emptied the - # _out_queue, and we'll try again - return - else: - raise + utils.IgnoreSignals(self.sendto, payload, 0, (ip, port)) self._out_queue.pop(0) # this method is overriding an asyncore.dispatcher method diff --git a/lib/utils.py b/lib/utils.py index c1d5c6fbe876aad9c916b6731d05c0e7d8e0d851..3cbebe404e958dedd08dd7fd28e1bb7650a2559e 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -2507,6 +2507,20 @@ def RunInSeparateProcess(fn, *args): return bool(exitcode) +def IgnoreSignals(fn, *args, **kwargs): + """Tries to call a function ignoring failures due to EINTR. + + """ + try: + return fn(*args, **kwargs) + except (EnvironmentError, socket.error), err: + if err.errno != errno.EINTR: + raise + except select.error, err: + if not (err.args and err.args[0] == errno.EINTR): + raise + + def LockedMethod(fn): """Synchronized object access decorator. diff --git a/test/ganeti.utils_unittest.py b/test/ganeti.utils_unittest.py index ad08f125025579343ca32c14a8c7e179e02f068c..b8c00de466f0b15d9d94337c0cc83cfd09f48cf7 100755 --- a/test/ganeti.utils_unittest.py +++ b/test/ganeti.utils_unittest.py @@ -39,6 +39,7 @@ import warnings import distutils.version import glob import md5 +import errno import ganeti import testutils @@ -1822,5 +1823,41 @@ class TestLineSplitter(unittest.TestCase): "", "x"]) +class TestIgnoreSignals(unittest.TestCase): + """Test the IgnoreSignals decorator""" + + @staticmethod + def _Raise(exception): + raise exception + + @staticmethod + def _Return(rval): + return rval + + def testIgnoreSignals(self): + sock_err_intr = socket.error(errno.EINTR, "Message") + sock_err_intr.errno = errno.EINTR + sock_err_inval = socket.error(errno.EINVAL, "Message") + sock_err_inval.errno = errno.EINVAL + + env_err_intr = EnvironmentError(errno.EINTR, "Message") + env_err_inval = EnvironmentError(errno.EINVAL, "Message") + + self.assertRaises(socket.error, self._Raise, sock_err_intr) + self.assertRaises(socket.error, self._Raise, sock_err_inval) + self.assertRaises(EnvironmentError, self._Raise, env_err_intr) + self.assertRaises(EnvironmentError, self._Raise, env_err_inval) + + self.assertEquals(utils.IgnoreSignals(self._Raise, sock_err_intr), None) + self.assertEquals(utils.IgnoreSignals(self._Raise, env_err_intr), None) + self.assertRaises(socket.error, utils.IgnoreSignals, self._Raise, + sock_err_inval) + self.assertRaises(EnvironmentError, utils.IgnoreSignals, self._Raise, + env_err_inval) + + self.assertEquals(utils.IgnoreSignals(self._Return, True), True) + self.assertEquals(utils.IgnoreSignals(self._Return, 33), 33) + + if __name__ == '__main__': testutils.GanetiTestProgram()