SaltyCrane Blog — Notes on JavaScript and web development

Running functions periodically using Twisted's LoopingCall

Twisted is pretty cool-- it is very powerful, but I haven't had the easiest time learning it. Here is a simple example that runs a couple functions periodically (at different rates) using LoopingCall.

For more information, here are the Twisted docs for LoopingCall.

from datetime import datetime
from twisted.internet.task import LoopingCall
from twisted.internet import reactor

def hyper_task():
    print "I like to run fast", datetime.now()

def tired_task():
    print "I want to run slowly", datetime.now()

lc = LoopingCall(hyper_task)
lc.start(0.1)

lc2 = LoopingCall(tired_task)
lc2.start(0.5)

reactor.run()

Results:

I like to run fast 2008-10-14 15:51:02.449537
I want to run slowly 2008-10-14 15:51:02.449915
I like to run fast 2008-10-14 15:51:02.551972
I like to run fast 2008-10-14 15:51:02.652013
I like to run fast 2008-10-14 15:51:02.752006
I like to run fast 2008-10-14 15:51:02.852008
I like to run fast 2008-10-14 15:51:02.952487
I want to run slowly 2008-10-14 15:51:02.952681
I like to run fast 2008-10-14 15:51:03.052012
I like to run fast 2008-10-14 15:51:03.152012
I like to run fast 2008-10-14 15:51:03.252010
I like to run fast 2008-10-14 15:51:03.352009
I like to run fast 2008-10-14 15:51:03.452008
I want to run slowly 2008-10-14 15:51:03.452206
I like to run fast 2008-10-14 15:51:03.552009
I like to run fast 2008-10-14 15:51:03.652013

Using TimerService with twistd

To create a daemon with twistd that achieves the same effect, use TimerService. TimerService runs LoopingCall under the hood. It is meant to be used with the Twisted Application infrastructure. See also the documentation on TimerService.

timerservice_ex.py

from datetime import datetime
from twisted.application import service
from twisted.application.internet import TimerService

def tired_task():
    print "I want to run slowly", datetime.now()

application = service.Application("myapp")
ts = TimerService(0.5, tired_task)
ts.setServiceParent(application)

Run it:

$ twistd -y timerservice_ex.py 

Console output is stored in twistd.log:

2010-09-20 18:53:50-0700 [-] Log opened.
2010-09-20 18:53:50-0700 [-] using set_wakeup_fd
2010-09-20 18:53:50-0700 [-] twistd 10.1.0 (/home/saltycrane/.virtualenvs/default/bin/python 2.6.5) starting up.
2010-09-20 18:53:50-0700 [-] reactor class: twisted.internet.selectreactor.SelectReactor.
2010-09-20 18:53:50-0700 [-] I want to run slowly 2010-09-20 18:53:50.896477
2010-09-20 18:53:51-0700 [-] I want to run slowly 2010-09-20 18:53:51.397043
2010-09-20 18:53:51-0700 [-] I want to run slowly 2010-09-20 18:53:51.897087
2010-09-20 18:53:52-0700 [-] I want to run slowly 2010-09-20 18:53:52.397047
2010-09-20 18:53:52-0700 [-] I want to run slowly 2010-09-20 18:53:52.897068
2010-09-20 18:53:53-0700 [-] I want to run slowly 2010-09-20 18:53:53.397073
2010-09-20 18:53:53-0700 [-] I want to run slowly 2010-09-20 18:53:53.897032
2010-09-20 18:53:54-0700 [-] I want to run slowly 2010-09-20 18:53:54.397083

Comments


#1 Parand commented on :

What's your general impression of Twisted? I'm a fan of the deferred model, but somehow each time I pick up Twisted I leave it aside due to a combination of documentation (lack of), breadth (too much), and general unfamiliarity. I've yet to figure out if Twisted is worth the effort to learn, versus using more conventional models.


#2 Eliot commented on :

hey Parand! I like Twisted... I think it is very powerful. You are right-- the documentation is lacking and it is difficult to learn. Bruce Eckels, among others, have noted this as well. I don't have a huge amount of experience with the alternatives, but I think Twisted's fundamental functionality (i.e. Deferreds and the event loop) are pretty cool. I'd imagine there are areas where this design shines, and areas where an alternative is the right choice. I am looking forward to learning more Twisted though.

I am still figuring out how to use everything properly in Twisted. For example, in my current project, I just incorporated a beanstalkd message queue (thanks to your post!) with my existing Twisted setup. However, the design doesn't seem quite right-- beanstalkd seems to provide redundant functionality, but I'm not sure the proper way to implement this in pure Twisted. I tried searching for Twisted message queue but couldn't find much. I will continue to look into it and post any new findings.


#3 Parand Darugar commented on :

Exactly, Twisted and messaging have overlapping functionality. I'm trying to figure out if using a message queue gives me the important 90% of the functionality of Twisted with an easier programming model, of if I should bite the bullet and figure out Twiested.


#4 Eliot commented on :

My stuff spends a lot of time waiting on I/O so I think Twisted can help me speed up these things.


#5 Alfredo commented on :

Hi all, i'm a absolute beginner with Twisted anf asynchronous programming, but i look at your code and i do not want use LoopingCall object; also i looked at Stomp and Message Queue Architecture from the tutorial at "http://cometdaily.com/2008/10/10/scalable-real-time-web-architecture-part-2-a-live-graph-with-orbited-morbidq-and-jsio/". That tutorial showcases a data broadcast (Simple Real-Time Graph) where data is produced from a "data_producer.py" launched on the server and sent to the connected client towards stomp channels (using Twisted's LoopingCall object) every second, then in synchronous way.

There was the code (from "data_producer.py") that use also LoopingCall functions:

+-------------------------------------+
class DataProducer(StompClientFactory):
    def recv_connected(self, msg):
        print 'Connected; producing data'
        self.data = [ 
            int(random()*MAX_VALUE) 
            for 
            x in xrange(DATA_VECTOR_LENGTH)
        ]
        self.timer = LoopingCall(self.send_data)
        self.timer.start(INTERVAL/1000.0)

    def send_data(self):
        # modify our data elements
        self.data = [ 
            min(max(datum+(random()-.5)*DELTA_WEIGHT*MAX_VALUE,0),MAX_VALUE)
            for 
            datum in self.data
        ]
        self.send(CHANNEL_NAME, json.encode(self.data))

reactor.connectTCP('localhost', 61613, DataProducer())
reactor.run()
+-------------------------------------+

Now, i saw your examples, however i want to modify that code from the tutorial to realize monitoring session that broadcasts data generated from data_producer in asynchronous way, as soon as they were received from the outside world and immediately processed towards the stomp channels. I tried to modify "recv_message" and "send_data" defs and make it work, but with no success: data were generated and shown onto the stdout but not sent towards connected clients, with orbited, python stomper's or stompservices' example programs (stompbuffer-rx.py), etc.

Can you help me to undestand with simple examples how handling Twisted functions to implement these asynchronous capabilities without using LoopingCall object?

There is my simple code: . I looked at

+-------------------------------------+
class DataProducer(StompClientFactory):
    def recv_connected(self, msg):
        # Once connected, I want to subscribe to my the message queue
        self.data = "Initialize..."
        # What goes now at this point?   <------------
        ???????????  #    <---------------------------

    def send_data(self):
        try:
           while 1:
               # Read data (this is a string) from outside world
               frame=RecData(smon)   
               # Show it towards stdout 
               WriteLog(frame)
               # 
               self.data=frame
               self.send("broadcast/monitor", json.encode(self.data))
        except ...


reactor.connectTCP('localhost', 61613, DataProducer())
reactor.run()
+-------------------------------------+

Thanks for your appreciated help.

Alfredo


#6 sally commented on :

hello all: I used twisted and udp protocol,and set a LoopingCall which will run every minute to insert some data to mysql, but now I find the Loop only run for about 20 minutes and never run again! so what's wrong? Can someone help me ? Thanks!