/
githubmirror
/
salt
Обзор
Документация
Войти
/
githubmirror
/
salt
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
tests/pytests/unit/utils/test_asynchronous.py
140 строк
4 KB
Daniel A. Wozniak
pre-commit fix
01 июл 2026, 09:42
01 июл 2026, 09:42
902dfe4
Код
Авторство
О чём код?
""" Tests for salt.utils.asynchronous.SyncWrapper. Includes regression tests for issue #65702: on Python 3.12+ the worker thread spawned by ``SyncWrapper._wrap`` had no asyncio event loop installed. Any wrapped coroutine that touched ``asyncio.get_event_loop`` (notably pyzmq's future-based sockets, which back every master-initiated job) raised ``RuntimeError: There is no current event loop in thread 'Thread-N (_target)'`` and aborted the publish. """ import asyncio import tornado.gen import tornado.ioloop import salt.utils.asynchronous as asynchronous class HelperA: async_methods = [ "sleep", ] def __init__(self, io_loop=None): pass @tornado.gen.coroutine def sleep(self): yield tornado.gen.sleep(0.1) raise tornado.gen.Return(True) class HelperB: async_methods = [ "sleep", ] def __init__(self, a=None, io_loop=None): if a is None: a = asynchronous.SyncWrapper(HelperA) self.a = a @tornado.gen.coroutine def sleep(self): yield tornado.gen.sleep(0.1) self.a.sleep() raise tornado.gen.Return(False) class _LoopProbe: """ Minimal async helper whose coroutine calls ``asyncio.get_event_loop`` from inside the SyncWrapper worker thread - the same call pyzmq's ``zmq.eventloop.future`` machinery performs on every send/poll. """ async_methods = ["check_loop"] def __init__(self, io_loop=None): pass @tornado.gen.coroutine def check_loop(self): # On Python 3.12+ this raises RuntimeError unless an asyncio loop # has been installed on the current thread. Pre-3.12 it returns # (and may auto-create) the loop. loop = asyncio.get_event_loop() raise tornado.gen.Return(loop is not None) def test_helpers(): """ Test that the helper classes do what we expect within a regular asynchronous env """ asyncio_loop = asyncio.new_event_loop() io_loop = tornado.ioloop.IOLoop(asyncio_loop=asyncio_loop, make_current=False) ret = io_loop.run_sync(lambda: HelperA().sleep()) assert ret is True ret = io_loop.run_sync(lambda: HelperB().sleep()) assert ret is False def test_basic_wrap(): """ Test that we can wrap an asynchronous caller. """ sync = asynchronous.SyncWrapper(HelperA) ret = sync.sleep() assert ret is True def test_basic_wrap_series(): """ Test that we can wrap an asynchronous caller and call the method in series. """ sync = asynchronous.SyncWrapper(HelperA) ret = sync.sleep() assert ret is True ret = sync.sleep() assert ret is True def test_double(): """ Test when the asynchronous wrapper object itself creates a wrap of another thing This works fine since the second wrap is based on the first's IOLoop so we don't have to worry about complex start/stop mechanics """ sync = asynchronous.SyncWrapper(HelperB) ret = sync.sleep() assert ret is False def test_double_sameloop(): """ Test asynchronous wrappers initiated from the same IOLoop, to ensure that we don't wire up both to the same IOLoop (since it causes MANY problems). """ a = asynchronous.SyncWrapper(HelperA) sync = asynchronous.SyncWrapper(HelperB, (a,)) ret = sync.sleep() assert ret is False def test_sync_wrapper_thread_has_asyncio_loop_65702(): """ SyncWrapper's worker thread must expose an asyncio event loop so that libraries which call ``asyncio.get_event_loop`` (e.g. pyzmq's future-based sockets used by master-initiated job publishes) work on Python 3.12+. """ sync = asynchronous.SyncWrapper(_LoopProbe) try: assert sync.check_loop() is True finally: sync.close()