lib.App: Restore previous event loop in run()
When run() creates an event loop, it installs it with set_event_loop() but never restores the thread's previous loop, so after run() returns, the thread is left with the now-closed loop created by run(). Any code that calls get_event_loop() afterwards gets a closed loop, and on Python 3.13+ a thread that had no loop at all starts emitting or raising deprecation errors that run() caused. Capture the thread's current loop with _get_current_event_loop() before installing a new one, and restore it in the finally block. If there was no previous loop, unset the loop with set_event_loop(None) so that the thread is left without a loop instead of with the closed one. Assisted-by: unsloth/Qwen3.8-27B-GGUF:Q4_K_M with pi.dev v0.84.2 Signed-off-by: Jan Lindemann <jan@janware.com>
This commit is contained in:
parent
157fa86fb7
commit
845601ff10
1 changed files with 20 additions and 0 deletions
|
|
@ -4,6 +4,7 @@ import asyncio
|
||||||
import cProfile
|
import cProfile
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import warnings
|
||||||
|
|
||||||
from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser, Namespace
|
from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser, Namespace
|
||||||
from typing import TYPE_CHECKING, Any, cast, override
|
from typing import TYPE_CHECKING, Any, cast, override
|
||||||
|
|
@ -31,6 +32,17 @@ if TYPE_CHECKING:
|
||||||
from typing import TypeVar
|
from typing import TypeVar
|
||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
|
|
||||||
|
def _get_current_event_loop() -> asyncio.AbstractEventLoop | None:
|
||||||
|
"""Return the current event loop of this thread, or None if there is
|
||||||
|
none, without creating one implicitly or emitting a deprecation
|
||||||
|
warning."""
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter('error', DeprecationWarning)
|
||||||
|
try:
|
||||||
|
return asyncio.get_event_loop()
|
||||||
|
except (RuntimeError, DeprecationWarning):
|
||||||
|
return None
|
||||||
|
|
||||||
class App: # export
|
class App: # export
|
||||||
|
|
||||||
def _add_arguments(self, parser: ArgumentParser) -> None:
|
def _add_arguments(self, parser: ArgumentParser) -> None:
|
||||||
|
|
@ -341,7 +353,9 @@ class App: # export
|
||||||
return self.__parser
|
return self.__parser
|
||||||
|
|
||||||
def run(self, argv: list[str] | None = None) -> None:
|
def run(self, argv: list[str] | None = None) -> None:
|
||||||
|
previous_eloop: asyncio.AbstractEventLoop | None = None
|
||||||
if self.__eloop is None:
|
if self.__eloop is None:
|
||||||
|
previous_eloop = _get_current_event_loop()
|
||||||
eloop = asyncio.new_event_loop()
|
eloop = asyncio.new_event_loop()
|
||||||
asyncio.set_event_loop(eloop)
|
asyncio.set_event_loop(eloop)
|
||||||
self.__eloop = eloop
|
self.__eloop = eloop
|
||||||
|
|
@ -350,6 +364,12 @@ class App: # export
|
||||||
ret = self.eloop.run_until_complete(self.__run(argv))
|
ret = self.eloop.run_until_complete(self.__run(argv))
|
||||||
finally:
|
finally:
|
||||||
self.close()
|
self.close()
|
||||||
|
# -- Restore the event loop the thread had before run(), or
|
||||||
|
# unset the loop if there was none.
|
||||||
|
if previous_eloop is not None:
|
||||||
|
asyncio.set_event_loop(previous_eloop)
|
||||||
|
else:
|
||||||
|
asyncio.set_event_loop(None)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue