Frequently Asked Questions

What’s it for?

Toro makes it easy for Tornado coroutines–that is, functions decorated with gen.coroutine–to coordinate using Events, Conditions, Queues, and Semaphores. Toro supports patterns in which coroutines wait for notifications from others.

Why the name?

A coroutine is often called a “coro”, and a library of primitives useful for managing coroutines is called “coros” in Gevent and “coro” in Shrapnel. So I call a library to manage Tornado coroutines “toro”.

Why do I need synchronization primitives for a single-threaded app?

Protecting an object shared across coroutines is mostly unnecessary in a single-threading Tornado program. For example, a multithreaded app would protect counter with a Lock:

import threading

lock = threading.Lock()
counter = 0

def inc():
    lock.acquire()
    counter += 1
    lock.release()

This isn’t needed in a Tornado coroutine, because the coroutine won’t be interrupted until it explicitly yields. Thus Toro is not designed to protect shared state.

Instead, Toro supports complex coordination among coroutines with The Wait / Notify Pattern: Some coroutines wait at particular points in their code for other coroutines to awaken them.

Why no RLock?

The standard-library RLock (reentrant lock) can be acquired multiple times by a single thread without blocking, reducing the chance of deadlock, especially in recursive functions. The thread currently holding the RLock is the “owning thread.”

In Toro, simulating a concept like an “owning chain of coroutines” would be over-complicated and under-useful, so there is no RLock, only a Lock.

Has Toro anything to do with Tulip?

Toro predates Tulip, which has very similar ideas about coordinating async coroutines using locks and queues. Toro’s author implemented Tulip’s queues, and version 0.5 of Toro strives to match Tulip’s API.

The chief differences between Toro and Tulip are that Toro uses yield instead of yield from, and that Toro uses absolute deadlines instead of relative timeouts. Additionally, Toro’s Lock and Semaphore aren’t context managers (they can’t be used with a with statement); instead, the Futures returned from Lock.acquire() and Semaphore.acquire() are context managers:

>>> from tornado import gen
>>> import toro
>>> lock = toro.Lock()
>>>
>>> @gen.coroutine
... def f():
...    with (yield lock.acquire()):
...        assert lock.locked()
...
...    assert not lock.locked()