Skip to content

fix: Gracefully handle event loops in fresh process - #811

Open
hneiva wants to merge 1 commit into
mainfrom
hneiva/event-loop-fix
Open

fix: Gracefully handle event loops in fresh process#811
hneiva wants to merge 1 commit into
mainfrom
hneiva/event-loop-fix

Conversation

@hneiva

@hneiva hneiva commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

In Python 3.12/3.13 they deprecated get_event_loop() returning a new loop when none is current. In Python 3.14 it now raises a RuntimeError.
Reference: https://docs.python.org/3.14/library/asyncio-eventloop.html#asyncio.get_event_loop

@hneiva
hneiva requested a review from a team as a code owner July 30, 2026 23:29
Comment thread HISTORY.rst Outdated
Comment thread src/scriptworker/context.py Outdated
Comment on lines +248 to +251
try:
self._event_loop = asyncio.get_running_loop()
except RuntimeError:
self._event_loop = asyncio.new_event_loop()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems kind of awful. Can we do something consistent here instead? What do the tests actually require?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it's awful.
But it's better than if asyncio.get_event_loop_policy()._local._loop is not None

They don't have a way of checking if there is a current event loop and actually recommend this exact method 🫠

@hneiva
hneiva force-pushed the hneiva/event-loop-fix branch from 10c3b47 to 4bc73ad Compare July 31, 2026 15:44
@hneiva
hneiva enabled auto-merge July 31, 2026 15:45
@hneiva
hneiva requested a review from jcristau July 31, 2026 15:45
In Python 3.12/3.13 they deprecated get_event_loop() returning a new loop when none is current.
In Python 3.14 it now raises a RuntimeError.
Reference: https://docs.python.org/3.14/library/asyncio-eventloop.html#asyncio.get_event_loop
@hneiva
hneiva force-pushed the hneiva/event-loop-fix branch from 254ec40 to d994670 Compare August 4, 2026 19:37
except RuntimeError as exc:
raise ScriptWorkerException(
"No event loop is running and none was set on the context. Set ``context.event_loop`` before use, or read it from a coroutine."
) from exc

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only change here now is that we raise a ScriptWorkerException instead of letting asyncio raise a RuntimeError

@bhearsum bhearsum left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm r+'ing, but please seriously consider scrapping the noted test, and maybe even dropping the event loop closing to avoid unnecessary complexity (and tests).

if event_loop is None:
owned_event_loop = asyncio.new_event_loop()
context.event_loop = owned_event_loop
else:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a tad unfortunate that the only use for this path is in tests...but that's not easy to change considering some of the tests appear to need to re-use the same event loop for other things. Definitely out of scope here.

finally:
if owned_event_loop is not None:
# closing the loop also deregisters the signal handlers added above
owned_event_loop.close()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest, I don't really think there's a need to explicitly close this, which would avoid the need to keep track of it like this. There's no python code that runs after this point...so there's no harm in not explicitly closing it IMO.

Not going to block on this though.

Comment thread tests/test_worker.py


def test_main_repeated_calls_do_not_leak_loops(mocker, context):
"""Restarting ``main()`` in one process doesn't accumulate open loops."""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: the only way for main to be called outside of tests is by running scriptworker. There is no way for it to be called more than once per process. IMO you should scrap this test, as it doesn't cover any remotely real scenario.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants