util.timer: Expire timer instance if another instance is already set to take care...
[prosody.git] / util / timer.lua
1 -- Prosody IM
2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 Waqas Hussain
4 --
5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information.
7 --
8
9 local indexedbheap = require "util.indexedbheap";
10 local log = require "util.logger".init("timer");
11 local server = require "net.server";
12 local get_time = require "socket".gettime;
13 local type = type;
14 local debug_traceback = debug.traceback;
15 local tostring = tostring;
16 local xpcall = xpcall;
17
18 local _ENV = nil;
19
20 local _add_task = server.add_task;
21
22 local _active_timers = 0;
23 local h = indexedbheap.create();
24 local params = {};
25 local next_time = nil;
26 local _id, _callback, _now, _param;
27 local function _call() return _callback(_now, _id, _param); end
28 local function _traceback_handler(err) log("error", "Traceback[timer]: %s", debug_traceback(tostring(err), 2)); end
29 local function _on_timer(now)
30         local peek;
31         while true do
32                 peek = h:peek();
33                 if peek == nil or peek > now then break; end
34                 local _;
35                 _, _callback, _id = h:pop();
36                 _now = now;
37                 _param = params[_id];
38                 params[_id] = nil;
39                 --item(now, id, _param); -- FIXME pcall
40                 local success, err = xpcall(_call, _traceback_handler);
41                 if success and type(err) == "number" then
42                         h:insert(_callback, err + now, _id); -- re-add
43                         params[_id] = _param;
44                 end
45         end
46
47         if peek ~= nil and _active_timers > 1 and peek == next_time then
48                 -- Another instance of _on_timer already set next_time to the same value,
49                 -- so it should be safe to not renew this timer event
50                 peek = nil;
51         else
52                 next_time = peek;
53         end
54
55         if peek then
56                 -- peek is the time of the next event
57                 return peek - now;
58         end
59         _active_timers = _active_timers - 1;
60 end
61 local function add_task(delay, callback, param)
62         local current_time = get_time();
63         local event_time = current_time + delay;
64
65         local id = h:insert(callback, event_time);
66         params[id] = param;
67         if next_time == nil or event_time < next_time then
68                 next_time = event_time;
69                 _active_timers = _active_timers + 1;
70                 _add_task(next_time - current_time, _on_timer);
71         end
72         return id;
73 end
74 local function stop(id)
75         params[id] = nil;
76         return h:remove(id);
77 end
78 local function reschedule(id, delay)
79         local current_time = get_time();
80         local event_time = current_time + delay;
81         h:reprioritize(id, delay);
82         if next_time == nil or event_time < next_time then
83                 next_time = event_time;
84                 _add_task(next_time - current_time, _on_timer);
85         end
86         return id;
87 end
88
89 return {
90         add_task = add_task;
91         stop = stop;
92         reschedule = reschedule;
93 };
94