Manjusaka

Manjusaka

The Road to async/await

The Road to async/await#

After reading Mr. Peng's article This Python, I have many thoughts and I want to share them.

First of all, our company is relatively daring in trying new things in China. The most direct manifestation is that we will promptly follow the iteration of relevant basic services in the community and dare to try new things. Well, from June last year until now, we have been implementing async/await for a long time online, and we have introduced new frameworks like Sanic, but we have to say goodbye to async/await for now.

Why did we choose async/await?#

It is related to the specific scenarios of our team. A considerable part of the scenarios in our team involves requesting data from different sub-services based on different URLs, and then combining them for further unified processing. At this time, the traditional synchronous method becomes helpless as the data sources become more and more diverse.

We had several options at that time:

  1. Maintain a process/thread pool and use generic processes/threads to handle requests.

  2. Use a third-party coroutine+EventLoop solution like Gevent.

  3. Use the async/await + asyncio combination.

First of all, we ruled out option 1 because it was too heavy. Option 2 was also temporarily ruled out at the beginning because we were afraid of the seemingly unreliable monkey-patching method.

588d9201f5b14873b0504e3a6ab7b402

So we happily chose option 3, the async/await + asyncio combination.

In fact, the initial effect was wonderful. However, later on, we found that this operation was actually eating shit QwQ.

Farewell to async/await#

Why did we give up async/await?#

Actually, it's a few old questions.

1. Contagiousness at the code level#

Python's official coroutine implementation is actually based on the modification of generators using yield/yield from. This means that you need to start at the entry point and gradually follow the async/await method for use. In the same project, having synchronous/asynchronous code everywhere is, believe me, a kind of disaster in terms of maintenance.

2. Ecosystem and compatibility#

The current compatibility of async/await is really daunting. Currently, the scope of async/await is limited to Pure Python Code. Here's a problem: many C Extensions we use in the project, such as mysqlclient, are not covered by async/await.

At the same time, currently, the ecosystem around async/await is really a very, very big problem. It can be said to be full of bugs that no one fixes. For example, the issue of link leakage in aiohttp for https links. Another example is the messy design structure of Sanic.

When we investigate a new technology for a production project, we often focus on whether a new thing can cover our services with existing technologies and whether its surroundings can meet our daily needs. Currently, the async/await ecosystem cannot meet these requirements.

3. Performance issues#

Currently, asyncio, which was proposed by PEP 3156, is the officially recommended event loop for async/await. However, the official implementation currently lacks many things. For example, the issue of link leakage in aiohttp for https links can be traced back to the implementation of asyncio's SSL. Therefore, when we use it, we often choose to use a third-party loop. Currently, the mainstream implementation of third-party loops is based on libuv/libev. In this way, its performance is comparable to Gevent, or even lower (after all, Greenlet avoids the overhead of maintaining PyFrameObject).

So, for the sake of our hair, we will gradually retire async/await from our online code. By the end of this year at the latest, we will complete the process of removing async/await.

What is our alternative?#

Currently, we plan to use Gevent as an alternative (yes, it's really good).

The reasons are simple:

  1. It is currently mature and has no significant bugs.

  2. The surrounding development is mature. For Pure Python Code, we can easily migrate existing code by Monkey-Patching, and for C Extensions, we have Greenify, which has been internally validated by Douban, as a solution.

  3. The underlying Greenlet provides corresponding APIs, which can easily trace the context of coroutine switching when necessary.

Other things I want to say about async/await#

First of all, async/await is a good thing, but it is not practical now. This actually depends on the community's further exploration of its usage.

Speaking of this, many people may ask me, what do you think of things like ASGI and Django Channels?

First of all, we need to clarify that ASGI was not designed for async/await. Its initial design idea was to solve the problem that the PEP333/PEP3333 WSGI protocol is inadequate when facing increasingly complex network protocol models. Django Channels is also a solution to this problem, and it implements ASGI (initially to solve Websockets?). This set of solutions does solve many problems, such as the easy implementation of WebSocket Broadcast in Django Channel 2.0. However, they are not closely related to async/await.

At PyCon 2018, the Core of the Django team introduced that Channel 2.0 added support for async/await. In the future, Django may also add corresponding support. However, the problem is that once async/await is used, the current overall ecosystem is still the most worrying and weakest point.

So, hello async/await, goodbye async/await!

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.