1 | /*
|
2 | Copyright 2016 OpenMarket Ltd
|
3 |
|
4 | Licensed under the Apache License, Version 2.0 (the "License");
|
5 | you may not use this file except in compliance with the License.
|
6 | You may obtain a copy of the License at
|
7 |
|
8 | http://www.apache.org/licenses/LICENSE-2.0
|
9 |
|
10 | Unless required by applicable law or agreed to in writing, software
|
11 | distributed under the License is distributed on an "AS IS" BASIS,
|
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13 | See the License for the specific language governing permissions and
|
14 | limitations under the License.
|
15 | */
|
16 |
|
17 | /* A re-implementation of the javascript callback functions (setTimeout,
|
18 | * clearTimeout; setInterval and clearInterval are not yet implemented) which
|
19 | * try to improve handling of large clock jumps (as seen when
|
20 | * suspending/resuming the system).
|
21 | *
|
22 | * In particular, if a timeout would have fired while the system was suspended,
|
23 | * it will instead fire as soon as possible after resume.
|
24 | */
|
25 |
|
26 | ;
|
27 |
|
28 | // we schedule a callback at least this often, to check if we've missed out on
|
29 | // some wall-clock time due to being suspended.
|
30 |
|
31 | var TIMER_CHECK_PERIOD_MS = 1000;
|
32 |
|
33 | // counter, for making up ids to return from setTimeout
|
34 | var _count = 0;
|
35 |
|
36 | // the key for our callback with the real global.setTimeout
|
37 | var _realCallbackKey = void 0;
|
38 |
|
39 | // a sorted list of the callbacks to be run.
|
40 | // each is an object with keys [runAt, func, params, key].
|
41 | var _callbackList = [];
|
42 |
|
43 | // var debuglog = console.log.bind(console);
|
44 | var debuglog = function debuglog() {};
|
45 |
|
46 | /**
|
47 | * Replace the function used by this module to get the current time.
|
48 | *
|
49 | * Intended for use by the unit tests.
|
50 | *
|
51 | * @param {function} f function which should return a millisecond counter
|
52 | *
|
53 | * @internal
|
54 | */
|
55 | module.exports.setNow = function (f) {
|
56 | _now = f || Date.now;
|
57 | };
|
58 | var _now = Date.now;
|
59 |
|
60 | /**
|
61 | * reimplementation of window.setTimeout, which will call the callback if
|
62 | * the wallclock time goes past the deadline.
|
63 | *
|
64 | * @param {function} func callback to be called after a delay
|
65 | * @param {Number} delayMs number of milliseconds to delay by
|
66 | *
|
67 | * @return {Number} an identifier for this callback, which may be passed into
|
68 | * clearTimeout later.
|
69 | */
|
70 | module.exports.setTimeout = function (func, delayMs) {
|
71 | delayMs = delayMs || 0;
|
72 | if (delayMs < 0) {
|
73 | delayMs = 0;
|
74 | }
|
75 |
|
76 | var params = Array.prototype.slice.call(arguments, 2);
|
77 | var runAt = _now() + delayMs;
|
78 | var key = _count++;
|
79 | debuglog("setTimeout: scheduling cb", key, "at", runAt, "(delay", delayMs, ")");
|
80 | var data = {
|
81 | runAt: runAt,
|
82 | func: func,
|
83 | params: params,
|
84 | key: key
|
85 | };
|
86 |
|
87 | // figure out where it goes in the list
|
88 | var idx = binarySearch(_callbackList, function (el) {
|
89 | return el.runAt - runAt;
|
90 | });
|
91 |
|
92 | _callbackList.splice(idx, 0, data);
|
93 | _scheduleRealCallback();
|
94 |
|
95 | return key;
|
96 | };
|
97 |
|
98 | /**
|
99 | * reimplementation of window.clearTimeout, which mirrors setTimeout
|
100 | *
|
101 | * @param {Number} key result from an earlier setTimeout call
|
102 | */
|
103 | module.exports.clearTimeout = function (key) {
|
104 | if (_callbackList.length === 0) {
|
105 | return;
|
106 | }
|
107 |
|
108 | // remove the element from the list
|
109 | var i = void 0;
|
110 | for (i = 0; i < _callbackList.length; i++) {
|
111 | var cb = _callbackList[i];
|
112 | if (cb.key == key) {
|
113 | _callbackList.splice(i, 1);
|
114 | break;
|
115 | }
|
116 | }
|
117 |
|
118 | // iff it was the first one in the list, reschedule our callback.
|
119 | if (i === 0) {
|
120 | _scheduleRealCallback();
|
121 | }
|
122 | };
|
123 |
|
124 | // use the real global.setTimeout to schedule a callback to _runCallbacks.
|
125 | function _scheduleRealCallback() {
|
126 | if (_realCallbackKey) {
|
127 | global.clearTimeout(_realCallbackKey);
|
128 | }
|
129 |
|
130 | var first = _callbackList[0];
|
131 |
|
132 | if (!first) {
|
133 | debuglog("_scheduleRealCallback: no more callbacks, not rescheduling");
|
134 | return;
|
135 | }
|
136 |
|
137 | var now = _now();
|
138 | var delayMs = Math.min(first.runAt - now, TIMER_CHECK_PERIOD_MS);
|
139 |
|
140 | debuglog("_scheduleRealCallback: now:", now, "delay:", delayMs);
|
141 | _realCallbackKey = global.setTimeout(_runCallbacks, delayMs);
|
142 | }
|
143 |
|
144 | function _runCallbacks() {
|
145 | var cb = void 0;
|
146 | var now = _now();
|
147 | debuglog("_runCallbacks: now:", now);
|
148 |
|
149 | // get the list of things to call
|
150 | var callbacksToRun = [];
|
151 | while (true) {
|
152 | var first = _callbackList[0];
|
153 | if (!first || first.runAt > now) {
|
154 | break;
|
155 | }
|
156 | cb = _callbackList.shift();
|
157 | debuglog("_runCallbacks: popping", cb.key);
|
158 | callbacksToRun.push(cb);
|
159 | }
|
160 |
|
161 | // reschedule the real callback before running our functions, to
|
162 | // keep the codepaths the same whether or not our functions
|
163 | // register their own setTimeouts.
|
164 | _scheduleRealCallback();
|
165 |
|
166 | for (var i = 0; i < callbacksToRun.length; i++) {
|
167 | cb = callbacksToRun[i];
|
168 | try {
|
169 | cb.func.apply(global, cb.params);
|
170 | } catch (e) {
|
171 | console.error("Uncaught exception in callback function", e.stack || e);
|
172 | }
|
173 | }
|
174 | }
|
175 |
|
176 | /* search in a sorted array.
|
177 | *
|
178 | * returns the index of the last element for which func returns
|
179 | * greater than zero, or array.length if no such element exists.
|
180 | */
|
181 | function binarySearch(array, func) {
|
182 | // min is inclusive, max exclusive.
|
183 | var min = 0,
|
184 | max = array.length;
|
185 |
|
186 | while (min < max) {
|
187 | var mid = min + max >> 1;
|
188 | var res = func(array[mid]);
|
189 | if (res > 0) {
|
190 | // the element at 'mid' is too big; set it as the new max.
|
191 | max = mid;
|
192 | } else {
|
193 | // the element at 'mid' is too small. 'min' is inclusive, so +1.
|
194 | min = mid + 1;
|
195 | }
|
196 | }
|
197 | // presumably, min==max now.
|
198 | return min;
|
199 | }
|
200 | //# sourceMappingURL=realtime-callbacks.js.map |
\ | No newline at end of file |