Skip to content

Commit b4426f8

Browse files
author
Eric Prud'hommeaux
committed
~ replace Promise.race with embedded timeout
1 parent 0711a78 commit b4426f8

File tree

3 files changed

+67
-36
lines changed

3 files changed

+67
-36
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "timeout-promise-queue",
3-
"version": "0.9.1",
3+
"version": "0.9.2",
44
"description": "Promise queue with timeouts and promise cleanup after expiration.",
55
"repository": "https://github.com/ericprud/timeout-promise-queue.git",
66
"keywords": ["timeout", "promise", "queue", "processes"],

test/flood-test.js

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,15 @@ Tests = [
1919

2020
{ processes: 1, sleep: 1000, ok: true },
2121

22+
{ processes: 5, sleep: 100, resolve: {exitCode: 0} , timeout: 200, ok: true },
23+
2224
{ processes: 1, sleep: 10000, timeout: 20, ok: false,
2325
// no rejection parameter so expect timeout-promise-queue's default:
2426
exceptionPattern: RegExp('^timeout of 20 exceeded$') },
2527

28+
{ processes: 5, sleep: 100, reject: Error('died') , timeout: 200, ok: false,
29+
exceptionPattern: RegExp('^died$') },
30+
2631
{ processes: 1, sleep: 10000, timeout: 20, ok: false,
2732
rejection: Error(TestTimeoutMessage),
2833
exceptionPattern: RegExp('^' + TestTimeoutMessage + '$') },
@@ -37,27 +42,50 @@ Tests.forEach((test, idx) => {
3742
const command = 'setTimeout(() => { process.exit(0); }, ' + test.sleep + ')'
3843
// const Command = 'for (let i = 0; i < 2**28; ++i) ;' // The busy alternative.
3944
SuiteTimeout += 2 * test.sleep * test.processes / QueueSize
40-
startProcesses(idx, test.processes, command, test.timeout, test.ok, test.rejection)
45+
startProcesses(idx, test, command)
4146
ExpectedQueueSize += test.processes
4247
})
4348

44-
function startProcesses (batch, processes, command, timeout, ok, rejection) {
45-
for (let i = 0; i < processes; ++i) {
49+
function startProcesses (batch, test, command) {
50+
for (let i = 0; i < test.processes; ++i) {
4651
const label = batch + '-' + i
47-
AllTests.push({
52+
AllTests.push(Object.assign({
4853
label: label,
49-
ok: ok,
5054
start: new Date(),
51-
exec: Queue.add(cancel => new Promise((resolve, reject) => {
52-
let program = child_process.spawn('node', ['-e', command])
53-
program.on('exit', exitCode => { resolve({exitCode:exitCode}) })
54-
program.on('error', reject)
55-
if (cancel)
56-
cancel.on('timeout', err => {
57-
program.kill()
58-
reject()
59-
})
60-
}), timeout, rejection)
55+
exec: Queue.add(
56+
('resolve' in test || 'reject' in test
57+
? makeThread(test)
58+
: makeProcess(test)),
59+
test.timeout, test.rejection)
60+
}, test)) // copy guts of test template
61+
}
62+
63+
function makeProcess (test) {
64+
return cancel => new Promise((resolve, reject) => {
65+
let program = child_process.spawn('node', ['-e', command])
66+
program.on('exit', exitCode => { resolve({exitCode:exitCode}) })
67+
program.on('error', reject)
68+
if (cancel)
69+
cancel.on('timeout', err => {
70+
program.kill()
71+
reject(err)
72+
})
73+
})
74+
}
75+
76+
function makeThread (test) {
77+
return cancel => new Promise((resolve, reject) => {
78+
setTimeout(() => {
79+
if ('reject' in test) {
80+
reject(test.reject)
81+
} else {
82+
resolve(test.resolve)
83+
}
84+
}, test.sleep)
85+
if (cancel)
86+
cancel.on('timeout', err => {
87+
reject(err)
88+
})
6189
})
6290
}
6391
}
@@ -86,7 +114,9 @@ describe('timeout-promise-queue', () => {
86114
report(false, e.message)
87115
} else {
88116
if (test.exceptionPattern) {
89-
if (e.message.match(test.exceptionPattern)) {
117+
if (!e || !e.message) {
118+
report(false, "expected structured error instead of " + e)
119+
} else if (e.message.match(test.exceptionPattern)) {
90120
report(true)
91121
} else {
92122
report(false, "expected \"" + e.message + "\" to match exceptionPattern")

timeout-promise-queue.js

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ function PromiseQueue (threshold) {
88
return {
99
/** queue (I/O) functions which return promises.
1010
* @pfunc returns a promise
11+
* If add is called with a @timeout, @pfunc will be called with an
12+
* EventEmitter which will emit a 'timeout' if @timeout is exceeded.
1113
*/
1214
add: function (pfunc, timeout, rejection) {
1315
if (++inPlay > threshold) {
@@ -21,34 +23,32 @@ function PromiseQueue (threshold) {
2123
}
2224

2325
function makeTimeout () {
24-
let timer = null
25-
let clientCancellation = new EventEmitter();
26-
let myCancellation = new EventEmitter();
2726
let ret = timeout === undefined
28-
? pfunc()
29-
: Promise.race([
30-
new Promise((resolve, reject) => {
31-
timer = setTimeout(() => {
27+
? pfunc()
28+
: new Promise((resolve, reject) => {
29+
let clientCancellation = new EventEmitter();
30+
31+
// Create a timer to send a cancellation to pfunc()'s promise.
32+
let timer = setTimeout(() => {
3233
let r = typeof rejection === 'undefined'
3334
? Error('timeout of ' + timeout + ' exceeded')
3435
: typeof rejection === 'function'
3536
? rejection()
3637
: rejection
3738
clientCancellation.emit('timeout', r);
38-
nextEntry()
3939
reject(r)
4040
}, timeout)
41-
// pfunc().then emits 'clear' which resolves with pfunc's result.
42-
myCancellation.on('clear', result => resolve(result))
43-
return timer
44-
}),
45-
pfunc(clientCancellation).then(result => {
46-
myCancellation.emit('clear', result);
47-
clearTimeout(timer)
48-
return result
41+
42+
// Delete timer after resolution.
43+
resolve(pfunc(clientCancellation).then(result => {
44+
clearTimeout(timer)
45+
return result
46+
}).catch(result => {
47+
clearTimeout(timer)
48+
throw result
49+
}))
4950
})
50-
])
51-
return ret.then(nextEntry)
51+
return ret.then(nextEntry).catch(result => {throw nextEntry(result)})
5252
}
5353
},
5454

@@ -59,8 +59,9 @@ function PromiseQueue (threshold) {
5959
}
6060
}
6161

62+
/** After each resolution or rejection, check the queue.
63+
*/
6264
function nextEntry (ret) {
63-
// After each resolution, check the queue.
6465
--inPlay
6566
if (queue.length > 0) {
6667
queue.pop()()

0 commit comments

Comments
 (0)