wiki:Idioms
Last modified 3 years ago Last modified on 06/05/2012 10:07:53 PM

Idioms

This is a collection of useful snippets of code which make using Stackless easier, or tweak it to behave a different way.

Being Nice

When you are using tasklets within another framework, sometimes you need to put that framework before Stackless. One way to do this is to use an alternate method of yielding a tasklet, other than stackless.schedule(). "BeNice" is another way.

Idiom:

yieldChannel = stackless.channel()

def BeNice():
    yieldChannel.receive()

def ScheduleTasklets():
    # Only schedule as many tasklets as there are waiting when
    # we start.  This is because some of the tasklets we awaken
    # may BeNice their way back onto the channel.
    n = -yieldChannel.balance
    while n > 0:
        yieldChannel.send(None)
        n -= 1

    # Run any scheduled tasklets.  We should never find ourselves
    # having interrupted one, as that would be more likely to indicate
    # an infinite loop, as tasklets should use BeNice and never end
    # up in the scheduler.
    interruptedTasklet = stackless.run(1000000)
    if interruptedTasklet:
        # Print a stacktrace for the tasklet.
        raise RuntimeError("Better handling needed")

def f():
    while True:
        BeNice()
stackless.tasklet(f)()

while True:
    ScheduleTasklets()
    # Handle framework messages.
    # Wait a while to reduce CPU usage?

Micromanaging

It is useful to be able to know when your tasklets actually run, have uncaught exceptions or are about to exit because the function they were bound to returned.

The simplest approach is to avoid subclassing stackless.tasklet and just bind a normal tasklet to a wrapping function instead of the desired one, then the wrapping function calls the desired one in turn. This either requires a custom function be used to create any tasklets or a little extra work when creating them.

Idiom:

def ManageTasklet(func, args, kwargs):
    print "The tasklet has just been run."
    try:
        func(*args, **kwargs)
    except Exception, e:
        print "Handle an exception."
        raise e
    print "Managed function returned."

def CreateTasklet(func, *args, **kwargs):
    return stackless.tasklet(ManageTasklet)(func, args, kwargs)

Example:

def f(*args, **kwargs):
    print "f called with", args, kwargs

t = CreateTasklet(f, 1, 2, 3, x=1)
t.run()

Output:

The tasklet has just been run.
f called with (1, 2, 3) {'x': 1}
Managed function returned.

A way to do the same thing, but to avoid a using the custom function, or extra boilerplate is to override the tasklet class's __call__() function. This way, any tasklets created naturally get managed how you want.

Idiom:

def newCall(self, *args, **kwargs):
    oldFunction = self.tempval

    def newFunction(oldFunction, args, kwargs):
        print "The tasklet has just been run."
        try:
            oldFunction(*args, **kwargs)
        except Exception, e:
            print "Handle an exception."
            raise e
        print "Managed function returned."

    self.tempval = newFunction
    stackless.tasklet.setup(self, oldFunction, args, kwargs)
    return self

stackless.tasklet.__call__ = newCall

Example:

def f(*args, **kwargs):
    print "f called with", args, kwargs

t = stackless.tasklet(f)(1, 2, 3, x=1)
t.run()

Output:

The tasklet has just been run.
f called with (1, 2, 3) {'x': 1}
Managed function returned.

Naming

Naming your tasklets allows them to be distinguished from each other.

Idiom:

class NamedTasklet(stackless.tasklet):
    __slots__ = [ "name" ]

    def __str__(self):
        return "<NamedTasklet(%s)>" % self.name

def CreateNamedTasklet(name, func, *args, **kwargs):
    t = NamedTasklet(func)(*args, **kwargs)
    t.name = name
    return t

Sleeping

There is often a need to block a tasklet until a desired amount of time has passed. The simplest approach is to just reschedule it repeatedly, checking to see if the time to continue has arrived.

Idiom:

secondsToWait = 10.0
endTime = time.time() + secondsToWait
while time.time() < endTime:
    stackless.schedule()

However, having each tasklet that waits continually get rescheduled in order to recheck whether it should resume its activities can cause an unnecessarily heavy use of resources. It is convenient to write a common function that is called in order to block a tasklet until it should be awoken. Here is one way to do that.

Note

Richard: This is actually still busy sleep. The only way to make it truly not busy would be to put the system checks to sleep. Whether you centralize it or not without an actual block on execution, the code eats CPU. I propose that you put the sleep manager to sleep until the next waking tasklet comes online, remove that, search for the next soonest wake, and go to sleep again. You will need to use a separate system thread to manage this execution since sleeping on the main thread would halt execution. Alternately, you could just tuck a small piece of code into this which granularizes the sleep at maybe .1 seconds. But it is a busy wait no matter what you do -- unless you break it off onto a separate thread

Idiom - Sleep function for general use:

sleepingTasklets = []

def Sleep(secondsToWait):
    channel = stackless.channel()
    endTime = time.time() + secondsToWait
    sleepingTasklets.append((endTime, channel))
    sleepingTasklets.sort()
    # Block until we get sent an awakening notification.
    channel.receive()

def ManageSleepingTasklets():
    while 1:
        if len(sleepingTasklets):
            endTime = sleepingTasklets[0][0]
            if endTime <= time.time():
                channel = sleepingTasklets[0][1]
                del sleepingTasklets[0]
                # We have to send something, but it doesn't matter what as it is not used.
                channel.send(None)
        stackless.schedule()

stackless.tasklet(ManageSleepingTasklets)()

Example - Sleep function in use:

def TestSleep()
    print "pre", time.time()
    Sleep(5.0)
    print "post", time.time()

stackless.tasklet(TestSleep)()
stackless.run()

Output:

pre 1133381175.27
post 1133381180.28
<no more sleeping tasklets so ManageSleepingTasklets gets scheduled infinitely>