-
-
Notifications
You must be signed in to change notification settings - Fork 180
Submitting a coroutine to an asyncio event loop #273
Conversation
Tornado's chain_future is equivalent to your |
@ajdavis All right, let me know if you have other suggestions |
Can you fill out the PSF contributor form at https://www.python.org/psf/contrib/contrib-form/ ? This is a one-time chore. :-) |
"""Copy state from a future to another future. | ||
Compatible with both asyncio.Future and concurrent.futures.Future.""" | ||
# Future._copy_state code should probably move in here | ||
return Future._copy_state.__get__(destination)(source) |
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.
Can you refactor this so that the actual code is here and Future._copy_state invokes this? Or even better make it just a helper function -- the one place where Future._copy_state is invoked you can just change it to call the function.
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.
(Actually I see you removed the direct call site. So you can just move the code here.)
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.
Thanks for the review!
I just pushed a commit to make _copy_state
a static method, is it ok? I had to update the tests and I noticed this line:
# Test the internal _copy_state method since it's being directly
# invoked in other modules.
I couldn't find any other usage in asyncio
, is another project using this method?
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.
That comment may be old, but it's best to have an explicit test for this function anyway.
Anyway, I hate static methods, can you just make it a function?
Thanks! This looks promising. I've left a bunch of refactoring suggestions. Let me know when you're ready for another code review. |
All right, it should look better now! |
@@ -360,25 +360,6 @@ def set_exception(self, exception): | |||
|
|||
# Truly internal methods. |
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.
This comment is obsolete; just delete it.
I like How about
It was requested by someone on bugs.python.org to better document
|
I'll let you guys settle on a name, I changed it to
All right, I removed |
return | ||
assert not destination.done() | ||
if source.cancelled(): | ||
destination.cancel() |
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.
This actually doesn't work for a concurrent Future. Its cancel() method is a no-op once the state is RUNNING (which it is once set_running_or_notify_cancel() returns).
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.
You're right, this implementation of _copy_state
is trickier than what I expected... I came up with this:
def _copy_state(source, destination):
"""Copy state from a future to another future.
Compatible with both asyncio.Future and concurrent.futures.Future.
"""
# Check source cancellation
assert source.done()
if source.cancelled():
destination.cancel()
return
# Check destination cancellation
if isinstance(destination, concurrent.futures.Future):
if not destination.set_running_or_notify_cancel():
return
elif destination.cancelled():
return
assert not destination.done()
# Set exception or result
exception = source.exception()
if exception is not None:
destination.set_exception(exception)
else:
result = source.result()
destination.set_result(result)
It should be ok to check the source cancellation before since a cancel
method should never fail, regardless of the future type. I also wrote a new test to cover the case you described.
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.
@gvanrossum
I thought about what you said and separating the two _copy_state
implementations. What about keeping the current implementation of Future._copy_state
and changing _copy_state
to:
def _copy_state(source, destination):
# Use Future._copy_state implementation
if isinstance(destination, Future):
return destination._copy_state(source)
# Implements _copy_state for concurrent.future.Futures
assert source.done()
if source.cancelled():
destination.cancel()
if not destination.set_running_or_notify_cancel():
return
exception = source.exception()
if exception is not None:
destination.set_exception(exception)
else:
result = source.result()
destination.set_result(result)
Pros:
- Each implementation can be maintained separately
- The second part can easily be moved somewhere else if needed
Cons:
- A bit of code duplication
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.
I like this idea. I would go even further -- I would have a _copy_asyncio_future_state_to_concurrent_future()
method (maybe use a shorter name :-) and decide which one to use when registering the callback.
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.
Actually, the source type doesn't matter since the source is done already: the only thing we need to know about it is if it's cancelled, done with a result or done with an exception. This is why _copy_state
fits very well as a method, since the only type that matters is the type of the instance itself. I thought of _set_concurrent_future_state
with the following prototype:
def _set_concurrent_future_state(concurrent, other):
I think it's less confusing and more similar to:
def _copy_state(self, other):
I'll push it in a second, please let me know what you think about it.
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.
Sounds good, but please name the other argument source
.
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.
All right, should I rename it in Future._copy_state
too for consistency?
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, please leave that alone. Consistency is overrated. ;-)
…te and change its prototype
Thanks! I consider this done except for Python docs. I've manually applied your patch and pushed it to the repo. I'm in the middle of committing the patch to the CPython repo. I would love it if you volunteered doc updates for CPython! (Starting with 3.4.) See you in bugs.python.org for that... |
Cross-reference CPython issue: http://bugs.python.org/issue25304 |
This PR follows up on this conversation on the python-ideas mailing list. It is based on the implementation I posted and commented here, but discarding the executor.
This PR includes:
connect_futures
function, to safely connect two futures regardless of their typessubmit_to_loop
function, to submit a coroutine from a thread to a given event loopsubmit_to_loop
(runs in about 15 ms)It also affects
wrap_future
, refactored to useconnect_futures
and avoid code duplication.I tried to gather my changes as much as possible. However, you might want to have a look at:
import asyncio
(in futures.py). I neededensure_future
forsubmit_in_loop
and couldn't import it withfrom . import tasks
(because of circular import)._copy_state
(in futures.py). The code inFuture._copy_state
should probably move in_copy_state
as I pointed out in a comment._schedule
works exactly likeensure_future
but adds an optionaldestination
argument. There could be some refactoring here, though you probably don't want to change theensure_future
prototype.New names and prototypes might also need to be rethought, along with docstrings and type testing. I'm not sure either about how to use the
TestLoop
, though the tests seem to pass properly.