cpython icon indicating copy to clipboard operation
cpython copied to clipboard

deprecate the asyncio child watchers system and policy system

Open graingert opened this issue 3 years ago • 27 comments

Deprecate the child watchers system and the policy system in favour of asyncio.Runner(loop_factory=asyncio.ProactorEventLoop/asyncio.SelectorEventLoop/uvloop.new_event_loop) that would be deprecating:

asyncio.get_event_loop() # already deprecated unless the loop is running 
asyncio.set_event_loop()  # asyncio.set_event_loop(None) should probably be exempt
asyncio.get_event_loop_policy()
asyncio.set_event_loop_policy()  # asyncio.set_event_loop_policy(None) should probably be exempt
asyncio.set_child_watcher()
asyncio.get_child_watcher()  # `_make_subprocess_transport` will instead attempt to use os.open_pidfd and fallback to starting a thread

I'd also like to introduce a new API: asyncio.EventLoop implemented as:

if sys.platform == "win32":
    EventLoop = ProactorEventLoop
else:
    EventLoop = SelectorEventLoop

asyncio.new_event_loop() will issue a DeprecationWarning if the current policy is not the default policy, and then in 3 releases become an alias of asyncio.EventLoop

Originally posted by @graingert in https://github.com/python/cpython/issues/93896#issuecomment-1159182995

  • PR: gh-99386

graingert avatar Jul 06 '22 08:07 graingert

Some background to this proposed deprecation:

This is a more comprehensive version of https://github.com/python/cpython/issues/82772

@serhiy-storchaka proposed deprecating set_event_loop() in https://github.com/python/cpython/issues/93453#issue-1259542498

But maybe we should first deprecate set_event_loop()? It will be a no-op now.

@asvetlov noted that get_event_loop_policy().get_event_loop() was not deprecated by oversight in https://github.com/python/cpython/issues/83710#issuecomment-1093855411

IMHO, asyncio.set_event_loop() and policy.get_event_loop()/policy.set_event_loop() are not deprecated by oversight.

graingert avatar Jul 06 '22 08:07 graingert

We are in dire need of more asyncio experts. @1st1 this isn't urgent but would be nice to have your perspective in time to do this in 3.12.

gvanrossum avatar Jul 06 '22 17:07 gvanrossum

For 3.12 IMO we should deprecate MultiLoopWatcher https://github.com/python/cpython/issues/82504 and others which have race condition and other issue. Once that's done we may deprecate the entire child watcher system but just removing MultiLoopWatcher would be a good start.

kumaraditya303 avatar Jul 06 '22 17:07 kumaraditya303

@kumaraditya303 https://github.com/python/cpython/pull/94648

graingert avatar Jul 07 '22 09:07 graingert

We discussed this at the sprint and we agree that there are many things wrong with the child watchers and the policy system.

Deprecating child watchers: @1st1 thinks these should be done per loop (like uvloop does), not globally by the policy. Much more discussion on this topic is already in https://github.com/python/cpython/issues/82772. Bottom line, we agree to deprecate it, details remain to be seen.

Deprecating policies: Yes please. The policies no longer serve a real purpose. Loops are always per thread, there is no need to have a "current loop" when no loop is currently running. The only thing we still need is a loop factory, so perhaps instead of an API for getting/setting a global "policy", we could have an API for getting/setting a global "loop factory".

I'm fine with the EventLoop alias (it ties up a loose end), but I recommend that the API for creating a new event loop (when not using runners) should be asyncio.new_event_loop(), not asyncio.EventLoop().

We should totally deprecate set_event_loop() (even with None argument). At that point we can make get_event_loop() an alias for get_running_loop() (or the other way around -- I prefer calling get_event_loop() :-).

gvanrossum avatar Oct 06 '22 23:10 gvanrossum

so perhaps instead of an API for getting/setting a global "policy", we could have an API for getting/setting a global "loop factory".

I disagree, that's the most painful part of the policy system that I'm looking to deprecate here in favor of passing an explicit loop_factory to asyncio.Runner. The behavior of Runner should be to pick the best event loop by default, if people need to change the behavior of Runner they should pass an explicit factory, if they really need to patch this behavior globally for the whole process they can use a monkey patch

graingert avatar Oct 07 '22 09:10 graingert

that's the most painful part of the policy system

What exactly is most painful? That there's a global default for something? To me the painful thing is that the policy system is over-engineered, you have to create a class that overrides new_event_loop, instantiate it, and call set_event_loop_policy with the instance. That is just classic Java. You shouldn't need to have to create a class, just a function.

I totally agree that most people should use run() or Runner, but I disagree that we should deprecate all other workflows. To me, Runner is just a convenience class.

gvanrossum avatar Oct 07 '22 14:10 gvanrossum

I discussed this with Yury and he convinced me that we don't need a global loop factory. Instead we should just have a loop_factory=None keyword arg to asyncio.run().

We still have to come up with a way to transition to a world where child watching is per-loop instead of global though.

gvanrossum avatar Oct 07 '22 17:10 gvanrossum

Good news. @1st1 has a simple refactoring of PidfdChildWatcher that makes it independent from the main loop -- just like ThreadedChildWatcher. Once that is merged (PR is forthcoming) we can also merge @kumaraditya303's PR GH-98024, and then we can start deprecating all other child watcher implementations.

We can then also deprecate set_child_watcher() (both the asyncio function and the policy method) and eventually we can move the child watcher out of the policy. There's some hand-waving here because in theory people could subclass the default policy class and override get_child_watcher() to construct their own child watcher -- we'll have to deprecate that too somehow.

But all this builds a road to a world where policies are no longer needed and eventually no longer exist.

gvanrossum avatar Oct 07 '22 18:10 gvanrossum

@gvanrossum I have a PR for Pidfdchildwatcher already https://github.com/python/cpython/pull/94184

graingert avatar Oct 07 '22 18:10 graingert

It would also be good for the child watchers to be responsible for calling the callback on the event loop thread. Currently the callback needs to defensively call call_soon_threadsafe when it's redundant eg the Pidfdchildwatcher

Eg https://github.com/python/cpython/blob/282edd7b2a74c4dfe1bfe3c5b1d30f9c21d554d6/Lib/asyncio/unix_events.py#L227

graingert avatar Oct 07 '22 20:10 graingert

I just read the comments in GH-93453: "Make get_event_loop() an alias of get_running_loop()". This makes me want to go slow with the whole "deprecate policies" part. (I am still fine with deprecating watchers ASAP.)

Maybe we could start by deprecating just set_event_loop_policy(), hence making the policy (eventually) just a global singleton that stores some state (in particular the current thread's loop, even if it's not running)?

gvanrossum avatar Oct 08 '22 04:10 gvanrossum

Let's deprecate child watchers first and then we can think about policy since it will require more discussion.

kumaraditya303 avatar Oct 08 '22 06:10 kumaraditya303

Maybe we can also deprecate set_child_watcher now?

gvanrossum avatar Oct 08 '22 20:10 gvanrossum

I'd like to see set_child_watcher and get_child_watcher deprecated with a private _get_child_watcher added temporarily that returns whatever was set until set/get_child_watcher is removed

graingert avatar Oct 08 '22 20:10 graingert

But we can't guarantee that the private _get_child_watcher is supported as long as set_event_loop_policy can be called. We could of course just check whether it exists on the policy object and call it only if it exists, otherwise call the public API.

Would it be acceptable if set_child_watcher was a no-op that reported a warning? Then the unix event loop could just do its own event handling and never use the policy to get the watcher. (IIUC uvloop already doesn't use the watcher API.)

gvanrossum avatar Oct 08 '22 21:10 gvanrossum

Would it be acceptable if set_child_watcher was a no-op that reported a warning?

Probably not, I think people would still expect to get whatever was set during the deprecation period. Maybe the asyncio subprocess API could just ignore it like uvloop does

graingert avatar Oct 08 '22 21:10 graingert

Probably not, I think people would still expect to get whatever was set during the deprecation period. Maybe the asyncio subprocess API could just ignore it like uvloop does

But then wouldn't they also expect the watcher they set to be used?

Who knows, we need to do some searching. A possible approach would be to deprecate get_child_watcher() and set_child_watcher() but keep their implementation the same, but also stop calling get_child_watcher() in _make_subprocess_transport(). (Arguably if we do this, we should simplify the implementation and always set up a ThreadedChildWatcher in _init_watcher().)

gvanrossum avatar Oct 09 '22 00:10 gvanrossum

Searching for set_child_watcher I found gbulb, a library that integrates the GLib main event loop with asyncio. (I actually found glibcoro first, and its README mentions gbulb.)

The importance of this find is that gbulb defines its own child watcher class that integrates with the GLib event loop. They also have a custom policy that manages this watcher, and (of course) a custom event loop. I have a feeling they really need to use their custom watcher in their event loop, because of how GLib works (although I don't know anything about GLib).

I'm guessing in the long run they can refactor their code to avoid using get/set_child_watcher, but the deprecation might be inconvenient for them. (Then again they override the policy methods so they wouldn't get the deprecation warnings.)

This is the first non-trivial mention of set_event_handler I've found (there are lots of dummies and copies around -- a lot of people somehow emulate asyncio).

gvanrossum avatar Oct 09 '22 03:10 gvanrossum

There's also an intriguing custom watcher in chaperone but this package appears unmaintained (last commits in 2016). It appears a modified clone of FastChildWatcher.

gvanrossum avatar Oct 09 '22 04:10 gvanrossum

We need a better plan for this, here's my plan:

  • [x] Make PidfdChildWatcher the default where supported. https://github.com/python/cpython/pull/98024
  • [x] Deprecate all other child watchers. https://github.com/python/cpython/pull/98089
  • [x] Deprecate all child watcher configuration methods and functions e.g set_child_watcher and get_child_watcher to warn when used. (uvloop does not uses any of this). https://github.com/python/cpython/pull/98215
  • [ ] asyncio ignores set child watcher and instead always uses PidfdChildWatcher or ThreadedChildWatcher.
  • [ ] Custom event loop needs to implement child watching themselves like uvloop rather than relying on child watchers system and policy.
  • [ ] In 3.14 all the config methods will be removed.

kumaraditya303 avatar Oct 09 '22 10:10 kumaraditya303

Are you thinking of doing all of these in 3.12 except the final checkbox? If so we should probably do "asyncio ignores set child watcher and instead always uses PidfdChildWatcher or ThreadedChildWatcher" next, before "Deprecate all child watcher configuration methods and functions ..."

gvanrossum avatar Oct 11 '22 18:10 gvanrossum

I first want to get agreement on whether we should raise DeprecationWarning when overriding the child watcher or just ignore it. I analyzed this with https://cs.github.com/ and it is used mostly to workaround old asyncio bug where ThreadedChildWatcher wasn't used by default instead some other was used. This isn't an issue since about Python 3.8.

kumaraditya303 avatar Oct 12 '22 10:10 kumaraditya303

I think the remaining three tasks cannot be done until 3.14.

gvanrossum avatar Oct 15 '22 23:10 gvanrossum

This is done for 3.12, now onto the policy minefield.

kumaraditya303 avatar Oct 16 '22 16:10 kumaraditya303

FWIW it seems that Jupyter has a legitimate reason to override the default policy (with another one of the predefined ones), see https://github.com/python/cpython/issues/93453#issuecomment-1281414458.

gvanrossum avatar Oct 18 '22 15:10 gvanrossum

FWIW it seems that Jupyter has a legitimate reason to override the default policy (with another one of the predefined ones), see #93453 (comment).

The WindowsSelectorEventLoopPolicy shouldn't be needed with tornado 6.2 where it runs a selector in a background thread so the main event loop can be the ProactorEventLoop

graingert avatar Oct 18 '22 16:10 graingert

We are in dire need of more asyncio experts. @1st1 this isn't urgent but would be nice to have your perspective in time to do this in 3.12.

It seems @1st1 is in favor of deprecating the rest of the policy system: https://twitter.com/1st1/status/1711007413275365590?t=PGAYW5447_2JZdgiIsu6eQ&s=19 can we do this for 3.13?

graingert avatar Oct 11 '23 14:10 graingert

I am fine with that. But I am not someone with a lot of time for it. In 3.13 all we need is some deprecations. Can you help with that?

gvanrossum avatar Oct 11 '23 14:10 gvanrossum

@gvanrossum I can definitely help adding deprecations!

graingert avatar Oct 11 '23 15:10 graingert