Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Convert inlineCallbacks to async/await #7988

Closed
19 of 48 tasks
clokep opened this issue Jul 30, 2020 · 5 comments
Closed
19 of 48 tasks

Convert inlineCallbacks to async/await #7988

clokep opened this issue Jul 30, 2020 · 5 comments
Labels
T-Task Refactoring, removal, replacement, enabling or disabling functionality, other engineering tasks. Z-Help-Wanted We know exactly how to fix this issue, and would be grateful for any contribution

Comments

@clokep
Copy link
Member

clokep commented Jul 30, 2020

There's a desire to convert code that uses inlineCallbacks to async / await since it makes stack traces nicer and helps with profiling (e.g. #7670).

Some progress on this can be tracked at https://patrick.cloke.us/areweasyncyet/

I've broken down the Synapse code into modules for tracking of this work:

@clokep clokep self-assigned this Jul 30, 2020
@clokep
Copy link
Member Author

clokep commented Jul 30, 2020

I should mention that if anyone is interested in helping out here they can poke me here or in #synapse-dev:matrix.org

@clokep clokep added the Z-Help-Wanted We know exactly how to fix this issue, and would be grateful for any contribution label Jul 30, 2020
@clokep
Copy link
Member Author

clokep commented Aug 31, 2020

To give a bit of a higher level update here:

  • Most non-test modules have been converted to use async/await, there's also a few PRs outstanding (notably for runInteraction).
  • Most tests need to be switched from using defer.inlineCallbacks style to use get_success().
  • There's probably some documentation that needs to be updated.
  • It would be nice to see if some ensureDeferred calls could be removed (since that seems to make inlineCallback objects).

@clokep
Copy link
Member Author

clokep commented Sep 14, 2020

The current work on this is done, but I wanted to give another update:

  • The vast majority of the code has been converted from inlineCallbacks to async.
  • There are a few remaining bits that could be converted, it is probably worth doing those, but at a much lower effort than we spent so far on this.
  • Generally methods that return an awaitable are now async and await on other calls instead of passing through awaitables from other calls.
  • There are remaining uses of ensureDeferred, which has the unfortunate side-effect of converting async to inlineCallbacks.
  • The tests need a lot of updating, in particular the use of tests being defined as inlineCallbacks and then using yield should be replaced with self.get_success, but this requires the test case inheriting from HomeserverTestCase, which not all tests use. This would be a great way to contribute if someone is interested!

@clokep
Copy link
Member Author

clokep commented Jun 10, 2021

It came up that it was never really written down why we were doing this or how we were doing this. I'll attempt to document what I remember:

Why convert inlineCallbacks to async/await:

  • inlineCallbacks mangle stack traces pretty badly, async/await should improve the stack traces for profiling and exceptions (e.g. in Sentry).
  • async/await is more modern looking and better understood by people who don't have deep knowledge of Twisted.
  • Since async/await is a language feature it has better support from other packages, static analyzers, tools, etc.
  • async functions can provide better type hints for return values.
  • There's some thought that this might have a small performance boost (although I don't think we ever saw any).

Rules for convert from inlineCallbacks to async/await:

The result of calling an async function is an Awaitable, the result of calling an inlineCallbacks function is a Deferred. async functions use await internally to wait for another Awaitable, inlineCallbacks use yield internally to wait for another Deferred.

  • You can await a Deferred (since it is also an Awaitable).
  • You cannot yield an Awaitable.
  • You can convert an Awaitable into a Deferred via defer.ensureDeferred.
  • Note that you can yield on a non-Deferred (and it just immediately continues), but calling await on a non-Awaitable is a runtime error.
  • Twisted APIs still expect Deferreds.

Methodology for converting from inlineCallbacks to async/await:

Since you can await a Deferred the easiest way to do this is to start at the outer layers and work inward. By doing this you end up with async functions which call into code which returns Deferreds, but this is fine. For Synapse we converted things via:

  1. The REST layer.
  2. The handler layer.
  3. The database layer.

In order to avoid doing an entire layer at once you want to start with the modules which are called into the least (preferably only via the layer above!). If there are other callers which have not yet been converted, the call-site is modified to wrap the returned Awaitable with ensureDeferred.

The REST layer needed some magic since this needs to integrate into Twisted, see _AsyncResource and sub-classes, in particular this:

  1. Overrides render (which is a Twisted API from IResource).
    1. Calls the async function with defer.ensureDeferred to ensure it gets scheduled with the reactor.
    2. Returns NOT_DONE_YET so that Twisted doesn't close the connection.
  2. It then searches for a method called _async_render_<HTTP METHOD> and calls it.
  3. If that is also async it awaits it.
  4. Finally it sends the response.

We also had many places which were undecorated functions which returned a Deferred via calling something else. While doing this conversion we updated those to be async and then internally await the called function, for clarity.

This also involved updating the tests to match the type as well (i.e. if a function was made async and we mock that function somewhere, the mock should also be async).

@erikjohnston erikjohnston added the T-Task Refactoring, removal, replacement, enabling or disabling functionality, other engineering tasks. label Jul 26, 2021
@clokep
Copy link
Member Author

clokep commented Aug 10, 2021

As this isn't actively being worked on I'm not sure having a tracking issue for this makes sense anymore. The https://patrick.cloke.us/areweasyncyet/ page was updated as of yesterday (and is easy enough to keep up-to-date).

I think most of the remaining bits are converting the tests to use HomeserverTestCase + get_success instead of marking the test as inlineCallbacks and yield-ing each Deferred. This is tedious, although not particularly hard. It also isn't particularly urgent since the gains from it are minor.

I think most of the remaining uses in the synapse package probably need to be kept in order to interface with Twisted APIs, although there might be a few more that could go away.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
T-Task Refactoring, removal, replacement, enabling or disabling functionality, other engineering tasks. Z-Help-Wanted We know exactly how to fix this issue, and would be grateful for any contribution
Projects
None yet
Development

No branches or pull requests

2 participants