-
Notifications
You must be signed in to change notification settings - Fork 141
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added support for subinterpreter workers #850
Conversation
src/anyio/_core/_exceptions.py
Outdated
else: | ||
return dedent( | ||
f""" | ||
{super().__str__()} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
formatting
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better now?
src/anyio/to_interpreter.py
Outdated
try: | ||
func, args, kwargs = loads(item) | ||
retval = func(*args, **kwargs) | ||
except Exception as exc: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BaseException?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed.
src/anyio/to_interpreter.py
Outdated
_interpreter_id: int | ||
_queue_id: int | ||
|
||
async def initialize(self) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
async def initialize(self) -> None: | |
def initialize(self) -> None: |
does this need to be async?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, and in fact it should not set up the interpreter in the event loop thread either. I'll fix both issues at once.
Do we need pruning of interpreters that have been unused for too long? |
Actually I was going to comment on that. following the trio thread cache model has been standard. Unlike threads or processes though, it seems like your implementation would not be able to free up any resources of a timed-out worker until someone calls Also, are there any implications for unexpected behavior as the subinterpreters jump around different threads? |
I was thinking of pruning unused workers before or after a call to
None that I'm aware of. |
Separately, the cancellation story of a busy worker seems poor here. if you abandon the thread, the |
But the worker threads should all be gone by the time the atexit hooks are run? |
I tested with this: import time
import anyio
from anyio import to_interpreter
async def main():
await to_interpreter.run_sync(time.sleep, 6, abandon_on_cancel=True)
anyio.run(main) It won't exit the process until the worker thread has run its course. |
I think you mean processes? anyway, the issue is that the processes can automatically free up most resources except the OS process table bits that need waiting. An interpreter might sit on gigabytes of memory or thousands of sockets until the next call rather than the prescribed timeout.
Not if the subinterpereter is deadlocked or something like that! Also, just realized that a cancelled subinterpreter worker should definitely not be returned to the idle worker queue. |
Explain please. The task might abandon the worker thread, but the worker thread won't abandon the subinterpreter; it will continue to run the given code until it completes.
Yes, if |
Is this all happening in a non-daemon thread? or is the interpreter shutodwn logic of python doing this? Either way it seems problematic. Maybe an initial release could ignore "abandon_on_cancel"? |
The worker threads are daemonic, but there is a hook added to the root task that will ensure the threads have finished before the event loop exits. It's not 100% foolproof (what is?) but good enough for the vast majority of cases. But I'm okay with leaving out |
Maybe i don't understand the order of operations on exit. I thought the main script ends, then atexits are run, then daemon threads are held up acquiring the gil, then interpreters are destroyed, then python runtime ends. Reading your previous message, it sounds like anyio interjects another step up there. I think cancellation should wait until subinterpreters have a better interruption story. I tried and failed to make workers that run on channels and thread/interpreter pairs. |
Co-authored-by: Jordan Speicher <uSpike@users.noreply.github.com>
I just reviewed the |
We can add it back later, just needs to be consistent across the worker thread/interpreter/process APIs
Changes
Adds experimental support for subinterpreter workers
Checklist
If this is a user-facing code change, like a bugfix or a new feature, please ensure that
you've fulfilled the following conditions (where applicable):
tests/
) added which would fail without your patchdocs/
, in case of behavior changes or newfeatures)
docs/versionhistory.rst
).If this is a trivial change, like a typo fix or a code reformatting, then you can ignore
these instructions.
Updating the changelog
If there are no entries after the last release, use
**UNRELEASED**
as the version.If, say, your patch fixes issue #123, the entry should look like this:
If there's no issue linked, just link to your pull request instead by updating the
changelog after you've created the PR.