Our Eye-beams Begin to Twist

Part 3: Our Eye-beams Begin to Twist

This continues the introduction started here. You can find an index to the entire series here.

Doing Nothing, the Twisted Way

Eventually we are going to re-implement our asynchronous poetry client using Twisted. But first let’s write a few really simple Twisted programs just to get the flavor of things. As I mentioned in Part 2, I developed these examples using Twisted 8.2.0. Twisted APIs do change, but the core APIs we are going to use will likely change slowly, if at all, so I expect these examples to work for many future releases. If you don’t have Twisted installed you can obtain it here.

The absolute simplest Twisted program is listed below, and is also available in basic-twisted/simple.py in the base directory of the twisted-intro example code.

from twisted.internet import reactor
reactor.run()

You can run it like this:

python basic-twisted/simple.py

As we saw in Part 2, Twisted is an implementation of the Reactor Pattern and thus contains an object that represents the reactor, or event loop, that is the heart of any Twisted program. The first line of our program imports the reactor object so we can use it, and the second line tells the reactor to start running the loop.

This program just sits there doing nothing. You’ll have to stop it by pressing Control-C, otherwise it will just sit there forever. Normally we would have given the loop one or more file descriptors (connected to, say, a poetry server) that we want to monitor for I/O. We’ll see how to do that later, but for now our reactor loop is stuck. Note that this is not a busy loop which keeps cycling over and over. If you happen to have a CPU meter on your screen, you won’t see any spikes caused by this technically infinite loop. In fact, our program isn’t using any CPU at all. Instead, the reactor is stuck at the top cycle of Figure 5, waiting for an event that will never come (to be specific, waiting on a select call with no file descriptors).

That might make for a compelling metaphor of Hamletian inaction, but it’s still a pretty boring program. We’re about to make it more interesting, but we can already draw a few conclusions:

  1. Twisted’s reactor loop doesn’t start until told to. You start it by calling reactor.run().
  2. The reactor loop runs in the same thread it was started in. In this case, it runs in the main (and only) thread.
  3. Once the loop starts up, it just keeps going. The reactor is now “in control” of the program (or the specific thread it was started in).
  4. If it doesn’t have anything to do, the reactor loop does not consume CPU.
  5. The reactor isn’t created explicitly, just imported.

That last point is worth elaborating on. In Twisted, the reactor is basically a Singleton. There is only one reactor object and it is created implicitly when you import it. If you open the reactor module in the twisted.internet package you will find very little code. The actual implementation resides in other files (see, for example, twisted.internet.selectreactor).

Twisted actually contains multiple reactor implementations. As mentioned in Part 2, the select call is just one method of waiting on file descriptors. Twisted includes several reactor implementations that use a variety of different methods. For example, twisted.internet.pollreactor uses the poll system call instead of select.

To use a specific reactor, you must install it before importing twisted.internet.reactor. Here is how you install the pollreactor:

from twisted.internet import pollreactor
pollreactor.install()

If you import twisted.internet.reactor without first installing a specific reactor implementation, then Twisted will install the default reactor for you. The particular one you get will depend on the operating system and Twisted version you are using. For that reason, it is general practice not to import the reactor at the top level of modules to avoid accidentally installing the default reactor. Instead, import the reactor in the same scope in which you use it.

Note: as of this writing, Twisted has been moving gradually towards an architecture which would allow multiple reactors to co-exist. In this scheme, a reactor object would be passed around as a reference rather than imported from a module.

Note: not all operating systems support the poll call. If that is the case for your system, this example will not work.

Now we can re-implement our first Twisted program using the pollreactor, as found in basic-twisted/simple-poll.py:

from twisted.internet import pollreactor
pollreactor.install()

from twisted.internet import reactor
reactor.run()

And we have a poll loop that does nothing at all instead of a select loop that does nothing at all. Neato.

We’re going to stick with the default reactor for the rest of this introduction. For the purposes of learning Twisted, all the reactors do the same thing.

Hello, Twisted

Let’s make a Twisted program that at least does something. Here’s one that prints a message to the terminal window, after the reactor loop starts up:

def hello():
    print 'Hello from the reactor loop!'
    print 'Lately I feel like I\'m stuck in a rut.'

from twisted.internet import reactor

reactor.callWhenRunning(hello)

print 'Starting the reactor.'
reactor.run()

This program is in basic-twisted/hello.py. If you run it, you will see this output:

Starting the reactor.
Hello from the reactor loop!
Lately I feel like I'm stuck in a rut.

You’ll still have to kill the program yourself, since it gets stuck again after printing those lines.

Notice the hello function is called after the reactor starts running. That means it is called by the reactor itself, so Twisted code must be calling our function. We arrange for this to happen by invoking the reactor method callWhenRunning with a reference to the function we want Twisted to call. And, of course, we have to do that before we start the reactor.

We use the term callback to describe the reference to the hello function. A callback is a function reference that we give to Twisted (or any other framework) that Twisted will use to “call us back” at the appropriate time, in this case right after the reactor loop starts up. Since Twisted’s loop is separate from our code, most interactions between the reactor core and our business logic will begin with a callback to a function we gave to Twisted using various APIs.

We can see how Twisted is calling our code using this program:

import traceback

def stack():
    print 'The python stack:'
    traceback.print_stack()

from twisted.internet import reactor
reactor.callWhenRunning(stack)
reactor.run()

You can find it in basic-twisted/stack.py and it prints out something like this:

The python stack:
...
  reactor.run() <-- This is where we called the reactor
...
...  <-- A bunch of Twisted function calls
...
  traceback.print_stack() <-- The second line in the stack function

Don’t worry about all the Twisted calls in between. Just notice the relationship between the reactor.run() call and our callback.

What’s the deal with callbacks?

Twisted is not the only reactor framework that uses callbacks. The older asynchronous Python frameworks Medusa and asyncore also use them. As do the GUI toolkits GTK and QT, both based, like many GUI frameworks, on a reactor loop.

The developers of reactive systems sure love callbacks. Maybe they should just marry them. Maybe they already did. But consider this:

  1. The reactor pattern is single-threaded.
  2. A reactive framework like Twisted implements the reactor loop so our code doesn’t have to.
  3. Our code still needs to get called to implement our business logic.
  4. Since it is “in control” of the single thread, the reactor loop will have to call our code.
  5. The reactor can’t know in advance which part of our code needs to be called.

In this situation callbacks are not just one option — they are the only real game in town.

Figure 6 shows what happens during a callback:

Figure 6: the reactor making a callback
Figure 6: the reactor making a callback

Figure 6 illustrates some important properties of callbacks:

  1. Our callback code runs in the same thread as the Twisted loop.
  2. When our callbacks are running, the Twisted loop is not running.
  3. And vice versa.
  4. The reactor loop resumes when our callback returns.

During a callback, the Twisted loop is effectively “blocked” on our code. So we should make sure our callback code doesn’t waste any time. In particular, we should avoid making blocking I/O calls in our callbacks. Otherwise, we would be defeating the whole point of using the reactor pattern in the first place. Twisted will not take any special precautions to prevent our code from blocking, we just have to make sure not to do it. As we will eventually see, for the common case of network I/O we don’t have to worry about it as we let Twisted do the asynchronous communication for us.

Other examples of potentially blocking operations include reading or writing from a non-socket file descriptor (like a pipe) or waiting for a subprocess to finish. Exactly how you switch from blocking to non-blocking operations is specific to what you are doing, but there is often a Twisted API that will help you do it. Note that many standard Python functions have no way to switch to a non-blocking mode. For example, the os.system function will always block until the subprocess is finished. That’s just how it works. So when using Twisted, you will have to eschew os.system in favor of the Twisted API for launching subprocesses.

Goodbye, Twisted

It turns out you can tell the Twisted reactor to stop running by using the reactor’s stop method. But once stopped the reactor cannot be restarted, so it’s generally something you do only when your program needs to exit.

Note: there has been past discussion on the Twisted mailing list about making the reactor “restartable” so it could be started and stopped as you like. But as of version 8.2.0, you can only start (and thus stop) the reactor once.

Here’s a program, listed in basic-twisted/countdown.py, which stops the reactor after a 5 second countdown:

class Countdown(object):

    counter = 5

    def count(self):
        if self.counter == 0:
            reactor.stop()
        else:
            print self.counter, '...'
            self.counter -= 1
            reactor.callLater(1, self.count)

from twisted.internet import reactor

reactor.callWhenRunning(Countdown().count)

print 'Start!'
reactor.run()
print 'Stop!'

This program uses the callLater API to register a callback with Twisted. With callLater the callback is the second argument and the first argument is the number of seconds in the future you would like your callback to run. You can use a floating point number to specify a fractional number of seconds, too.

So how does Twisted arrange to execute the callback at the right time? Since this program doesn’t listen on any file descriptors, why doesn’t it get stuck in the select loop like the others? The select call, and the others like it, also accepts an optional timeout value. If a timeout value is supplied and no file descriptors have become ready for I/O within the specified time then the select call will return anyway. Incidentally, by passing a timeout value of zero you can quickly check (or “poll”) a set of file descriptors without blocking at all.

You can think of a timeout as another kind of event the event loop of Figure 5 is waiting for. And Twisted uses timeouts to make sure any “timed callbacks” registered with callLater get called at the right time. Or rather, at approximately the right time. If another callback takes a really long time to execute, a timed callback may be delayed past its schedule. Twisted’s callLater mechanism cannot provide the sort of guarantees required in a hard real-time system.

Here is the output of our countdown program:

Start!
5 ...
4 ...
3 ...
2 ...
1 ...
Stop!

Note the “Stop!” line at the ends shows us that when the reactor exits, the reactor.run call returns. And we have a program that stops all by itself.

Take That, Twisted

Since Twisted often ends up calling our code in the form of callbacks, you might wonder what happens when a callback raises an exception. Let’s try it out. The program in basic-twisted/exception.py raises an exception in one callback, but behaves normally in another:

def falldown():
    raise Exception('I fall down.')

def upagain():
    print 'But I get up again.'
    reactor.stop()

from twisted.internet import reactor

reactor.callWhenRunning(falldown)
reactor.callWhenRunning(upagain)

print 'Starting the reactor.'
reactor.run()

When you run it at the command line, you will see this output:

Starting the reactor.
Traceback (most recent call last):
  ... # I removed most of the traceback
exceptions.Exception: I fall down.
But I get up again.

Notice the second callback runs after the first, even though we see the traceback from the exception the first raised. And if you comment out the reactor.stop() call, the program will just keep running forever. So the reactor will keep going even when our callbacks fail (though it will report the exception).

Network servers generally need to be pretty robust pieces of software. They’re not supposed to crash whenever any random bug shows its head. That’s not to say we should be lackadaisical when it comes to handling our own errors, but it’s nice to know Twisted has our back.

Poetry, Please

Now we’re ready to grab some poetry with Twisted. In Part 4, we will implement a Twisted version of our asynchronous poetry client.

Suggested Exercises

  1. Update the countdown.py program to have three independently running counters going at different rates. Stop the reactor when all counters have finished.
  2. Consider the LoopingCall class in twisted.internet.task. Rewrite the countdown program above to use LoopingCall. You only need the start and stop methods and you don’t need to use the “deferred” return value in any way. We’ll learn what a “deferred” value is in a later Part.
145 comments
  1. luisbg

    in the countdown app you are doing import the reactor twice. I don't think that's neccesary, the code runs fine without the one in line 7.

  2. Cyril Pauya

    Hi, this is a very good introduction to Twisted + callbacks.

    I did your suggested exercises and learned more. Thanks.

  3. Ted

    Thanks for the intro. Maybe a dumb question here, after I install a reactor will subsequent imports of reactor from twisted internet still use my installed reactor?

  4. Mark Wallace

    Me again :) Any idea why this, when trying to run basic-twisted/simple-poll.py:

    File "/System/Library/Frameworks/Python.framework/Versions/2.6/Extras/lib/python/twisted/internet/pollreactor.py", line 19, in
    from select import error as SelectError, poll

    Been beating my head against it for quite a while now. Do pardon whatever obvious thing I'm undoubtedly missing (or operator error, more likely).

  5. Mark Wallace

    Hilarious: I can't even report the problem correctly.

    Here's what I was actually wondering about:
    "ImportError: cannot import name poll"

    Where is 'select' supposed to be found? The error message seems to imply that 'error' itself has been imported correctly, but that 'poll' can't be located, yes? Any insights greatly appreciated.

  6. Narada

    Hi Dave! I'm trying to complete suggested exercise #2(LoopingCall), it's working ok, but did i understand right the way how i should implement that?
    https://gist.github.com/899383

    Many thanks for any thoughts.

  7. Felipe

    Thanks for this wonderful tutorial, Dave! You're a wonderful teacher. Just wanted to share my approach for the second question, since I'm quite proud of it :) http://pastebin.com/n4UkHcpv

    Thanks again!

    1. jmb

      Hello Felipe,

      I didn't succeed to run your solution without changing

      line 10 : print "LoopCall # {} down to {}".format(index, counters[index])

      by

      print "LoopCall # {0} down to {1}".format(index, counters[index])

      copy/paste problem ?

      regards

      1. Kwami

        probably python versions. If I recall correctly, in 2.6 and earlier, you have to explicitly specify your var indices. My guess is Felipe is using 2.7 or higher...

  8. Mike Nerone

    Hi, Dave! I've really been enjoying your tutorial - your teaching style is both clear *and* entertaining. Thanks for the time you put in!

    Here's my exercise #2 solution: https://gist.github.com/1057650

    Win? :)

  9. Fabian

    Hey Dave,

    I'm quite new to Twisted Programming, and I have a question regarding the NotRestartable thingy: what if the sockets and factories the SelectReactor shall deal with, change during runtime? Say, an IP address changed, or - in my case - you got seperate modules for each Protocol which are not known at the moment the reactor.run()s ?
    As you cannot restart the reactor, I see the following options:
    - Change to poll reactor to have one reactor for each Protocol: not possible in my case (Mac OSX)
    - Change Program Architecture to start Reactor after sockets are known

    Are there any further options?

    Cheers

    1. Fabian

      THIMC, there's a discussion on the mailing list about that:
      http://twistedmatrix.com/pipermail/twisted-python/2011-September/024511.html

  10. Marconi

  11. Pastafarianist

    Here's my solution for #1. I don't really believe this is the best approach but since the counters have to communicate somehow between each other in order to decide when to call reactor.stop() the only solution I managed to come up with was global variables.

    http://dl.dropbox.com/u/11996458/countdown.py

    I would greatly appreciate if someone could give me a hint on how this could could be solved in a better way.

    P.S.: I'm not a native speaker, so excuse me for any mistakes I could have made :)

  12. Lauren

    Hi, Thank you for the awesome tutorial.

    Here is my solution for Exercise 2: http://pastebin.com/pyGYfdUM

    Learned a lot by doing this! I recommend that everyone do the exercises. :)

  13. Amit

    Very well explained .
    I am new to python and having some problem in exercise :
    this is link to my solution of exercise1 :http://pastebin.com/T5hihtTM
    this is giving some errors, please give it a look!!(i m sure i m not using select.select() correctly )

    I was looking at solution of Narada , https://gist.github.com/899383 . It is looking sync to me . I mean where is outer loop which keep care of which counter to run which time (which is not blocked).

    I know these are silly things to ask and not worthy of your time ,but i have tried myself to understand and no success. Hoping your explanation will help me to understand later parts in better way.

  14. Fred

    Hi Dave! Thanks for tutorial, I really like the way you explain.

    One question I have is, how does Twisted benefits from smaller tasks (callbacks)? Would it make any difference for one single request, if I do say 100 smaller callbacks instead of 10 bigger? In the end, its the same amount of work. And those callbacks do they work actually blocking the reactor for a small period of time, right?

  15. slothy

    Thanks for the tuto! I'm enjoying very much!

    Here mine http://dpaste.com/hold/760217/

    Anybody has a solution without globals???
    I cant find it.

    1. slothy

      Yes....it has a very big problem with the names...;)

  16. umi

    I love how you explicitly point out things like "when our callbacks are running, the Twisted loop is not running"! It really helps :)

  17. Seth

    Hi Dave,
    Thanks for the tutorial. I'm new to python and async. I'm getting some errors in my attempt at exercise 1. My thoughts were to pass in a separate object that would store a dictionary of the different counters and their respective counts and would check for when the counters all were down to zero. However, I think that my class is running outside the reactor as any debugging print statements happen before the reactor start message. I think that this is the root of my issues. My 'complete' method is tossing errors that I don't understand. Any insight you could offer would be a great assistance! Thanks! http://dpaste.com/hold/788866/

  18. mike

    Thanks for the tutorial. I've done some asyn sockets in C, but was struggling to wrap my head around the programming model of twisted. Here's my code for exercise 2.: https://gist.github.com/3433337

  19. Dave

    Hi Dave,
    I'm having trouble with exercise #1. You did refer to it in another reply, but it doesn't show up. Can you repeat it or provide a new link?

    Your tutorial is fantastic -- very well written and even fun to read.

    Thanks,
    dave

  20. Dave

    I found the (a) solution:

    #!/usr/bin/env python

    class Countdown(object):
    number_running = 0

    def __init__(self, max):
    self.max_count = max
    self.counter = self.max_count
    Countdown.number_running += 1

    def count_down(self):
    if self.counter == 0:
    Countdown.number_running -= 1
    if Countdown.number_running == 0:
    reactor.stop()
    else:
    print self.counter, '...'
    self.counter -= 1
    reactor.callLater(1, self.count_down)

    from twisted.internet import reactor

    counter1 = Countdown(5)
    counter2 = Countdown(10)
    counter3 = Countdown(15)
    reactor.callWhenRunning(counter1.count_down)
    reactor.callWhenRunning(counter2.count_down)
    reactor.callWhenRunning(counter3.count_down)

    print 'Start!'
    reactor.run()
    print 'Stop!'

    1. Steve Dowling

      Hi Dave,

      I used your solution as a springboard to my own but I think your solution only works if you have the same delay for each counter. The exercise asked us to have a variable delay and if you follow your code it doesn't work. What I did was to move the if block dealing with the reactor stop below the original "if else" block to deal with this.

      No doubt my code can be massively improved, but here's my solution that allows for both variable delay, and number of counts.

      class Countdown(object):

      number_of_counters = 0

      def __init__(self, name="Countdown", delay=1, counter=5):
      self.delay = delay
      self.counter = counter
      self.name = name
      Countdown.number_of_counters +=1

      def count(self):
      if self.counter == 0:
      print 'A task (%s) has completed' % (self.name,)
      Countdown.number_of_counters -= 1
      else:
      print self.name, self.counter, '...'
      self.counter -= 1
      reactor.callLater(self.delay, self.count)
      if Countdown.number_of_counters == 0:
      print 'Stop the reactor'
      reactor.stop()

      from twisted.internet import reactor

      reactor.callWhenRunning(Countdown("Counter 1",3,3).count)
      reactor.callWhenRunning(Countdown("Counter 2",1,3).count)
      reactor.callWhenRunning(Countdown().count

      print 'Start!'
      reactor.run()
      print 'Stop!'

      And the output is as follows:

      Start!
      Counter 1 3 ...
      Counter 2 3 ...
      Countdown 5 ...
      Counter 2 2 ...
      Countdown 4 ...
      Counter 2 1 ...
      Countdown 3 ...
      Counter 1 2 ...
      A task (Counter 2) has completed
      Countdown 2 ...
      Countdown 1 ...
      A task (Countdown) has completed
      Counter 1 1 ...
      A task (Counter 1) has completed
      Stop the reactor
      Stop!

    2. Bhaarat

      Hi Dave

      I tried your code above and it didn't work for me. I don't know the exact reason but what I am guessing is that you have created 3 different objects of the class Countdown so all 3 objects are diff and have no relation/way to communicate between them to see if all the counters have finished or not.

      Please correct me if I am wrong.

      PS: I am very new to python please pardon if something is wrong with my understanding.

  21. daniel ashman

    random question here on python, in the following code why is it reactor.callWhenRunning(stack) and NOT reactor.callWhenRunning(stack())... what is the difference in meaning between the two?

    def stack():
    print 'The python stack:'
    traceback.print_stack()

    from twisted.internet import reactor
    reactor.callWhenRunning(stack)

  22. Zancas

    I believe that the "selectreactor" is no longer the deafult reactor. IIUC, "epollreactor" is now the default reactor (at least on linux).

    https://twistedmatrix.com/trac/browser/tags/releases/twisted-12.2.0/twisted/internet/default.py

  23. Zancas

    Sorry... IIUC = "If I Understand Correctly"

  24. plover

    Many thanks for this lovely series! I'm new to Twisted and was unfamiliar with asynchronous programming terminology, so this has been a great help.

    I've been imagining a "reactor" as the thing keeping the program moving forward, perhaps by spitting out callbacks something like how a nuclear reactor spits out neutrons. While I still like the image, I suspect your account makes more sense...

    Here are my solutions for the exercises:

    #1: http://pastebin.com/uGjv6Fnh
    #2: http://pastebin.com/hqpGcJnm

    I actually came up with the two different approaches as solutions for #1, and then altered one of them to use LoopingCall.

    Is my sense that #2 (with or without LoopingCall) seems more "Twisted flavored" correct? And is the instinct (also in #2) to remove the call to reactor.stop from other callbacks, a good one (even if unnecessary here)?

  25. plover

    In writing the two solutions, I was thinking mostly of the difference between the first and second solutions as being how coordination was organized, i.e. between having the callbacks go to the aggregate pool vs going to the individual countdown objects. While I did notice that by the time I was done the Countdowns in #1 were stripped down almost to being structs, I chalked that up to this being a toy example.

    Though of course facilities like LoopingCall are the reason for using Twisted in the first place, the point of my aside was that even without LoopingCall, the organization of #2 would still be more suggestive of Twisted thinking -- which you appear to agree with. :)

    Indeed, your suggestion of what to do further here seems to continue the process of separating the components: not only does the monitor not get to control the countdowns, they don't even need to send it status messages.

    So far, the only strategy I've come up with that actually leaves the Countdowns blissfully oblivious of what's going on around them is to have the monitor object poll them to see if they've finished. Like this:

    #3 http://pastebin.com/2WZ24H0k

    (The pasted version has (at least) two bugs, that don't have any effect here: monitor.__init__ doesn't set the rate attribute to the function argument, and monitor.poll fails to call self.looper.stop)

    Is that what you were getting at? It works, but I don't have a very intuitive sense of whether it's a good way to do this.

    Thanks for taking the time to look at my code.

  26. plover

    It seems there was one point I misunderstood in writing #3. My design goal there was that the Countdown objects were inward turned -- an object like the monitor could examine what they were doing, but beyond a start signal, they accepted no outside information after construction, and called no other objects. I suppose I can say that this was at least asynchronous, but I'm not sure it could be called an asynchronous network...

    So here we go, a version in which the Countdown objects are very bad airline passengers that accept callbacks in brown paper wrappers from random passersby:

    #4: http://pastebin.com/WHJnqQ7f

    Also, now the monitor will track any object with a setCallback method. This feels a bit unspecific since there are events other than completion of a process that should trigger callbacks. Maybe it should be something like callWhenFinished?

    One rather arbitrary design goal I have used in all the versions is to store the startTime in only one place. This means I'm always trying to solve the problem of where and how each endTime should be generated. I included a commented out line that suggests how a timestamp could be passed back when the callback is triggered. Given that these callbacks triggered by a process completing, it doesn't seem entirely out of place to send a timestamp. Though, now I think of it, I suppose the endTime argument could be made optional with a default. Are these the kinds of issues solved by Deferred objects?

    I find it useful to work through things on a low level like this. Thanks again for your help and patience here.

  27. Indradhanush Gupta

    In one of the above comments I saw this fragment of code :

    from twisted.internet import reactor

    counter1 = Countdown(5)
    counter2 = Countdown(10)
    counter3 = Countdown(15)
    reactor.callWhenRunning(counter1.count_down)
    reactor.callWhenRunning(counter2.count_down)
    reactor.callWhenRunning(counter3.count_down)

    I'd like to ask that are all the 3 callWhenRunning() function calls asynchronous? Or does the second call wait for the first one to complete. Pardon me if this is a basic question. I seem to have some difficulty in understanding the callbacks. :(

  28. Indradhanush Gupta

    If that's asynchronous then in that case, shouldn't the code give a slightly different output each time? Or at least there's a chance of obtaining a different output? Please correct me if I am wrong.

  29. Indradhanush Gupta

    Yes. This clears up the cloud of confusion. Thanks! :)

  30. Kevin

    Thoroughly enjoying this! Thanks dave

  31. Kasun thennakoon

    thanks dave nice article :)

  32. yangweinjcn

    Hi Dave,
    Thanks for your great series.
    I have followed your discussion with Plover above and finally worked the solution to exercise#2 as following:
    http://pastebin.com/LWNeU3wv
    My basic idea is to set a manager to handle both the job of monitoring and managing of LoopingCall object. In this way, the client code can focus on its main task and only need to call the finalize method to tell the manager the job is done.
    Secondly, I want to make the manager a common manager of all kinds of tasks; thus the client must implant a interface with two method, Name() and Job(), to let the manager aware of its property without bound to it.
    I do not know whether this is a good idea or not. Or could you please give me some indication to further directions to craft this task?

  33. yangweinjcn

    That's quite clear. Now I think I know it better.
    Thanks

    1. yangweinjcn

      And this is my final solution:
      http://pastebin.com/xtiNsKCn

  34. Bhaarat

    Hi dave

    Thanks for the article, I love reading it and it makes all the points quiet clear and simple.

    I tried the excercise 1 but was not able to get through it, since I am new to Python I would be thankful if you share some code to how actually it has to be done would be very helpful for me to understand.

    Thanks
    Bhaarat

  35. Deepika

    Hi, I have this python twisted code I am working on in which I call the reactor.connectTCP with a hostname . This program gets stuck inside the reactor.run.However when I give the IP its works fine and executes completely. How can I get the error message and determine why the callback functions do not get executed.

  36. Spence

    hi Dave, I am trying to understand why this small modification to your code causes it to stop working properly:

    class Countdown(object):

    counter = 5
    
    def count(self,s):
        if self.counter == 0:
            reactor.stop()
        else:
            print self.counter, '...'
            self.counter -= 1
            reactor.callLater(1, self.count(1))
    

    from twisted.internet import reactor

    reactor.callWhenRunning(Countdown().count(1))

    print 'Start!'
    reactor.run()
    print 'Stop!'

    all I have done is attempted to add a variable to the 'count' function that at this point isn't even used. I don't understand why this causes the function to run before the reactor starts.

  37. akadi

    Thanks for this wonderful tutorial.

    I have a question, when i run reactor from thread in a synchrone python program, the twisted code is never called, so to resolve this problem, I had to put a sleep.

    def _reactor_thread(self):
    if not self.reactor.running:
    self.reactor.run(installSignalHandlers=0)

    def _start_thread( self ):

    self.client_thread = Thread( target=self._reactor_thread,
                                 name="mine" )
    self.client_thread.setDaemon(True)
    self.client_thread.start()
    from time import sleep
    sleep( 0.5 )
    

    What is the best way to do it, instead of calling sleep?

    Thank you.

  38. Wangui

    Hi. I want to thank you for this awesome tutorial. You are a life changer. Am a newbie in python and am having trouble with my code for Exercises #2. I know I must stop the LoopingCall somewhere but don't know where. Am not an Native English Speaker, so pardon me if my question is vague. Here is my code http://pastebin.com/01JSL8MG

  39. alphabetasoup

    Hi dave, thanks for the brilliant tutorial. I don't think I've ever seen such a comprehensive tutorial, and I'm really grateful. I'm looking forward to the rest of it. Here is my go at the first exercise: https://gist.github.com/alpha-beta-soup/f3cb58b9da0a9feb2f00 I didn't even know about class-level variables, they seem so magic.

  40. Jay Dizzle

    Hey man,

    Just want to say thank you for such an awesome piece.
    Six years later this is still paying dividends!
    I've learn't an absolute tonne (and all of the magic of NodeJS has vanished after this), and so many ideas have opened up to me.

    Cheers,
    Jamie

  41. demian

    i really enjoy the way you explain, very simple and the step by step are incredible well done!... i wish we can peak at the answers of the suggested exercises. thanks for doing this tutorials.

  42. HBGHOST

    i found a)solution:
    class Countdown(object):
    counter1 = 5
    counter2= 5
    counter3=5

    def count(self):
        if self.counter1 == 0:
            print("second counter will start")
            reactor.callWhenRunning(Countdown().count2)
        else:
            print(self.counter1, '...')
            self.counter1 -= 1
            reactor.callLater(1, self.count)
    def count2(self):
        if self.counter2 == 0:
            print("Last counter will start")
            reactor.callWhenRunning(Countdown().count3)
        else:
            print(self.counter2, '...')
            self.counter2 -= 1
            reactor.callLater(1, self.count2)
    def count3(self):
        if self.counter3 == 0:
            reactor.stop()
        else:
            print(self.counter3, '...')
            self.counter3 -= 1
            reactor.callLater(1, self.count3)
    

    from twisted.internet import reactor

    reactor.callWhenRunning(Countdown().count)

    print('Start!')
    reactor.run()
    print('Stop!')

  43. racket99

    '''
    Exercise 1
    '''

    from twisted.internet import reactor

    class Countdown(object):
    counter = counter2 = counter3 = 5

    def count(self):
        if self.counter == 0:
            reactor.callLater(.5,self.stopSignal)
            reactor.stop()
        else:
            print self.counter, '...', 'counter'
            self.counter -= 1
            reactor.callLater(2,self.count)
    
    def count2(self):
        if self.counter2 != 0:
            print self.counter2, '...', 'counter2'
            self.counter2 -= 1
            reactor.callLater(1,self.count2)
    
    def count3(self):
        if self.counter3 != 0:
            print self.counter3, '...', 'counter3'
            self.counter3 -= 1
            reactor.callLater(0.5,self.count3)
    
    def stopSignal(self):
        if (self.counter+self.counter2+self.counter3)==0:
            reactor.stop()
    

    reactor.callWhenRunning(Countdown().count) # runs immediately when reactor starts
    reactor.callWhenRunning(Countdown().count2) # runs 2nd when reactor starts
    reactor.callWhenRunning(Countdown().count3) # runs 3rd when reactor starts

    print 'start!'
    reactor.run()
    print 'stop!'

  44. Yaman Singla

    class Countdown(object):

    counter1 = 5
    counter2 = 20
    counter3 = 50
    
    def count1(self):
        if self.counter1 == 0 and self.counter2 == 0 and self.counter3 == 0:
            reactor.stop()
        elif self.counter1 > 0:
            print self.counter1, '... process',1
            self.counter1 -= 1
            reactor.callLater(0.1, self.count1)
    
    def count2(self):
        if self.counter1 == 0 and self.counter2 == 0 and self.counter3 == 0:
            reactor.stop()
        elif self.counter2 > 0:
            print self.counter2, '... process',2
            self.counter2 -= 1.25
            reactor.callLater(0.12, self.count2)
    
    def count3(self):
        if self.counter1 == 0 and self.counter2 == 0 and self.counter3 == 0:
            reactor.stop()
        elif self.counter3 > 0:
            print self.counter3, '... process',3
            self.counter3 -= 12.5
            reactor.callLater(0.2345, self.count3)
    

    from twisted.internet import reactor

    obj = Countdown()
    reactor.callWhenRunning(obj.count1)
    reactor.callWhenRunning(obj.count2)
    reactor.callWhenRunning(obj.count3)

    print 'Start!'
    reactor.run()
    print 'Stop!'

  45. racket99

    Hi Dave,

    Sorry the website didn't allow me to reply to your last reply so I am posting here. I incorporated many of your suggestions. Is this closer to what you had in mind?

    Thanks for being patient!

    --- code ---

    from twisted.internet import reactor

    class Countdown(object):

    def __init__(self,Counter,Speed):
        self.__Runs=0
        self.__Counter=Counter
        self.__Speed=Speed
        for i in range(len(self.__Speed)):
            self.count(self.__Speed[i],self.__Counter)
    
    def count(self,Rate,Counter):
        if Counter == 0:
            return Countdown.finish(self)
        else:
            print Counter, '...'
            Counter -= 1
            reactor.callLater(Rate,self.count,Rate,Counter)
    
    def finish(self):
        self.__Runs +=1
        if self.__Runs == len(self.__Speed):
            reactor.stop()
    

    reactor.callWhenRunning(Countdown,5,[.1,.2,.5])

    print 'start!'
    reactor.run()
    print 'stop!'

  46. felony

    For exercise-1 I used your suggestion. Is it okay to use global variables like that ?

    class Countdown(object):
    def init(self,title,counter,interval):
    self.counter = counter
    self.title = title
    self.interval = interval
    def count(self):
    if self.counter == 0:
    reactor.callLater(1,globCounter)
    else:
    print self.title, self.counter, '...'
    self.counter -= 1
    reactor.callLater(self.interval, self.count)

    from twisted.internet import reactor

    mcounter = 3

    def globCounter():
    global mcounter
    mcounter = mcounter -1
    if mcounter == 0:
    reactor.stop()

    reactor.callWhenRunning(Countdown("First Counter: ",5,0.1).count)
    reactor.callWhenRunning(Countdown("Second Counter: ",7,2).count)
    reactor.callWhenRunning(Countdown("Third Counter: ",3,1.2).count)
    print 'Start!'
    reactor.run()
    print 'Stop!'

  47. Jezrael Arciaga

    class Countdown(object):

    instances = []
    
    counter = 5
    
    def __init__(self, tick):
        self.tick = tick
        self.instances.append(self)
    
    def count(self):
        if self.counter > 0:
            print self.counter, '...'
            self.counter -= 1
            reactor.callLater(self.tick, self.count)
        else:
            self.instances.remove(self)
    
        if not self.instances:
            reactor.stop()
    

    from twisted.internet import reactor

    reactor.callWhenRunning(Countdown(1).count)
    reactor.callWhenRunning(Countdown(.5).count)
    reactor.callWhenRunning(Countdown(.3).count)

    print 'Start!'
    reactor.run()
    print 'Stop!'

  48. Romain Vincent

    class Counter(object):

    def __init__(self, counter, rate, name):
        self.name = name
        self.counter = counter
        self.rate = rate
        self.counting = 1
    
    def count(self):
        if self.counter == 0:
            self.counting = 0
            print self.name, ' : ', 'Done!'
        else:
            print self.name, ' : ', self.counter, '...'
            self.counter -= self.rate
            reactor.callLater(1, self.count)
    

    class Countdown2(object):

    counts = [20, 20, 20]
    rates = [1, 2, 5]
    names = ['1st', '2nd', '3rd']
    
    counters = [Counter(c, r, n) for c, r, n
                in zip(counts, rates, names)]
    
    def is_counting(self):
        return sum([counter.counting for counter
                    in self.counters])
    
    def shutdown(self):
    
        if self.is_counting():
            reactor.callLater(1, self.shutdown)
        else:
            print 'All done!'
            reactor.stop()
    
    def update(self):
        for counter in self.counters:
            counter.count()
    

    from twisted.internet import reactor

    ctd = Countdown2()

    reactor.callWhenRunning(ctd.update)
    reactor.callWhenRunning(ctd.shutdown)

    print 'Start!'
    reactor.run()
    print 'Stop!'

  49. rishant singh

    Hi Dave,
    I have never come across such a nicely written article. It is so well written, that even if I dont intend to use Twisted, still I would read the article for the joy of reading it.

    Thanks a lot.

  50. Bienchen

    Hi Dave,

    I also had a go on exercise one, it closely came to your reply here link. While I thought the exercise asks for separate countdowns... which led me to understanding that inside the method calls data is not kept, hence the start-1 bit.

    [code lang=text]
    class Countdown(object):
    rgd_counters = 0

    def register_counter(self):
    self.rgd_counters += 1

    def rm_counter(self):
    self.rgd_counters -= 1
    if self.rgd_counters == 0:
    reactor.stop()

    def init_counter(self, start, my_wait):
    self.register_counter()
    reactor.callLater(my_wait, self.count, start, my_wait)

    def count(self, start, my_wait):
    if start == 0:
    self.rm_counter()
    else:
    print start, '...', my_wait
    reactor.callLater(my_wait, self.count, start - 1, my_wait)

    from twisted.internet import reactor

    my_counter = Countdown()
    reactor.callWhenRunning(my_counter.init_counter, 5, 1)
    reactor.callWhenRunning(my_counter.init_counter, 5, 2)
    reactor.callWhenRunning(my_counter.init_counter, 5, 5)

    print 'Start!'
    reactor.run()
    print 'Stop!'
    [/code]

  51. AiOnOiD

    Hi Dave,

    first i want to thank you for this great tutorial, this is the first time i understands the difference between synchronous and asynchronous is an easy way, and i used it before in the Qt library, it is asynchronous by default

    here is my try on Exc 1, added time to see the point you mentioned before

    [code language="python"]
    from twisted.internet import reactor
    from datetime import datetime

    class Countdown(object):

    counter = [
    {'name': 'bot001', 'time': 1, 'count': 20},
    {'name': 'bot002', 'time': 2, 'count': 5},
    {'name': 'bot003', 'time': .5, 'count': 10},
    {'name': 'bot004', 'time': .1, 'count': 20},
    ]

    started = []

    finish = len(counter)

    def count(self, cnt):
    if self.finish == 0:
    reactor.stop()
    else:
    print('count for {', self.counter[cnt]['time'], '} is : ...', self.counter[cnt]['count'])
    self.counter[cnt]['count'] -= 1
    if self.counter[cnt]['count'] >= 0:
    reactor.callLater(self.counter[cnt]['time'], self.count, cnt)
    if self.counter[cnt]['count'] == 0:
    self.finish -= 1
    print(self.counter[cnt]['name'], 'Task DONE'
    , (datetime.now() - self.started[cnt]).total_seconds())

    def count_run(self):
    for i in list(range(len(self.counter))):
    self.started.append(datetime.now())
    self.count(i)
    if self.finish == 0:
    reactor.stop()

    if __name__ == '__main__':
    reactor.callWhenRunning(Countdown().count_run)

    print('Start!')
    reactor.run()
    print('Stop!')

    [/code]

  52. remes431

    Hi Dave,

    Here is my try on exercise 1. It is far from ideal, but at least if uses a callback to check if all counters did finish as you suggested above.

    from twisted.internet import reactor
    
    class Countdown(object):
    
        def __init__(self, name, counter, rate):
            self.name = name
            self.counter = counter
            self.rate = rate
    
        def count(self):
            if self.counter == 0:
                reactor.callWhenRunning(finished)
            if self.counter > 0:
                print self.name, '...', self.counter, '... process'
                self.counter -= 1
                reactor.callLater(self.rate, self.count)
    
    def check_to_stop(counters):
        for c in counters:
            c.count()
    
    def finished():
        total = sum([c.counter for c in counters])
        if total == 0:
            reactor.stop()
    
    counters = [
        Countdown('Counter 1', 20, 0.3),
        Countdown('Counter 2', 5, 1),
        Countdown('Counter 3', 4, 0.5),
        Countdown('Counter 4', 10, 0.375),
        Countdown('Counter 5', 50, 0.1),
    ]
    reactor.callWhenRunning(check_to_stop, counters)
    
    print 'Start!'
    reactor.run()
    print 'Stop!'
    
  53. Kamil

    class Countdown:

    CountdownNum = 1
    
    def __init__(self, counter, when):
        self.counter = counter
        self.when = when
        self.activeCounts = Countdown.CountdownNum
        Countdown.CountdownNum += 1
    
    def count(self):
        if self.counter == 0:
            Countdown.CountdownNum -= 1
            if Countdown.CountdownNum == 1:
                reactor.stop()
        else:
            print 'Counter number', self.activeCounts, ':', self.counter, '...'
            self.counter -= 1
            reactor.callLater(self.when, self.count)
    

    from twisted.internet import reactor

    reactor.callWhenRunning(Countdown(50, 0.01).count)
    reactor.callWhenRunning(Countdown(25, 0.02).count)
    reactor.callWhenRunning(Countdown(16, 0.03).count)
    reactor.callWhenRunning(Countdown(12, 0.04).count)
    reactor.callWhenRunning(Countdown(10, 0.05).count)

    print 'Start!'
    reactor.run()
    print 'Stop!'

  54. Tong

    Maybe a little late to reply.

    class Countdown(object):

    def __init__(self, rates, **kws):
        self.rates = rates
        self.counters = kws.get('counters', [5] * len(rates))
    
    def start_count(self):
        def wrapper_count(id):
            if self.counters[id] != 0:
                print(f'Counter {id}: {self.counters[id]}')
                self.counters[id] -= 1
                reactor.callLater(self.rates[id], wrapper_count, id)
            elif sum(self.counters) == 0:
                reactor.stop()
        for id in range(len(self.counters)):
            reactor.callWhenRunning(wrapper_count, id)
    

    from twisted.internet import reactor

    reactor.callWhenRunning(Countdown([1,1.2,1.5]).start_count)

    print('Start!')
    reactor.run()
    print('Stop!')

  55. antlers

    I'm finding this really helpful, insightful, and entertaining -- thank you!