Merge "Fix incompatiablity between apscheduler and eventlet"

This commit is contained in:
Zuul 2024-12-02 19:13:24 +00:00 committed by Gerrit Code Review
commit 99fea33fac
2 changed files with 90 additions and 7 deletions

View File

@ -16,16 +16,22 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import eventlet
from apscheduler import events from apscheduler import events
from apscheduler.executors.pool import BasePoolExecutor from apscheduler.executors import pool as pool_executor
from apscheduler.schedulers import background from apscheduler.schedulers import background
import futurist import futurist
from oslo_service import service from oslo_service import service
from watcher import eventlet as eventlet_helper
job_events = events job_events = events
class GreenThreadPoolExecutor(BasePoolExecutor): class GreenThreadPoolExecutor(pool_executor.BasePoolExecutor):
"""Green thread pool """Green thread pool
An executor that runs jobs in a green thread pool. An executor that runs jobs in a green thread pool.
@ -43,16 +49,25 @@ executors = {
} }
class BackgroundSchedulerService(service.ServiceBase, class BackgroundSchedulerService(
background.BackgroundScheduler): service.ServiceBase, background.BackgroundScheduler):
def __init__(self, gconfig={}, **options): def __init__(self, gconfig=None, **options):
self.should_patch = eventlet_helper.is_patched()
if options is None: if options is None:
options = {'executors': executors} options = {'executors': executors}
else: else:
if 'executors' not in options.keys(): if 'executors' not in options.keys():
options['executors'] = executors options['executors'] = executors
super(BackgroundSchedulerService, self).__init__( super().__init__(gconfig or {}, **options)
gconfig, **options)
def _main_loop(self):
if self.should_patch:
# NOTE(sean-k-mooney): is_patched and monkey_patch form
# watcher.eventlet check a non thread local variable to early out
# as we do not use eventlet_helper.patch() here to ensure
# eventlet.monkey_patch() is actually called.
eventlet.monkey_patch()
super()._main_loop()
def start(self): def start(self):
"""Start service.""" """Start service."""

View File

@ -0,0 +1,68 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from unittest import mock
import eventlet
from apscheduler.schedulers import background
from watcher.common import scheduling
from watcher import eventlet as eventlet_helper
from watcher.tests import base
class TestSchedulerMonkeyPatching(base.BaseTestCase):
def setUp(self):
super().setUp()
self.started = False
self.test_scheduler = scheduling.BackgroundSchedulerService()
self.addCleanup(self._cleanup_scheduler)
def _cleanup_scheduler(self):
if self.started:
self.test_scheduler.shutdown()
self.started = False
def _start_scheduler(self):
self.test_scheduler.start()
self.started = True
@mock.patch.object(scheduling.BackgroundSchedulerService, 'start')
def test_scheduler_start(self, mock_start):
self.test_scheduler.start()
mock_start.assert_called_once_with()
@mock.patch.object(scheduling.BackgroundSchedulerService, 'shutdown')
def test_scheduler_stop(self, mock_shutdown):
self._start_scheduler()
self.test_scheduler.stop()
mock_shutdown.assert_called_once_with()
@mock.patch.object(scheduling.BackgroundSchedulerService, '_main_loop')
def test_scheduler_main_loop(self, mock_main_loop):
self._start_scheduler()
mock_main_loop.assert_called_once_with()
@mock.patch.object(background.BackgroundScheduler, '_main_loop')
@mock.patch.object(eventlet, 'monkey_patch')
def test_main_loop_is_monkey_patched(
self, mock_monky_patch, mock_main_loop):
self.test_scheduler._main_loop()
self.assertEqual(
eventlet_helper.is_patched(), self.test_scheduler.should_patch)
mock_monky_patch.assert_called_once_with()
mock_main_loop.assert_called_once_with()
def test_scheduler_should_patch(self):
self.assertEqual(
eventlet_helper.is_patched(), self.test_scheduler.should_patch)