-
Notifications
You must be signed in to change notification settings - Fork 17
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
♻️ Make Process.run
async
#272
Conversation
Codecov ReportPatch coverage:
Additional details and impacted files@@ Coverage Diff @@
## support/0.21.x #272 +/- ##
==================================================
- Coverage 90.82% 90.82% -0.00%
==================================================
Files 21 21
Lines 2973 2972 -1
==================================================
- Hits 2700 2699 -1
Misses 273 273
☔ View full report in Codecov by Sentry. |
0b30f35
to
ff1b699
Compare
@@ -225,7 +227,7 @@ async def execute(self) -> State: # type: ignore # pylint: disable=invalid-over | |||
try: | |||
try: | |||
self._running = True | |||
result = self.run_fn(*self.args, **self.kwargs) | |||
result = await self.run_fn(*self.args, **self.kwargs) |
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.
Maybe the better way to do this is using https://docs.python.org/3/library/inspect.html#inspect.isawaitable
🤔 (see e.g. https://textual.textualize.io/blog/2023/03/15/no-async-async-with-python/)
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 mentioned in the blog post that @chrisjsewell mentioned in https://github.com/aiidateam/plumpy/pull/272/files#r1257558025 with await_me_maybe
would be the better design IMO. I think but we already do it the same way in the Process
class so this is at least consistent. Maybe we can instead improve the doc a bit on the places where we use ensure_coroutine
. Something like
We wrap `run_fn` to a coroutine so we can apply await on it, even it if it was not a coroutine in the first place. This allows the same usage of async and non-async function with the await syntax while not changing the program logic.
At least this is my understanding why we do this. (Also would add something like this to _run_task in class Process)
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 @agoscinski , I've added your comment.
Thanks @chrisjsewell . If we can test this in an integrated way with Then again, given that 0.22 has already been released, if we are going to bother with proper semver, I guess we should really not merge it in there either and release it with 0.23 instead 🤔 |
Yeh so I rebased it onto v0.21, just so I could get aiidateam/aiida-core#6079 to work (without PR5732) Here I mention that I haven't actually implemented any (new) asyncio behaviour for the local/ssh transports, as this may take some more thinking, for a "production ready" to ensure it doesn't create any new problems (e.g. limiting how many file transfers can be running at any one time) I'm sure for ssh (or Firecrest) it should not be too difficult to show a toy example of a speed up. Obviously it is very dépendant on the type of calculations you are running though, I guess most suited if you are uploading/downloading many 1000s of files and/or very large files (and there is also different async strategies for both) |
For sure getting PR5732 through would be nice 👍 |
@@ -225,7 +227,7 @@ async def execute(self) -> State: # type: ignore # pylint: disable=invalid-over | |||
try: | |||
try: | |||
self._running = True | |||
result = self.run_fn(*self.args, **self.kwargs) | |||
result = await self.run_fn(*self.args, **self.kwargs) |
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 mentioned in the blog post that @chrisjsewell mentioned in https://github.com/aiidateam/plumpy/pull/272/files#r1257558025 with await_me_maybe
would be the better design IMO. I think but we already do it the same way in the Process
class so this is at least consistent. Maybe we can instead improve the doc a bit on the places where we use ensure_coroutine
. Something like
We wrap `run_fn` to a coroutine so we can apply await on it, even it if it was not a coroutine in the first place. This allows the same usage of async and non-async function with the await syntax while not changing the program logic.
At least this is my understanding why we do this. (Also would add something like this to _run_task in class Process)
I think it is okay, just a bit doc would be nice |
super().__init__(process) | ||
assert run_fn is not None | ||
self.run_fn = run_fn | ||
self.run_fn = ensure_coroutine(run_fn) |
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 am quite worried about this. I think this run_fn will become the continue_fn
when it is recover from the Waiting state, which means all such xx_fn
should be coroutines along the way. I need to take a close look to see how this change will make things different. Will do it next week.
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.
@chrisjsewell suggested to do await_me_maybe
https://github.com/aiidateam/plumpy/pull/272/files#r1257558025 that would avoid this but I did not read about any technical reason do it. The blog post he links is only arguing for it cause of cleanness of the code. I assumed wrapping a blocking function is like writing async
to the function, it then is just executed like a blocking function when used with await. So
def two():
# blocking function
time.sleep(1)
print("Two")
async def blocking():
print("One")
two()
print("Three")
async def also_blocking():
# does the same as blocking
coro = ensure_coroutine(two)
print("One")
await coro()
print("Three")
async def not_blocking():
coro = ensure_coroutine(two)
print("One")
coro() # Runtime warning
print("Three")
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 assumed wrapping a blocking function is like writing async to the function, it then is just executed like a blocking function when used with await.
That's true, the blog post mentions the "maybe await" pattern is mostly for async framework that can support the downstream app can write block function. If the operation is block function, then it is run in block manner.
I think this run_fn will become the continue_fn when it is recover from the Waiting state, which means all such xx_fn should be coroutines along the way.
From aiida-core point of view, this never happened, since the continue_fn
is never set in the aiida-core
Waiting class. The def run
is used to create the initial Created
state and used to transfer the aiida Process
into its own Waiting
state(s).
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.
If the goal of this PR is for aiidateam/aiida-core#6626 to support async SSH operations. I think it should not be the 'running' state but the 'waiting' state run
method should be async, no? I am not sure, will take a close look with the thing I mentioned in the comment above.
I am wrong, the LocalTransport do not need to be async but it calls |
I think it is good to go, as mentioned by @agoscinski, might be better to add some doc here and there. This is meanwhile is a backward incompatible change for |
Please don't merge in this state though. There is something severly wrong with the branch as it contains many commits that are already on the master branch. Please fix the branch first |
@sphuber, I made some tests, overall looks good. |
Thanks everyone |
Co-authored-by: Ali <[email protected]>
Co-authored-by: Ali <[email protected]>
Co-authored-by: Ali <[email protected]> (cherry picked from commit 4611154)
Co-authored-by: Ali <[email protected]> (cherry picked from commit 4611154)
Co-authored-by: Ali <[email protected]> (cherry picked from commit 4611154)
Co-authored-by: Ali <[email protected]> (cherry picked from commit 4611154)
Co-authored-by: Ali <[email protected]> (cherry picked from commit 4611154)Co-authored-by: Ali <[email protected]>
Co-authored-by: Ali <[email protected]> (cherry picked from commit 4611154) Co-authored-by: Ali <[email protected]>
As discussed with @giovannipizzi, to allow for introducing
async
methods to the AiiDATransport
classes (particularly for uploading and downloading files)The downstream implementation and explanation in aiida-core is here: aiidateam/aiida-core#6079
(Also makes me think, can you also allow for optional
async
Workflow steps this way)