# Copyright (c) 2006-2007 Sippy Software, Inc. All rights reserved.
#
# This file is part of SIPPY, a free RFC3261 SIP stack and B2BUA.
#
# SIPPY is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# For a license to use the SIPPY software under conditions
# other than those described here, or to purchase support for this
# software, please contact Sippy Software, Inc. by e-mail at the
# following addresses: sales@sippysoft.com.
#
# SIPPY is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
#
# $Id: Timeout.py,v 1.4 2009/01/05 20:14:00 sobomax Exp $

from datetime import datetime
from twisted.internet import task
from traceback import print_exc, format_list, extract_stack
from sys import stdout

class Timeout(object):
    _task = None
    _ticks_left = None
    _timeout_callback = None

    def __init__(self, timeout_callback, interval, ticks = 1, *callback_arguments):
        self._ticks_left = ticks
        self._timeout_callback = timeout_callback
        self._task = task.LoopingCall(self._run, *callback_arguments)
        self._task.start(interval, False)

    def _run(self, *callback_arguments):
        try:
            self._timeout_callback(*callback_arguments)
        except:
            print datetime.now(), 'Timeout: unhandled exception in timeout callback'
            print '-' * 70
            print_exc(file = stdout)
            print '-' * 70
            stdout.flush()
        if self._ticks_left == 1:
            self.cancel()
        elif self._ticks_left != -1:
            self._ticks_left -= 1

    def cancel(self):
        self._task.stop()
        self._task = None
        self._timeout_callback = None

class Timeout_debug(Timeout):
    _traceback = None

    def __init__(self, *parameters, **kparameters):
        self._traceback = format_list(extract_stack())
        Timeout.__init__(self, *parameters, **kparameters)

    def cancel(self):
        if self._task == None:
            print self._traceback
        Timeout.cancel(self)

if __name__ == '__main__':
    from twisted.internet import reactor
    
    def test1(arguments):
        print 'test1'
        arguments['test'] = True
        reactor.crash()

    def test2(arguments):
        print 'test2'
        arguments['test'] = 'bar'
        reactor.crash()

    arguments = {'test':False}
    timeout_1 = Timeout(test1, 0, 1, arguments)
    reactor.run()
    assert(arguments['test'])
    timeout_1 = Timeout(test1, 0.1, 1, arguments)
    timeout_2 = Timeout(test2, 0.2, 1, arguments)
    timeout_1.cancel()
    reactor.run()
    assert(arguments['test'] == 'bar')

@@ -1083,29 +1083,29 @@
 v 1.
-4
+5
  2009/
-01/05 20:14:00
+11/19 09:23:07
  sob
@@ -1179,16 +1179,25 @@
 ort task
+, reactor
 %0Afrom tr
@@ -1442,32 +1442,259 @@
 ack_arguments):%0A
+        if ticks == 1:%0A            # Special case for just one call%0A            self._task = reactor.callLater(interval, timeout_callback, *callback_arguments)%0A            self.cancel = self.cancel_callLater%0A            return%0A
         self._ti
@@ -2447,16 +2447,450 @@
 = None%0A%0A
+    def cancel_callLater(self):%0A        self._task.cancel()%0A        self._task = None%0A%0Aclass TimeoutAbs:%0A    _task = None%0A    def __init__(self, timeout_callback, etime, *callback_arguments):%0A        etime -= reactor.seconds()%0A        if etime %3C 0:%0A            etime = 0%0A        self._task = reactor.callLater(etime, timeout_callback, *callback_arguments)%0A%0A    def cancel(self):%0A        self._task.cancel()%0A        self._task = None%0A%0A
 class Ti
@@ -3306,32 +3306,41 @@
  test1(arguments
+, testnum
 ):%0A        print
@@ -3344,15 +3344,15 @@
 int 
-'
 test
-1'
+num
 %0A   
@@ -3421,32 +3421,41 @@
  test2(arguments
+, testnum
 ):%0A        print
@@ -3459,15 +3459,15 @@
 int 
-'
 test
-2'
+num
 %0A   
@@ -3595,24 +3595,33 @@
 1, arguments
+, 'test1'
 )%0A    reacto
@@ -3698,32 +3698,41 @@
 .1, 1, arguments
+, 'test2'
 )%0A    timeout_2 
@@ -3761,24 +3761,425 @@
 1, arguments
+, 'test3')%0A    timeout_1.cancel()%0A    reactor.run()%0A    assert(arguments%5B'test'%5D == 'bar')%0A%0A    arguments = %7B'test':False%7D%0A    timeout_1 = TimeoutAbs(test1, reactor.seconds(), arguments, 'test4')%0A    reactor.run()%0A    assert(arguments%5B'test'%5D)%0A%0A    timeout_1 = TimeoutAbs(test1, reactor.seconds() + 0.1, arguments, 'test5')%0A    timeout_2 = TimeoutAbs(test2, reactor.seconds() + 0.2, arguments, 'test6'
 )%0A    timeou

Add TimeoutAbs - version of Timeout class that takes absolute time instead of relative and fires only once. Based on that also add optimized version of Timeout that only need to fire once.