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

Fix shutdown and cleanup behavior #772

Merged
merged 22 commits into from Apr 25, 2022

Conversation

blink1073
Copy link
Member

@blink1073 blink1073 commented Apr 17, 2022

Fixes #771

$ jupyter qtconsole
qt.qpa.drawing: Layer-backing can not be explicitly controlled on 10.14 when built against the 10.14 SDK
$

Includes improvements made while working on jupyter-server/jupyter_server#792

Also avoids leaving event loops open at exit in a few more places.

@blink1073 blink1073 requested a review from Zsailer April 17, 2022 12:29
@blink1073 blink1073 changed the title Fix shutdown behavior Fix shutdown and cleanup behavior Apr 17, 2022
@blink1073 blink1073 marked this pull request as draft April 18, 2022 11:25
@blink1073
Copy link
Member Author

This is now working well in conjunction with jupyter-server/jupyter_server#792. Note that the downstream jupyter_server test passes with these changes, but that PR requires these changes in order to exit cleanly.

@blink1073 blink1073 marked this pull request as ready for review April 24, 2022 11:23
@Carreau Carreau mentioned this pull request Apr 24, 2022
@blink1073
Copy link
Member Author

This actually breaks nbclient, I can repro locally. Working a fix.

@blink1073 blink1073 marked this pull request as draft April 24, 2022 11:59
@blink1073
Copy link
Member Author

Okay, this now works for all the test cases:

@blink1073 blink1073 marked this pull request as ready for review April 24, 2022 16:07
Copy link
Member

@kevin-bates kevin-bates left a comment

Choose a reason for hiding this comment

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

These changes look good to me - thank you @blink1073!

I confirmed that the noise following the exit of jupyter qtconsole when 7.2.2 is used is now completely silent.

@ccordoba12 or @mrclary - are either of you able to take this for a spin as well? (Thanks for the great reproduction scenario!)

@mrclary
Copy link

mrclary commented Apr 25, 2022

@kevin-bates @blink1073, thanks for the quick updates!
I can confirm that this PR does not produce the ZMQ errors that I reported in #771.

The baseline:

>> conda create -n jup python=3.9.12 jupyter_client=7.2.2 qtconsole=5.3.0
>> conda activate jup
(jup) >> jupyter qtconsole

produces the errors that I reported on shutdown. Then using https://github.com/blink1073/jupyter_client/tree/fix-shutdown-behavior

(jup) >> pip install --no-deps ~/Documents/Python/jupyter_client
(jup) >> jupyter qtconsole

does not produce the errors on shutdown.

@blink1073
Copy link
Member Author

Cool, let's give it a go.

@blink1073 blink1073 merged commit fa597d9 into jupyter:main Apr 25, 2022
@blink1073 blink1073 deleted the fix-shutdown-behavior branch April 25, 2022 18:24
@mariokostelac
Copy link

@blink1073 I might be wrong, but I think this causes nbclient leaking eventpoll FDs every time its util.just_run is called.

I've tried fixing it by stopping the loop in jupyter/nbclient#237, but

loop = asyncio.get_event_loop_policy().get_event_loop()
picks up a closed loop, and tries to run it, which fails.

I'm interested in why the method doesn't create a new loop every time there's no running loop?

        try:
            loop = asyncio.get_running_loop()
        except RuntimeError:
            # Workaround for bugs.python.org/issue39529.
            #try:
             #   loop = asyncio.get_event_loop_policy().get_event_loop()
            #except RuntimeError:
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)

I'd be quite happy to fix the issue, but I think I need a bit more context to understand what's expected from run_async function.

@blink1073
Copy link
Member Author

Hi @mariokostelac, I'd be concerned about another part of the application having a handle on a non-running loop. I think maybe we need to check if the loop returned by asyncio.get_event_loop_policy().get_event_loop() is closed before assigning a new one.

@mariokostelac
Copy link

mariokostelac commented Jun 30, 2022

Hi @mariokostelac, I'd be concerned about another part of the application having a handle on a non-running loop.

@blink1073 any chance you can explain this a bit more? Do you think that stopping and closing the loop in the linked PR is problematic? If we don't do it, we definitely leak open loops, and consequently, eventpoll FDs.

To be transparent upfront, I'm no expert on async subsytem in python, so take everything with a grain of salt.
My understanding is that the problem lies in

loop = asyncio.get_event_loop_policy().get_event_loop()
. If there is a running loop in the thread running a function,
loop = asyncio.get_running_loop()
will get it, and we'll enqueue our function at the end of it. That's a "happy path", it's all good.

If there's no running loop, we need some loop to run our coroutine. New loop seems perfectly reasonable to me, but we go further, and go straight to the event policy (to avoid the deprecation), and risk that we get a loop that's closed already.

Why do we do that? Is it to reduce loop creations? Is the problem that nbclient's just_run doesn't go that far to get a loop to run on?

Also, what's the expectation from "current loop"? Is the code I've suggested in nbclient lib breaking some contract by stopping and cancelling the loop?

@blink1073
Copy link
Member Author

My concern is if another part of the program has called asyncio.get_event_loop_policy().get_event_loop() or doesn't have warnings as errors turned on, and used asyncio.get_event_loop(), but that loop has not yet been started, we would throw it away instead of running it. What I'm suggesting is something like:

        try:
            loop = asyncio.get_running_loop()
        except RuntimeError:
            try:
                loop = asyncio.get_event_loop_policy().get_event_loop()
                if loop.is_closed():
                     loop = asyncio.new_event_loop()
            except RuntimeError:
                loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)

@mariokostelac
Copy link

Hmm, I get your concern. I wonder whether the solution I've suggested in nbclient is actually ok, or it leaves the system in some unexpected state. If latter is the case, we can change jupyter_client to get around it, but some other (unrelated) libraries could break.

Is it expected that get_event_loop always returns a non-closed loop?

@blink1073
Copy link
Member Author

It isn't clear from the docs. In any case, the get_event_loop_policy().get_event_loop() was meant to be deprecated along with asyncio.get_event_loop(), but wasn't in 3.10 due to an oversight. We'll want to implement #755 for a proper fix.

@mariokostelac
Copy link

mariokostelac commented Oct 11, 2022 via email

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

Successfully merging this pull request may close these issues.

Client hangs with version 7.2.2
4 participants