Skip to content
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

close shared clients when loop closes #579

Merged
merged 4 commits into from Feb 25, 2022

Conversation

minrk
Copy link
Member

@minrk minrk commented Feb 24, 2022

closes #578

Closes api_client by running a coroutine that will be cancelled when the event loop finishes holding with client.api_client. JupyterHub cancels all tasks prior to closing the loop, so this should be okay, but it isn't rigorous for all uses of asyncio.

requires monkeypatching asyncio.EventLoop.close to run cleanup functions

not sure what other recourse we have for long-running tasks which should cleanup eventually
@minrk
Copy link
Member Author

minrk commented Feb 24, 2022

If we duplicate JupyterHub's task cleanup into our test fixtures, I think I can make this work without patching.

Copy link
Member

@consideRatio consideRatio left a comment

Choose a reason for hiding this comment

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

Beautiful! I think this is relatively little intrusive as it just appends some logic while otherwise preserving all logic.

And I understand it as this only becomes relevant when the event loop shuts down - and that should only happen as part of the entire application starts to shut down - right?

Non-critical comments made - this LGTM as a solution to #578.

tests/test_utils.py Outdated Show resolved Hide resolved
kubespawner/_asyncio_atexit.py Outdated Show resolved Hide resolved
no patches required, but does assume that tasks are cancelled when the loop exits.
JupyterHub does this, so it's a reasonable assumption for us,
but not in general in asyncio.
@minrk
Copy link
Member Author

minrk commented Feb 24, 2022

I pushed another commit that avoids any monkeypatching, but instead relies on cleanup of pending tasks, which JupyterHub does in jupyterhub/jupyterhub#2519.

… must be async

KubeSpawner constructor calls shared_client,
which calls asyncio.get_running_loop.

Could switch to get_event_loop, but that's deprecated.
There is no longer a standard API for getting a 'current' but not running loop.
@minrk
Copy link
Member Author

minrk commented Feb 24, 2022

The one test failure is a real one and a race condition - the reflector tasks themselves may not be canceled before the close-client is canceled, meaning their requests may fail due to a closed session. Need to think about that a bit. We may need to explicitly shut down the reflectors somehow in the right order (garbage collection ensured the right order, but the gc __del__ can't be async...).

when tasks are cancelled, they should stop, not keep retrying

removes unnecessary threading Event now that reflectors are in the same thread
@consideRatio
Copy link
Member

consideRatio commented Feb 25, 2022

@minrk this looks good to me, are you happy with the changes as they are at this point? I'll re-run the test a few times to try catch a potential missed race condition and merge if you are.

Can you update the PR description to reflect the current state of how you went about things?

@minrk
Copy link
Member Author

minrk commented Feb 25, 2022

I think the CancelledError handling will actually solve the races, but I can't be 100% sure. If tests are reliable, let's go ahead.

@minrk
Copy link
Member Author

minrk commented Feb 25, 2022

Updated description.

@consideRatio consideRatio merged commit 0b6ad95 into jupyterhub:main Feb 25, 2022
@consideRatio
Copy link
Member

consideRatio commented Feb 25, 2022

Wieeee @minrk amazing work! I appreciate seeing this complexity reduction in the project and I love learning from reviewing your work! ❤️ 🎉

I ran the test suite five times or so, not a single failure showed up :)

@consideRatio
Copy link
Member

I'm not sure how to label things. This was a bug, but it wasn't released, so, should it be marked as a bug for the changelog that is fixed? On the other hand, should it be labelled a bug to know that it solved a bug in case someone used the default branch version or a developer wanted insight about the purpose of this change?

Yikes... Well, I'm going for maintenance now.

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

Successfully merging this pull request may close these issues.

Avoid emitting warnings/errors during normal operation
2 participants