]>
git.proxmox.com Git - ceph.git/blob - ceph/src/civetweb/src/third_party/duktape-1.3.0/examples/eventloop/ecma_eventloop.js
2 * Pure Ecmascript eventloop example.
4 * Timer state handling is inefficient in this trivial example. Timers are
5 * kept in an array sorted by their expiry time which works well for expiring
6 * timers, but has O(n) insertion performance. A better implementation would
7 * use a heap or some other efficient structure for managing timers so that
8 * all operations (insert, remove, get nearest timer) have good performance.
10 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Timers
16 * Timers are sorted by 'target' property which indicates expiry time of
17 * the timer. The timer expiring next is last in the array, so that
18 * removals happen at the end, and inserts for timers expiring in the
19 * near future displace as few elements in the array as possible.
24 timers
: [], // active timers, sorted (nearest expiry last)
25 expiring
: null, // set to timer being expired (needs special handling in clearTimeout/clearInterval)
33 socketListening
: {}, // fd -> callback
34 socketReading
: {}, // fd -> callback
35 socketConnecting
: {}, // fd -> callback
41 EventLoop
.dumpState = function() {
42 print('TIMER STATE:');
43 this.timers
.forEach(function(t
) {
44 print(' ' + Duktape
.enc('jx', t
));
47 print(' EXPIRING: ' + Duktape
.enc('jx', this.expiring
));
51 // Get timer with lowest expiry time. Since the active timers list is
52 // sorted, it's always the last timer.
53 EventLoop
.getEarliestTimer = function() {
54 var timers
= this.timers
;
56 return (n
> 0 ? timers
[n
- 1] : null);
59 EventLoop
.getEarliestWait = function() {
60 var t
= this.getEarliestTimer();
61 return (t
? t
.target
- Date
.now() : null);
64 EventLoop
.insertTimer = function(timer
) {
65 var timers
= this.timers
;
69 * Find 'i' such that we want to insert *after* timers[i] at index i+1.
70 * If no such timer, for-loop terminates with i-1, and we insert at -1+1=0.
74 for (i
= n
- 1; i
>= 0; i
--) {
76 if (timer
.target
<= t
.target
) {
77 // insert after 't', to index i+1
82 timers
.splice(i
+ 1 /*start*/, 0 /*deleteCount*/, timer
);
85 // Remove timer/interval with a timer ID. The timer/interval can reside
86 // either on the active list or it may be an expired timer (this.expiring)
87 // whose user callback we're running when this function gets called.
88 EventLoop
.removeTimerById = function(timer_id
) {
89 var timers
= this.timers
;
94 if (t
.id
=== timer_id
) {
95 // Timer has expired and we're processing its callback. User
96 // callback has requested timer deletion. Mark removed, so
97 // that the timer is not reinserted back into the active list.
98 // This is actually a common case because an interval may very
99 // well cancel itself.
106 for (i
= 0; i
< n
; i
++) {
108 if (t
.id
=== timer_id
) {
109 // Timer on active list: mark removed (not really necessary, but
110 // nice for dumping), and remove from active list.
112 this.timers
.splice(i
/*start*/, 1 /*deleteCount*/);
117 // no such ID, ignore
120 EventLoop
.processTimers = function() {
121 var now
= Date
.now();
122 var timers
= this.timers
;
123 var sanity
= this.maxExpirys
;
127 * Here we must be careful with mutations: user callback may add and
128 * delete an arbitrary number of timers.
130 * Current solution is simple: check whether the timer at the end of
131 * the list has expired. If not, we're done. If it has expired,
132 * remove it from the active list, record it in this.expiring, and call
133 * the user callback. If user code deletes the this.expiring timer,
134 * there is special handling which just marks the timer deleted so
135 * it won't get inserted back into the active list.
137 * This process is repeated at most maxExpirys times to ensure we don't
138 * get stuck forever; user code could in principle add more and more
139 * already expired timers.
142 while (sanity
-- > 0) {
143 // If exit requested, don't call any more callbacks. This allows
144 // a callback to do cleanups and request exit, and can be sure that
145 // no more callbacks are processed.
147 if (this.exitRequested
) {
148 //print('exit requested, exit');
159 if (now
<= t
.target
) {
160 // Timer has not expired, and no other timer could have expired
161 // either because the list is sorted.
166 // Remove the timer from the active list and process it. The user
167 // callback may add new timers which is not a problem. The callback
168 // may also delete timers which is not a problem unless the timer
169 // being deleted is the timer whose callback we're running; this is
170 // why the timer is recorded in this.expiring so that clearTimeout()
171 // and clearInterval() can detect this situation.
174 t
.removed
= true; // flag for removal
176 t
.target
= now
+ t
.delay
;
182 print('timer callback failed, ignored: ' + e
);
184 this.expiring
= null;
186 // If the timer was one-shot, it's marked 'removed'. If the user callback
187 // requested deletion for the timer, it's also marked 'removed'. If the
188 // timer is an interval (and is not marked removed), insert it back into
192 // Reinsert interval timer to correct sorted position. The timer
193 // must be an interval timer because one-shot timers are marked
200 EventLoop
.run = function() {
202 var POLLIN
= Poll
.POLLIN
;
203 var POLLOUT
= Poll
.POLLOUT
;
213 * Process expired timers.
216 this.processTimers();
220 * Exit check (may be requested by a user callback)
223 if (this.exitRequested
) {
224 //print('exit requested, exit');
229 * Create poll socket list. This is a very naive approach.
230 * On Linux, one could use e.g. epoll() and manage socket lists
236 for (fd
in this.socketListening
) {
237 poll_set
[fd
] = { events
: POLLIN
, revents
: 0 };
240 for (fd
in this.socketReading
) {
241 poll_set
[fd
] = { events
: POLLIN
, revents
: 0 };
244 for (fd
in this.socketConnecting
) {
245 poll_set
[fd
] = { events
: POLLOUT
, revents
: 0 };
248 //print(new Date(), 'poll_set IN:', Duktape.enc('jx', poll_set));
251 * Wait timeout for timer closest to expiry. Since the poll
252 * timeout is relative, get this as close to poll() as possible.
255 wait
= this.getEarliestWait();
257 if (poll_count
=== 0) {
258 print('no active timers and no sockets to poll, exit');
261 wait
= this.maximumWait
;
264 wait
= Math
.min(this.maximumWait
, Math
.max(this.minimumWait
, wait
));
268 * Do the actual poll.
272 Poll
.poll(poll_set
, wait
);
274 // Eat errors silently. When resizing curses window an EINTR
279 * Process all sockets so that nothing is left unhandled for the
283 //print(new Date(), 'poll_set OUT:', Duktape.enc('jx', poll_set));
284 for (fd
in poll_set
) {
289 cb
= this.socketReading
[fd
];
291 data
= Socket
.read(fd
); // no size control now
292 //print('READ', Duktape.enc('jx', data));
293 if (data
.length
=== 0) {
294 //print('zero read for fd ' + fd + ', closing forcibly');
295 rc
= Socket
.close(fd
); // ignore result
296 delete this.socketListening
[fd
];
297 delete this.socketReading
[fd
];
302 cb
= this.socketListening
[fd
];
304 acc_res
= Socket
.accept(fd
);
305 //print('ACCEPT:', Duktape.enc('jx', acc_res));
306 cb(acc_res
.fd
, acc_res
.addr
, acc_res
.port
);
314 cb
= this.socketConnecting
[fd
];
316 delete this.socketConnecting
[fd
];
319 //print('UNKNOWN POLLOUT');
323 if ((rev
& ~(POLLIN
| POLLOUT
)) !== 0) {
324 //print('revents ' + t.revents + ' for fd ' + fd + ', closing forcibly');
325 rc
= Socket
.close(fd
); // ignore result
326 delete this.socketListening
[fd
];
327 delete this.socketReading
[fd
];
333 EventLoop
.requestExit = function() {
334 this.exitRequested
= true;
337 EventLoop
.server = function(address
, port
, cb_accepted
) {
338 var fd
= Socket
.createServerSocket(address
, port
);
339 this.socketListening
[fd
] = cb_accepted
;
342 EventLoop
.connect = function(address
, port
, cb_connected
) {
343 var fd
= Socket
.connect(address
, port
);
344 this.socketConnecting
[fd
] = cb_connected
;
347 EventLoop
.close = function(fd
) {
348 delete this.socketReading
[fd
];
349 delete this.socketListening
[fd
];
352 EventLoop
.setReader = function(fd
, cb_read
) {
353 this.socketReading
[fd
] = cb_read
;
356 EventLoop
.write = function(fd
, data
) {
357 // This simple example doesn't have support for write blocking / draining
358 var rc
= Socket
.write(fd
, Duktape
.Buffer(data
));
364 * These interface with the singleton EventLoop.
367 function setTimeout(func
, delay
) {
371 var evloop
= EventLoop
;
373 if (typeof delay
!== 'number') {
374 throw new TypeError('delay is not a number');
376 delay
= Math
.max(evloop
.minimumDelay
, delay
);
378 if (typeof func
=== 'string') {
379 // Legacy case: callback is a string.
380 cb_func
= eval
.bind(this, func
);
381 } else if (typeof func
!== 'function') {
382 throw new TypeError('callback is not a function/string');
383 } else if (arguments
.length
> 2) {
384 // Special case: callback arguments are provided.
385 bind_args
= Array
.prototype.slice
.call(arguments
, 2); // [ arg1, arg2, ... ]
386 bind_args
.unshift(this); // [ global(this), arg1, arg2, ... ]
387 cb_func
= func
.bind
.apply(func
, bind_args
);
389 // Normal case: callback given as a function without arguments.
393 timer_id
= evloop
.nextTimerId
++;
400 target
: Date
.now() + delay
406 function clearTimeout(timer_id
) {
407 var evloop
= EventLoop
;
409 if (typeof timer_id
!== 'number') {
410 throw new TypeError('timer ID is not a number');
412 evloop
.removeTimerById(timer_id
);
415 function setInterval(func
, delay
) {
419 var evloop
= EventLoop
;
421 if (typeof delay
!== 'number') {
422 throw new TypeError('delay is not a number');
424 delay
= Math
.max(evloop
.minimumDelay
, delay
);
426 if (typeof func
=== 'string') {
427 // Legacy case: callback is a string.
428 cb_func
= eval
.bind(this, func
);
429 } else if (typeof func
!== 'function') {
430 throw new TypeError('callback is not a function/string');
431 } else if (arguments
.length
> 2) {
432 // Special case: callback arguments are provided.
433 bind_args
= Array
.prototype.slice
.call(arguments
, 2); // [ arg1, arg2, ... ]
434 bind_args
.unshift(this); // [ global(this), arg1, arg2, ... ]
435 cb_func
= func
.bind
.apply(func
, bind_args
);
437 // Normal case: callback given as a function without arguments.
441 timer_id
= evloop
.nextTimerId
++;
448 target
: Date
.now() + delay
454 function clearInterval(timer_id
) {
455 var evloop
= EventLoop
;
457 if (typeof timer_id
!== 'number') {
458 throw new TypeError('timer ID is not a number');
460 evloop
.removeTimerById(timer_id
);
464 function requestEventLoopExit() {
465 EventLoop
.requestExit();