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

Use duet for asynchrony, in particular for Collector #4009

Merged
merged 14 commits into from
Aug 25, 2021
Merged

Conversation

maffoo
Copy link
Contributor

@maffoo maffoo commented Apr 8, 2021

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 for Collector and PauliSumCollector 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 the nest-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 and Sampler.*_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.

@maffoo maffoo requested review from cduck, vtomole and a team as code owners April 8, 2021 22:52
@google-cla google-cla bot added the cla: yes Makes googlebot stop complaining. label Apr 8, 2021
@95-martin-orion
Copy link
Collaborator

Please fix the failing tests, then re-request review.

@maffoo
Copy link
Contributor Author

maffoo commented Apr 15, 2021

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?

@balopat balopat added the status/blocked This PR is blocked on another issue label Apr 27, 2021
@balopat
Copy link
Contributor

balopat commented Apr 27, 2021

This is blocked on #3574, which is blocked on google3 having their default python runtime upgraded to 3.7.
If we want to move forward with duet for the OSS community (I think that this is great!) then we have to think about workarounds until that upgrade is done which might take a year still. A workaround design might mean to offer the current async flow on 3.6 and duet on 3.7 somehow.

@balopat
Copy link
Contributor

balopat commented Jul 9, 2021

This is blocked on #3574, which is blocked on google3 having their default python runtime upgraded to 3.7.
If we want to move forward with duet for the OSS community (I think that this is great!) then we have to think about workarounds until that upgrade is done which might take a year still. A workaround design might mean to offer the current async flow on 3.6 and duet on 3.7 somehow.

One idea:

We could add a cirq-duet module that would use duet for the collector.
People who have python 3.7+ could use that module, the ones using 3.6 can still use the old collector, on which we could add even a deprecation warning...

WDYT, @maffoo?

@mpharrigan
Copy link
Collaborator

Do we have an update on timeline for Python 3.7+?

@maffoo
Copy link
Contributor Author

maffoo commented Jul 12, 2021

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.

@maffoo
Copy link
Contributor Author

maffoo commented Jul 13, 2021

This is ready for review. @balopat, @mpharrigan

@maffoo maffoo removed the status/blocked This PR is blocked on another issue label Jul 13, 2021
@CirqBot CirqBot added size: XL lines changed >1000 size: L 250< lines changed <1000 and removed size: XL lines changed >1000 labels Aug 12, 2021
Copy link
Collaborator

@mpharrigan mpharrigan left a 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.

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".'
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: It should

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@@ -14,10 +14,6 @@

"""Utilities for testing code."""

from cirq.testing.asynchronous import (
asyncio_pending,
Copy link
Collaborator

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

Copy link
Contributor Author

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.

"""
pool = work_pool.CompletionOrderedAsyncWorkPool()
results = duet.AsyncCollector[Tuple[CircuitSampleJob, 'cirq.Result']]()
Copy link
Collaborator

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?

Copy link
Contributor Author

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()

Copy link
Collaborator

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

Copy link
Contributor Author

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:
Copy link
Collaborator

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?

Copy link
Contributor Author

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.

Copy link
Collaborator

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:
Copy link
Collaborator

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?

Copy link
Contributor Author

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:
Copy link
Collaborator

Choose a reason for hiding this comment

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

thanks for explaining

@review-notebook-app
Copy link

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

Copy link
Collaborator

@dstrain115 dstrain115 left a 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.

@maffoo maffoo merged commit de31ee9 into master Aug 25, 2021
@maffoo maffoo deleted the u/maffoo/duet branch August 25, 2021 16:25
rht pushed a commit to rht/Cirq that referenced this pull request May 1, 2023
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
harry-phasecraft pushed a commit to PhaseCraft/Cirq that referenced this pull request Oct 31, 2024
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla: yes Makes googlebot stop complaining. size: L 250< lines changed <1000
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants