Skip to content

Commit 356e36b

Browse files
committed
edits
Change-Id: I8d3521c92d400ecb6157b358c2c063ea906e77c4
1 parent e734159 commit 356e36b

File tree

1 file changed

+69
-45
lines changed

1 file changed

+69
-45
lines changed

docs/build/synopsis.rst

+69-45
Original file line numberDiff line numberDiff line change
@@ -149,13 +149,16 @@ invoke non-blocking functions.**
149149
Detailed Breakdown
150150
==================
151151

152-
The whole approach of awaitlet is overall a little bit of a "dark art". It
152+
The whole approach of awaitlet is overall a little bit of a "dark art" (though
153+
actually less "dark" than what gevent and eventlet
154+
have done for decades). It
153155
takes a standard and pretty well known part of Python, the ``asyncio``
154156
library, and adds some syntactical helpers that were not intended to be part
155157
of asyncio itself. Inspired by libraries like gevent and eventlet, awaitlet
156158
makes use of greenlet in a similar way as those libraries do, but then makes
157159
use of asyncio for non-blocking primitives, rather than going through the
158-
effort of creating its own the way gevent and eventlet do.
160+
effort of creating its own and often needing to monkeypatch them into the standard
161+
library the way gevent and eventlet do.
159162

160163
The :func:`.async_def` function call is an awaitable that when invoked,
161164
internally starts up a greenlet that can be used to "context switch" to
@@ -172,9 +175,9 @@ other greenlets anywhere within the execution of that greenlet::
172175

173176
return await my_awaitable
174177

175-
Above, the ``send_receive_logic()`` function is called within a greenlet, but
176-
not before first before we enter into an actual async def that's behind
177-
the scenes::
178+
Above, the ``send_receive_logic()`` function is called within a greenlet that
179+
itself links to a parent greenlet that's local to the :func:`.async_def`
180+
callable (this is the normal way that greenlet works)::
178181

179182
async def async_def(
180183
fn: Callable[..., _T],
@@ -184,70 +187,91 @@ the scenes::
184187
) -> _T:
185188
"""Runs a sync function ``fn`` in a new greenlet."""
186189

187-
# make a greenlet.greenlet with the given function
190+
# make a greenlet.greenlet with the given function.
191+
# getcurrent() is the parent greenlet that is basically where we
192+
# are right now in the function
188193
context = _AsyncIoGreenlet(fn, greenlet.getcurrent())
189194

190-
# switch into it (start the function)
195+
# switch into the new greenlet (start the function)
191196
result = context.switch(*args, **kwargs)
192197

193198
# ... continued ...
194199

195-
Then, whenever some part of ``send_receive_logic()`` or some sub-function within
196-
it calls upon :func:`.awaitlet`, that goes back into awaitlet's greenlet code
197-
and uses ``greenlet.switch()`` to **context switch** back out into the behind-the-scenes
198-
async function, below illustrated in a simplified form of the actual code::
200+
When this line of code is first called::
199201

200-
async def async_def(
201-
fn: Callable[..., _T],
202-
*args: Any,
203-
assert_await_occurs: bool = False,
204-
**kwargs: Any,
205-
) -> _T:
206-
"""Runs a sync function ``fn`` in a new greenlet."""
202+
# switch into the new greenlet (start the function)
203+
result = context.switch(*args, **kwargs)
207204

208-
# make a greenlet.greenlet with the given function
209-
context = _AsyncIoGreenlet(fn, greenlet.getcurrent())
205+
It runs the given function, and blocks until the function is complete.
206+
However, within the function (which is our ``send_receive_logic()`` call),
207+
that function can call upon Python awaitables using :func:`.awaitlet`.
208+
:func:`.awaitlet` looks like this::
210209

211-
# switch into it (start the function)
212-
result = context.switch(*args, **kwargs)
210+
def awaitlet(awaitable: Awaitable[_T]) -> _T:
211+
"""Awaits an async function in a sync method."""
213212

214-
# we're back! is the context not dead ? (e.g. the funciton has more
215-
# code to run?)
216-
while not context.dead:
217-
# wait for a coroutine from awaitlet and then return its
218-
# result back to it.
219-
value = await result
213+
current = greenlet.getcurrent()
214+
return current.parent.switch(awaitable)
220215

221-
# then switch back in! (in reality there's error handling here also)
222-
result = context.switch(value)
216+
That is, it does nothing but context switch **back to the parent greenlet**,
217+
which means back outside of the ``context.switch()`` that got us here.
218+
The returned value is a real Python awaitable. So inside
219+
of the ``async_def()`` function, we check that the inner function is not
220+
complete yet, we then assume the result must be an awaitable, and we await it
221+
on behalf of our hosted function - remember, we're in a real ``async def``
222+
at this level::
223223

224-
When this line of code is first called::
224+
# switch into the new greenlet (start the function)
225+
result = context.switch(*args, **kwargs)
225226

226-
# switch into it (start the function)
227+
# loop for the function not done yet
228+
while not context.dead:
229+
# await on the result that we expect is awaitable
230+
value = await result
231+
232+
With the awaitable completed, we send the result of
233+
the awaitable **back into the hosted function and context switch back**::
234+
235+
# switch into the new greenlet (start the function)
227236
result = context.switch(*args, **kwargs)
228237

229-
It blocks while our function runs. Only when our function exits or
230-
calls :func:`.awaitlet` do we hit the next line. If the function calls
231-
:func:`.awaitlet`, awaitlet looks like this::
238+
# loop for the function not done yet
239+
while not context.dead:
240+
# await on the result that we expect is awaitable
241+
value = await result
242+
243+
# pass control back into the function, with the return value
244+
# of the awaitable
245+
result = context.switch(value)
246+
247+
The ``value`` passed in becomes the **return value of the awaitlet() call**::
232248

233249
def awaitlet(awaitable: Awaitable[_T]) -> _T:
234-
"""Awaits an async function in a sync method."""
250+
# ...
235251

236-
current = greenlet.getcurrent()
237252
return current.parent.switch(awaitable)
238253

239-
That is, it does nothing but greenlet switch **back to the parent greenlet**,
240-
which means back outside of the ``context.switch()`` that got us here.
241-
The returned value is a real Python awaitable. So inside
242-
of the ``async_def()`` funciton, we await it on behalf of our hosted function::
254+
and we are then back in the hosted function with an awaitable having proceeded
255+
and its return value passed back from the :func:`.awaitlet` call.
256+
257+
The loop continues; each time ``context.dead`` is False, we know that
258+
``result`` is yet another Python awaitable. Once ``context.dead`` is
259+
True, then we know the function completed; we return the result!
243260

261+
.. sourcecode::
262+
263+
# switch into it (start the function)
264+
result = context.switch(*args, **kwargs)
265+
266+
# loop for the function not done yet
244267
while not context.dead:
245268
# await on the result that we expect is awaitable
246269
value = await result
247270

248-
We send the result of the awaitable **back into the hosted function and
249-
context switch back**::
271+
result = context.switch(value)
272+
273+
# no more awaits; so this is the result!
274+
return result
250275

251-
result = context.switch(value)
252276

253-
Minus some more robustness details, that's the whole thing!
277+
Minus error handling and some other robustness details, that's the whole thing!

0 commit comments

Comments
 (0)