-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Use duet for asynchrony, in particular for Collector #4009
Conversation
Please fix the failing tests, then re-request review. |
Hm, we don't have a version of duet for 3.6, and it'd be quite hard to do because of some asnyc/await language features that landed in 3.7. @balopat, what is the plan for dropping python 3.6 support? |
This is blocked on #3574, which is blocked on google3 having their default python runtime upgraded to 3.7. |
One idea: We could add a cirq-duet module that would use duet for the collector. WDYT, @maffoo? |
Do we have an update on timeline for Python 3.7+? |
Actually, @fedimser is working on adding python 3.6 support to duet (google/duet#23) which will let us move forward with these async changes without waiting for 3.7 support in colab etc. |
This is ready for review. @balopat, @mpharrigan |
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 think the actual code changes in this PR are very straightforward. I asked some clarifying questions and posed some nits.
cirq-core/cirq/conftest.py
Outdated
f'{pyfuncitem._obj.__name__} is async but not ' | ||
f'decorated with "@pytest.mark.asyncio".' | ||
f'{pyfuncitem._obj.__name__} is a bare async function. ' | ||
f'Should be decorated with "@duet.sync".' |
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.
nit: It should
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.
Done.
cirq-core/cirq/testing/__init__.py
Outdated
@@ -14,10 +14,6 @@ | |||
|
|||
"""Utilities for testing code.""" | |||
|
|||
from cirq.testing.asynchronous import ( | |||
asyncio_pending, |
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 is technically a breaking change. I suspect no one externally has been using our async testing helper code? but we should make a note of this for the release
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.
Brought this back since it's needed to test the work pool, which is now deprecated but not removed.
cirq-core/cirq/work/collector.py
Outdated
""" | ||
pool = work_pool.CompletionOrderedAsyncWorkPool() | ||
results = duet.AsyncCollector[Tuple[CircuitSampleJob, 'cirq.Result']]() |
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.
what is going on here? Is this a form of type annotation I've never seen before or does this actually change the object that has been created?
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.
The AsyncCollector
class derives from typing.Generic
, which among other things implements __getitem__
on the class to allow specifying generic type params like this. It doesn't actually create a new class but rather serves as a hint to the type checker. This could also be written as a type annotation, at the cost of some repetition:
results: duet.AsyncCollector[Tuple[CircuitSampleJob, 'cirq.Result']] = duet.AsyncCollector()
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.
ok, glad it's just a type hint. my slight preference would be for the type annotation at the cost of some repetition but I defer to you
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.
Switched to type annotation. It still just fits on one line :-)
if not job_error: | ||
results.error(error) | ||
job_error = error | ||
else: |
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.
what is try .. else? why not put results.add inside the try?
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.
The else
clause executes if the try
block does not raise any exceptions. I wanted to make clear that the except
clause is only catching exceptions from the await call. If results.add
were part of the try
clause then the except would also handle errors from that; results.add
shouldn't raise errors, but this is just to be safe.
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 explaining
from typing import Optional, Awaitable, Union | ||
|
||
|
||
class CompletionOrderedAsyncWorkPool: |
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 seems like something someone may be relying on in cirq user land?
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.
It's certainly possible. I can restore this and deprecate it instead.
if not job_error: | ||
results.error(error) | ||
job_error = error | ||
else: |
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 explaining
Check out this pull request on See visual diffs & provide feedback on Jupyter Notebooks. Powered by ReviewNB |
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.
In general, it might be good to put more line comments in where duet is used, since it will be unfamiliar to the majority of developers, but I don't have any other objections to pulling this in.
This switches async sampler methods to use the duet library (https://github.com/google/duet). Duet is "reentrant" in that it supports nested invocations of the event loop (duet.run). This differs from most other python async libraries which are not natively reentrant (without patching as in the case of nest-asyncio for asyncio). Re-entrancy makes it possible to refactor code incrementally to use duet whereas non-reentrant libraries typically require more global changes. The asyncio-based work pool and testing utilities are deprecated and will be removed in a future release. Review: @mpharrigan, @dstrain115
This switches async sampler methods to use the duet library (https://github.com/google/duet). Duet is "reentrant" in that it supports nested invocations of the event loop (duet.run). This differs from most other python async libraries which are not natively reentrant (without patching as in the case of nest-asyncio for asyncio). Re-entrancy makes it possible to refactor code incrementally to use duet whereas non-reentrant libraries typically require more global changes. The asyncio-based work pool and testing utilities are deprecated and will be removed in a future release. Review: @mpharrigan, @dstrain115
We are planning to switch to duet for supporting async code, with one of the reasons being that it is "reentrant" and supports nested invocations of the event loop (
duet.run
). We don't yet have much async code in cirq, but do use it forCollector
andPauliSumCollector
and this can lead to problems if running in a context where the asyncio event loop is already running (#4004). The solution proposed there is to patch asyncio to make it reentrant, using thenest-asyncio
library, but this seems like something we should not rely on. Instead, I propose that we make the switch to duet.The main question I have is whether we need to continue to support
Collector.collect_async
andSampler.*_async
methods being asyncio coroutines, or whether we can just switch them to be duet coroutines. In this PR as it stands I've just switched things, but if we want to support asyncio we would need to name the duet coroutine functions differently and then wrap them into an asyncio-compatible form for the existing methods.