Saturday 5 November 2011

Scheduler deadlock

One common approach to creating tasklets, is to wrap the callable that will run within the newly created tasklet in a function that captures exceptions.

Something like the following:

import traceback

def create_tasklet(f, *args, **kwargs):
    try:
        return f(*args, **kwargs)
    except BaseException, e:
        # Let TaskletExit, SystemExit and KeyboardInterrupt raise up.
        if isinstance(e, SystemExit, KeyboardInterrupt):
            raise e
        traceback.print_exc()
Another exception that is worth handling especially, is RuntimeError. In the case where the last tasklet tries to block, the scheduler will instead raise a RuntimeError on it in order to prevent deadlock. Catching this exception and showing what the main tasklet is doing, and why it is blocked, is helpful. That is assuming you are running the scheduler in the main tasklet.

Something like the following:
import traceback

def create_tasklet(f, *args, **kwargs):
    try:
        return f(*args, **kwargs)
    except BaseException, e:
        if isinstance(e, RuntimeError) and e[0].startswith("Deadlock:"):
            print "Deadlocked tasklet, the main tasklet is doing the following:"
            traceback.print_stack(stackless.main.frame)
        # Let TaskletExit, SystemExit and KeyboardInterrupt raise up.
        elif isinstance(e, SystemExit, KeyboardInterrupt):
            raise e
        traceback.print_exc()
For future reference, the situation where I needed this was where I was polling for completed asynchronous file IO events. In theory this was programmed to do so in a way where it would schedule any tasklets blocked waiting for their IO to complete, so they would run when the scheduler was pumped next. However, due to a race condition the main tasklet sent the results of the IO operation to the channel before the tasklet doing the operation itself blocked to wait for those results. Then when the other tasklet itself blocked, the deadlock situation occurred.