views:

223

answers:

2

Hi, all!
Having 1-day experience in Twisted I try to schedule message sending in reply to tcp client:

import os, sys, time
from twisted.internet import protocol, reactor

self.scenario = [(1, "Message after 1 sec!"), (4, "This after 4 secs"), (2, "End final after 2 secs")]
for timeout, data in self.scenario:
        reactor.callLater(timeout, self.sendata, data)
        print "waited %d time, sent %s\n"%(timeout, data)

Now it sends messages, but I have 2 problems:
1) "timeout" is going from "now", and I want to count it after each previous task was completed (previous message was sent)
2) I don't know how to close connection after all messages were sent. If I place self.transport.loseConnection() after callLaters it closes connection immediately.

In previous try I didn't use reactor.callLater, but only self.transport.write() and time.sleep(n) in for loop. In this case all messages were sent together after all timeouts passed... Not something I wanted.
The purpose is to wait for client connection, wait timeout1 and send message1, wait timeout2 and send message2, ... etc. After final message - close connection.

+2  A: 

The important thing to realize when working with Twisted is that nothing waits for anything. When you call reactor.callLater(), you're asking the reactor to call something later, not now. The call finishes right away (after the call has been scheduled, before it has been executed.) Consequently, your print statement is a lie: you didn't wait timeout time; you didn't wait at all.

You can fix it in multiple ways, and which to use depends on what you actually want. If you want the second task to start four seconds after the first task started, you can simply add the delay (your timeout variable) of the first task to the delay of the second task. The first task may not start exactly when you schedule it, though; it may start later, if Twisted is too busy to start it sooner. Also, if your task takes a long time it may not actually be done before the second task starts.

The more common way is for the first task to schedule the second task, instead of scheduling the second task right away. You can schedule it four seconds after the first task ended (by calling reactor.callLater() at the end of the first task), or four seconds after the first task started (by calling reactor.callLater() at the start of the first task), or perform more complex calculations to determine when it should start, keeping track of elapsed time.

When you realize nothing in Twisted waits, dealing with closing the connection when you've performed all scheduled tasks becomes easy: you simply have your last task call self.transport.loseConnection(). For more complex situations you may want to chain Deferreds together, or use a DeferredList to perform the loseConnection() when all pending tasks have finished, even when they aren't strictly sequential.

Thomas Wouters
Thanks, now I understand why "sleeps" didn't work. Can you give an example with scheduling reactor.callLater() at the end of the previous reactor.callLater()?
DominiCane
Just define a function that calls `self.sendata(data)` and then calls `reactor.callLater()` for the next callback, and pass that function to the first `reactor.callLater()` instead of `self.sendata`
Thomas Wouters
A: 

Final solution for this deal..

import os, sys, time
from twisted.internet import protocol, reactor
import itertools

def sendScenario(self):
    def sendelayed(d):
        self.sendata(d)
        self.factory.out_dump.write(d)
        try:
            timeout, data = next(self.sc)
            reactor.callLater(timeout, sendelayed, data)
        except StopIteration:
            print "Scenario completed!"
            self.transport.loseConnection()

    self.scenario = [(1, "Message after 1 sec!"), (4, "This after 4 secs"), (2, "End final after 2 secs")]
    self.sc = iter(self.scenario)
    timeout, data = next(self.sc)
    reactor.callLater(timeout, sendelayed, data)
DominiCane
Just fyi: `self.scenario.__iter__()` -> `iter(self.scenario)`, `self.sc.next()` -> `next(self.sc)` (since 2.6 i think)
THC4k
Thanks, changed it.
DominiCane