From 159ef2e1d26f25a5d7a0514d5155f3c74c4a8a86 Mon Sep 17 00:00:00 2001 From: Zane Bitter Date: Mon, 10 Dec 2018 19:42:30 +1300 Subject: [PATCH] Restore correct signal handling in Python3 The patch 2ee3894f49f315e35abff968f54ae72e5480e892 broke the original fix cad75e4e139f734a5138d37ceafa6be169ff4e47 that ensured eventlet could be interrupted while sleeping after PEP475 was implemented in Python 3.5. Eventlet monkey-patches the signal module with its own version, so we have to look up the original module to determine whether the underlying OS actually supports the poll() function. Change-Id: Ia712c9a83d8081bf0b5e6fe36f169f9028aae3dc Closes-Bug: #1803731 Related-Bug: #1788022 Related-Bug: #1705047 --- oslo_service/service.py | 4 ++-- oslo_service/tests/test_service.py | 19 ++++++++++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/oslo_service/service.py b/oslo_service/service.py index 2e56d30a..133a6a2b 100644 --- a/oslo_service/service.py +++ b/oslo_service/service.py @@ -28,7 +28,6 @@ import io import logging import os import random -import select import signal import six import sys @@ -204,8 +203,9 @@ class SignalHandler(object): For Python 3.5 and later, deal with the changes in PEP 475 that prevent a signal from interrupting eventlet's call to poll() or sleep(). """ + select_module = eventlet.patcher.original('select') self.__force_interrupt_on_signal = (sys.version_info >= (3, 5) and - hasattr(select, 'poll')) + hasattr(select_module, 'poll')) if self.__force_interrupt_on_signal: try: diff --git a/oslo_service/tests/test_service.py b/oslo_service/tests/test_service.py index 7c616000..0719bbd6 100644 --- a/oslo_service/tests/test_service.py +++ b/oslo_service/tests/test_service.py @@ -478,15 +478,28 @@ class ProcessLauncherTest(base.ServiceBaseTestCase): signal_handler.clear() @mock.patch('sys.version_info', (3, 5)) - @mock.patch.object(service, 'select', spec=[]) - def test_setup_signal_interruption_no_select_poll(self, mock_select): + def test_setup_signal_interruption_no_select_poll(self): + # NOTE(claudiub): SignalHandler is a singleton, which means that it + # might already be initialized. We need to clear to clear the cache + # in order to prevent race conditions between tests. + service.SignalHandler.__class__._instances.clear() + with mock.patch('eventlet.patcher.original', + return_value=object()) as get_original: + signal_handler = service.SignalHandler() + get_original.assert_called_with('select') + self.addCleanup(service.SignalHandler.__class__._instances.clear) + self.assertFalse( + signal_handler._SignalHandler__force_interrupt_on_signal) + + @mock.patch('sys.version_info', (3, 5)) + def test_setup_signal_interruption_select_poll(self): # NOTE(claudiub): SignalHandler is a singleton, which means that it # might already be initialized. We need to clear to clear the cache # in order to prevent race conditions between tests. service.SignalHandler.__class__._instances.clear() signal_handler = service.SignalHandler() self.addCleanup(service.SignalHandler.__class__._instances.clear) - self.assertFalse( + self.assertTrue( signal_handler._SignalHandler__force_interrupt_on_signal) @mock.patch('signal.alarm')