A story of simplification and abstraction (stackless timeouts)

by Rob Galanakis on 11/01/2014

Someone was asking me the other day how to implement a timeout in a thread. His initial implementation used two background threads: one to do the work (making requests to a web service and updating a counter), and the other in a loop polling the counter and sleeping. If the first thread stopped updating the counter, the second should report some sort of error.

I helped him simplify the design in a couple ways. First I had him use stackless instead of threads and taught him how threading and microthreads work. Based on that, I suggested that instead of a counter and loop/sleep, there is a parent tasklet that kicks off a child tasklet which does the actual work.* The parent tasklet recvs on a channel with a timeout, and the child tasklet sends on the channel to act like a heartbeat. If the parent recv times out, it means the child tasklet hasn’t reported in and the user can be alerted. This simplified the code considerably.

I then asked a colleague (Kristj√°n Valur) how to do a timeout with stackless, and he told me about the stacklesslib.util.timeout context manager. Doh! It ended up being as simple as:

try:
    for item in items:
        with stacklesslib.util.timeout(200):
            process_item(item)
except stackless.util.TimeoutError:
    tell_user_about_timeout()

It’s pretty amazing what sort of power you’re able to wield with a good language and framework. It’s so important to have the right abstractions, but you need to know how to use it. Even with documentation, nothing beats a little help from your friends.


*Instead of a channel, we probably could have used an Event.

rob.galanakis@gmail.com

There are 5 comments in this article:

  1. 11/01/2014Adam Skutt says:

    If the actual task was doing network I/O, then surely doing non-blocking I/O or using socket timeouts (where supported) is the simplest thing. In particular, it makes clean up vastly more easy over the blocking I/O case.

  2. 11/01/2014Rob Galanakis says:

    Well getting to the root of a problem rather than addressing the symptom deserves a whole post of its own (is a common theme on Raymond Chen’s blog) and I probably didn’t do enough of it in this case (I got caught up teaching him about thread/tasklet scheduling and stuff like that). I’m not sure what the resulting solution would have been, especially if it meant having to cut up some of his processing code which I suppose he wouldn’t have wanted to do on short notice (the first versions of internal customer service tools tend to be rush jobs).

    What do you mean by cleanup being more difficult with blocking IO? You mean cleaning up the thread or tasklet that is blocked?

  3. 11/01/2014Adam Skutt says:

    Both. You can’t (generally) cancel blocking I/O, so you’re left with a stuck thread until the I/O call returns. You can’t even portably close the I/O channel from a second thread, ignoring the rather difficult locking required to even attempt such a task.

    Ultimately, if your code uses an internal timeout to “give up” on an I/O operation, then you must have all the necessary machinery to deal with a stuck thread until the I/O operation completes, which may never happen.

  4. 13/06/2014Jim Parris says:

    Say now, what version of stacklesslib are you guys using then?

    The version I have (the latest? stacklesslib-1.0.3-py2.7) does not have any such method as ‘timeout’ or similar.

  5. 29/06/2014Rob Galanakis says:

    Hi Jim, you may need to grab the newest version from the BitBucket repo. Kristjan isn’t the most prompt with new pypi versions.

Write a comment:


− 3 = five