-
Notifications
You must be signed in to change notification settings - Fork 13
/
timer-polyfill.js
170 lines (138 loc) · 5.29 KB
/
timer-polyfill.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
/*
Source is originated from https://github.com/morungos/java-xmlhttprequest
Articles about Nashorn:
- https://blog.codecentric.de/en/2014/06/project-nashorn-javascript-jvm-polyglott/
How it work:
in https://github.com/morungos/java-xmlhttprequest, it uses Timer to run setTimeout and setInterval task,
but they are run in a separate thread of the Timer creates that is different with the main JavaScript thread.
This implementation uses ScheduledExecutorService instead of Timer so the threads for task scheduling can be
reused instead of each JavasScript thread create a Timer thread when using Timer.
And most important thing is this adds global.nashornEventLoop and scheduled tasks only add function callback
object in eventLoop (ArrayQueue), and it is main JavaScript thread to run these function callback by calling
`global.nashornEventLoop.process();` at the end of JavaScript Application. It is just like browser or NodeJS
that event loop is called when the main stack is cleared.
When runs on server with Promise, remember to call `nashornEventLoop.process()` when waiting for Promise by
Thread.sleep(), and call `nashornEventLoop.reset()` if server thread (e.g. Servlet thread) decides to be
timeout so that eventLoop will be clean for next request.
*/
(function nashornEventLoopMain(context) {
'use strict';
var Thread = Java.type('java.lang.Thread');
var Phaser = Java.type('java.util.concurrent.Phaser');
var ArrayDeque = Java.type('java.util.ArrayDeque');
var HashMap = Java.type('java.util.HashMap');
var TimeUnit = Java.type("java.util.concurrent.TimeUnit");
var Runnable = Java.type('java.lang.Runnable');
var globalTimerId;
var timerMap;
var eventLoop;
var phaser = new Phaser();
// __NASHORN_POLYFILL_TIMER__ type is ScheduledExecutorService
var scheduler = context.__NASHORN_POLYFILL_TIMER__;
resetEventLoop();
// console.log('main javasript thread ' + Thread.currentThread().getName());
function resetEventLoop() {
globalTimerId = 1;
if (timerMap) {
timerMap.forEach(function (key, value) {
value.cancel(true);
})
}
timerMap = new HashMap();
eventLoop = new ArrayDeque();
}
function waitForMessages() {
phaser.register();
var wait = !(eventLoop.size() === 0);
phaser.arriveAndDeregister();
return wait;
}
function processNextMessages() {
var remaining = 1;
while (remaining) {
// console.log('eventLoop size ' + eventLoop.size() + 'in thread ' + Thread.currentThread().getName());
phaser.register();
var message = eventLoop.removeFirst();
remaining = eventLoop.size();
phaser.arriveAndDeregister();
var fn = message.fn;
var args = message.args;
try {
// console.log('processNextMessages in thread ' + Thread.currentThread().getName());
fn.apply(context, args);
} catch (e) {
console.trace(e);
console.trace(fn);
console.trace(args);
}
}
}
context.nashornEventLoop = {
process: function () {
// console.log('nashornEventLoop.process is called in thread ' + Thread.currentThread().getName())
while (waitForMessages()) {
processNextMessages()
}
},
reset: resetEventLoop
};
function createRunnable(fn, timerId, args, repeated) {
return new Runnable {
run: function () {
try {
var phase = phaser.register();
eventLoop.addLast({
fn: fn,
args: args
});
// console.log('TimerTask add one event, and eventLoop size ' + eventLoop.size() +
// ' in thread ' + Thread.currentThread().getName());
} catch (e) {
console.trace(e);
} finally {
if (!repeated) timerMap.remove(timerId);
phaser.arriveAndDeregister();
}
}
}
}
var setTimeout = function (fn, millis /* [, args...] */) {
var args = [].slice.call(arguments, 2, arguments.length);
var timerId = globalTimerId++;
var runnable = createRunnable(fn, timerId, args, false);
var task = scheduler.schedule(runnable, millis, TimeUnit.MILLISECONDS);
timerMap.put(timerId, task);
return timerId;
};
var setImmediate = function (fn /* [, args...] */) {
var args = [].slice.call(arguments, 1, arguments.length);
return setTimeout(fn, 0, args);
}
var clearImmediate = function (timerId) {
clearTimeout(timerId);
}
var clearTimeout = function (timerId) {
var task = timerMap.get(timerId);
if (task) {
task.cancel(true);
timerMap.remove(timerId);
}
};
var setInterval = function (fn, delay /* [, args...] */) {
var args = [].slice.call(arguments, 2, arguments.length);
var timerId = globalTimerId++;
var runnable = createRunnable(fn, timerId, args, true);
var task = scheduler.scheduleWithFixedDelay(runnable, delay, delay, TimeUnit.MILLISECONDS);
timerMap.put(timerId, task);
return timerId;
};
var clearInterval = function (timerId) {
clearTimeout(timerId);
};
context.setTimeout = setTimeout;
context.clearTimeout = clearTimeout;
context.setImmediate = setImmediate;
context.clearImmediate = clearImmediate;
context.setInterval = setInterval;
context.clearInterval = clearInterval;
})(typeof global !== "undefined" && global || typeof self !== "undefined" && self || this);