Trying to Understand asimpy

Posted

As a follow-on to yesterday’s post, I’m trying to figure out why the code in the tracing-sleeper branch of https://github.com/gvwilson/asimpy actually works. The files that actually matter for the moment are:

I’ve added lots of print statements to sleep.py and the three files in the package that it relies on. To run the code:

$ git clone git@github.com:gvwilson/asimpy
$ cd asimpy
$ uv venv
$ source .venv/bin/activate
$ uv sync
$ python examples/sleep.py

Inside src/asimpy/actions.py there’s a class called BaseAction that the framework uses as the base of all awaitable objects. When a process does something like sleep, or try to get something from a queue, or anything else that requires synchronization, it creates an instance of a class derived from BaseAction (such as the _Sleep class defined in src/asimpy/environment.py).

Now, if I understand the protocol correctly, when Python encounters ‘await obj’, it does the equivalent of:

iter = obj.__await__()  # get an iterator
try:
    value = next(iter)  # run to the first yield
except StopIteration as e:
    value = e.value     # get the result 

After stripping out docs, typing, and print statements, BaseAction’s implementation of __await__() is just:

def __await__(self):
    yield self
    return None

Looking at the printed output, both lines are always executed, and I don’t understand why. Inside Environment.run(), the awaitable is advanced by calling:

awaited = proc._coro.send(None)

(where proc is the object derived from Process and proc._coro is the iterator created by invoking the process’s async run() method). My mental model is that value should be set to self because that’s what the first line of __await__() yields; I don’t understand why execution ever proceeds after that, but my print statements show that it does.

And I know execution must proceed because (for example) BaseQueue.get() in src/asimpy/queue.py successfully returns an object from the queue. This happens in the second line of that file’s _Get.__await__(), and the more I think about this the more confused I get.

I created this code by imitating what’s in SimPy, reasoning through what I could, and asking ChatGPT how to fix a couple of errors late at night. It did all make sense at one point, but as I try to write the tutorial to explain it to others, I realize I’m on shaky ground. ChatGPT’s explanations aren’t helping; if I find something or someone that does, I’ll update this blog post.