thread-stream 0.11.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +3 -3
- package/.github/workflows/package-manager-ci.yml +59 -0
- package/.taprc +2 -0
- package/README.md +22 -0
- package/index.js +293 -210
- package/lib/worker.js +25 -1
- package/package.json +5 -3
- package/test/base.test.js +38 -102
- package/test/close-on-gc.js +8 -10
- package/test/commonjs-fallback.test.js +29 -0
- package/test/create-and-exit.js +5 -7
- package/test/end.test.js +22 -38
- package/test/esm.test.mjs +8 -36
- package/test/helper.js +8 -1
- package/test/multibyte-chars.test.mjs +6 -10
- package/test/string-limit.test.js +36 -0
- package/test/thread-management.test.js +5 -5
package/index.js
CHANGED
|
@@ -9,6 +9,13 @@ const {
|
|
|
9
9
|
WRITE_INDEX,
|
|
10
10
|
READ_INDEX
|
|
11
11
|
} = require('./lib/indexes')
|
|
12
|
+
const buffer = require('buffer')
|
|
13
|
+
const assert = require('assert')
|
|
14
|
+
|
|
15
|
+
const kImpl = Symbol('kImpl')
|
|
16
|
+
|
|
17
|
+
// V8 limit for string size
|
|
18
|
+
const MAX_STRING = buffer.constants.MAX_STRING_LENGTH
|
|
12
19
|
|
|
13
20
|
class FakeWeakRef {
|
|
14
21
|
constructor (value) {
|
|
@@ -28,6 +35,9 @@ const FinalizationRegistry = global.FinalizationRegistry || class FakeFinalizati
|
|
|
28
35
|
const WeakRef = global.WeakRef || FakeWeakRef
|
|
29
36
|
|
|
30
37
|
const registry = new FinalizationRegistry((worker) => {
|
|
38
|
+
if (worker.exited) {
|
|
39
|
+
return
|
|
40
|
+
}
|
|
31
41
|
worker.terminate()
|
|
32
42
|
})
|
|
33
43
|
|
|
@@ -42,8 +52,8 @@ function createWorker (stream, opts) {
|
|
|
42
52
|
filename: filename.indexOf('file://') === 0
|
|
43
53
|
? filename
|
|
44
54
|
: pathToFileURL(filename).href,
|
|
45
|
-
dataBuf: stream.
|
|
46
|
-
stateBuf: stream.
|
|
55
|
+
dataBuf: stream[kImpl].dataBuf,
|
|
56
|
+
stateBuf: stream[kImpl].stateBuf,
|
|
47
57
|
workerData
|
|
48
58
|
}
|
|
49
59
|
})
|
|
@@ -60,57 +70,62 @@ function createWorker (stream, opts) {
|
|
|
60
70
|
}
|
|
61
71
|
|
|
62
72
|
function drain (stream) {
|
|
63
|
-
stream.
|
|
64
|
-
stream.
|
|
73
|
+
assert(!stream[kImpl].sync)
|
|
74
|
+
if (stream[kImpl].needDrain) {
|
|
75
|
+
stream[kImpl].needDrain = false
|
|
76
|
+
stream.emit('drain')
|
|
77
|
+
}
|
|
65
78
|
}
|
|
66
79
|
|
|
67
80
|
function nextFlush (stream) {
|
|
68
|
-
const writeIndex = Atomics.load(stream.
|
|
69
|
-
let leftover = stream.
|
|
81
|
+
const writeIndex = Atomics.load(stream[kImpl].state, WRITE_INDEX)
|
|
82
|
+
let leftover = stream[kImpl].data.length - writeIndex
|
|
70
83
|
|
|
71
84
|
if (leftover > 0) {
|
|
72
|
-
if (stream.buf.length === 0) {
|
|
73
|
-
stream.flushing = false
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
stream
|
|
85
|
+
if (stream[kImpl].buf.length === 0) {
|
|
86
|
+
stream[kImpl].flushing = false
|
|
87
|
+
|
|
88
|
+
if (stream[kImpl].ending) {
|
|
89
|
+
end(stream)
|
|
90
|
+
} else if (stream[kImpl].needDrain) {
|
|
77
91
|
process.nextTick(drain, stream)
|
|
78
92
|
}
|
|
93
|
+
|
|
79
94
|
return
|
|
80
95
|
}
|
|
81
96
|
|
|
82
|
-
let toWrite = stream.buf.slice(0, leftover)
|
|
97
|
+
let toWrite = stream[kImpl].buf.slice(0, leftover)
|
|
83
98
|
let toWriteBytes = Buffer.byteLength(toWrite)
|
|
84
99
|
if (toWriteBytes <= leftover) {
|
|
85
|
-
stream.buf = stream.buf.slice(leftover)
|
|
100
|
+
stream[kImpl].buf = stream[kImpl].buf.slice(leftover)
|
|
86
101
|
// process._rawDebug('writing ' + toWrite.length)
|
|
87
|
-
stream
|
|
102
|
+
write(stream, toWrite, nextFlush.bind(null, stream))
|
|
88
103
|
} else {
|
|
89
104
|
// multi-byte utf-8
|
|
90
105
|
stream.flush(() => {
|
|
91
|
-
Atomics.store(stream.
|
|
92
|
-
Atomics.store(stream.
|
|
106
|
+
Atomics.store(stream[kImpl].state, READ_INDEX, 0)
|
|
107
|
+
Atomics.store(stream[kImpl].state, WRITE_INDEX, 0)
|
|
93
108
|
|
|
94
109
|
// Find a toWrite length that fits the buffer
|
|
95
110
|
// it must exists as the buffer is at least 4 bytes length
|
|
96
111
|
// and the max utf-8 length for a char is 4 bytes.
|
|
97
|
-
while (toWriteBytes > stream.buf.length) {
|
|
112
|
+
while (toWriteBytes > stream[kImpl].buf.length) {
|
|
98
113
|
leftover = leftover / 2
|
|
99
|
-
toWrite = stream.buf.slice(0, leftover)
|
|
114
|
+
toWrite = stream[kImpl].buf.slice(0, leftover)
|
|
100
115
|
toWriteBytes = Buffer.byteLength(toWrite)
|
|
101
116
|
}
|
|
102
|
-
stream.buf = stream.buf.slice(leftover)
|
|
103
|
-
stream
|
|
117
|
+
stream[kImpl].buf = stream[kImpl].buf.slice(leftover)
|
|
118
|
+
write(stream, toWrite, nextFlush.bind(null, stream))
|
|
104
119
|
})
|
|
105
120
|
}
|
|
106
121
|
} else if (leftover === 0) {
|
|
107
|
-
if (writeIndex === 0 && stream.buf.length === 0) {
|
|
122
|
+
if (writeIndex === 0 && stream[kImpl].buf.length === 0) {
|
|
108
123
|
// we had a flushSync in the meanwhile
|
|
109
124
|
return
|
|
110
125
|
}
|
|
111
126
|
stream.flush(() => {
|
|
112
|
-
Atomics.store(stream.
|
|
113
|
-
Atomics.store(stream.
|
|
127
|
+
Atomics.store(stream[kImpl].state, READ_INDEX, 0)
|
|
128
|
+
Atomics.store(stream[kImpl].state, WRITE_INDEX, 0)
|
|
114
129
|
nextFlush(stream)
|
|
115
130
|
})
|
|
116
131
|
} else {
|
|
@@ -122,6 +137,7 @@ function nextFlush (stream) {
|
|
|
122
137
|
function onWorkerMessage (msg) {
|
|
123
138
|
const stream = this.stream.deref()
|
|
124
139
|
if (stream === undefined) {
|
|
140
|
+
this.exited = true
|
|
125
141
|
// Terminate the worker.
|
|
126
142
|
this.terminate()
|
|
127
143
|
return
|
|
@@ -132,28 +148,14 @@ function onWorkerMessage (msg) {
|
|
|
132
148
|
// Replace the FakeWeakRef with a
|
|
133
149
|
// proper one.
|
|
134
150
|
this.stream = new WeakRef(stream)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
stream.
|
|
151
|
+
|
|
152
|
+
stream.flush(() => {
|
|
153
|
+
stream[kImpl].ready = true
|
|
138
154
|
stream.emit('ready')
|
|
139
|
-
}
|
|
140
|
-
stream.once('drain', function () {
|
|
141
|
-
stream.flush(() => {
|
|
142
|
-
stream.ready = true
|
|
143
|
-
stream.emit('ready')
|
|
144
|
-
})
|
|
145
|
-
})
|
|
146
|
-
nextFlush(stream)
|
|
147
|
-
}
|
|
155
|
+
})
|
|
148
156
|
break
|
|
149
157
|
case 'ERROR':
|
|
150
|
-
stream.
|
|
151
|
-
// TODO only remove our own
|
|
152
|
-
stream.worker.removeAllListeners('exit')
|
|
153
|
-
stream.worker.terminate().then(null, () => {})
|
|
154
|
-
process.nextTick(() => {
|
|
155
|
-
stream.emit('error', msg.err)
|
|
156
|
-
})
|
|
158
|
+
destroy(stream, msg.err)
|
|
157
159
|
break
|
|
158
160
|
default:
|
|
159
161
|
throw new Error('this should not happen: ' + msg.code)
|
|
@@ -167,13 +169,9 @@ function onWorkerExit (code) {
|
|
|
167
169
|
return
|
|
168
170
|
}
|
|
169
171
|
registry.unregister(stream)
|
|
170
|
-
stream.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
stream.emit('error', new Error('The worker thread exited'))
|
|
174
|
-
}
|
|
175
|
-
stream.emit('close')
|
|
176
|
-
})
|
|
172
|
+
stream.worker.exited = true
|
|
173
|
+
stream.worker.off('exit', onWorkerExit)
|
|
174
|
+
destroy(stream, code !== 0 ? new Error('The worker thread exited') : null)
|
|
177
175
|
}
|
|
178
176
|
|
|
179
177
|
class ThreadStream extends EventEmitter {
|
|
@@ -184,119 +182,88 @@ class ThreadStream extends EventEmitter {
|
|
|
184
182
|
throw new Error('bufferSize must at least fit a 4-byte utf-8 char')
|
|
185
183
|
}
|
|
186
184
|
|
|
187
|
-
this
|
|
188
|
-
this.
|
|
189
|
-
this.
|
|
190
|
-
this.
|
|
191
|
-
this.
|
|
192
|
-
this.
|
|
193
|
-
this.
|
|
194
|
-
this.
|
|
195
|
-
this.needDrain = false
|
|
196
|
-
this.
|
|
197
|
-
|
|
198
|
-
this.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
this.
|
|
206
|
-
Atomics.store(this._state, WRITE_INDEX, current + length)
|
|
207
|
-
Atomics.notify(this._state, WRITE_INDEX)
|
|
208
|
-
cb()
|
|
209
|
-
return true
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
_hasSpace () {
|
|
213
|
-
const current = Atomics.load(this._state, WRITE_INDEX)
|
|
214
|
-
return this._data.length - this.buf.length - current > 0
|
|
185
|
+
this[kImpl] = {}
|
|
186
|
+
this[kImpl].stateBuf = new SharedArrayBuffer(128)
|
|
187
|
+
this[kImpl].state = new Int32Array(this[kImpl].stateBuf)
|
|
188
|
+
this[kImpl].dataBuf = new SharedArrayBuffer(opts.bufferSize || 4 * 1024 * 1024)
|
|
189
|
+
this[kImpl].data = Buffer.from(this[kImpl].dataBuf)
|
|
190
|
+
this[kImpl].sync = opts.sync || false
|
|
191
|
+
this[kImpl].ending = false
|
|
192
|
+
this[kImpl].ended = false
|
|
193
|
+
this[kImpl].needDrain = false
|
|
194
|
+
this[kImpl].destroyed = false
|
|
195
|
+
this[kImpl].flushing = false
|
|
196
|
+
this[kImpl].ready = false
|
|
197
|
+
this[kImpl].finished = false
|
|
198
|
+
this[kImpl].errored = null
|
|
199
|
+
this[kImpl].closed = false
|
|
200
|
+
this[kImpl].buf = ''
|
|
201
|
+
|
|
202
|
+
// TODO (fix): Make private?
|
|
203
|
+
this.worker = createWorker(this, opts) // TODO (fix): make private
|
|
215
204
|
}
|
|
216
205
|
|
|
217
206
|
write (data) {
|
|
218
|
-
if (this.
|
|
207
|
+
if (this[kImpl].destroyed) {
|
|
219
208
|
throw new Error('the worker has exited')
|
|
220
209
|
}
|
|
221
210
|
|
|
222
|
-
if (
|
|
223
|
-
|
|
224
|
-
return this._hasSpace()
|
|
211
|
+
if (this[kImpl].ending) {
|
|
212
|
+
throw new Error('the worker is ending')
|
|
225
213
|
}
|
|
226
214
|
|
|
227
|
-
if (this.
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
this.flushing = true
|
|
236
|
-
setImmediate(nextFlush, this)
|
|
237
|
-
|
|
238
|
-
return this._hasSpace()
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
end () {
|
|
242
|
-
if (this.closed) {
|
|
243
|
-
throw new Error('the worker has exited')
|
|
215
|
+
if (this[kImpl].flushing && this[kImpl].buf.length + data.length >= MAX_STRING) {
|
|
216
|
+
try {
|
|
217
|
+
writeSync(this)
|
|
218
|
+
this[kImpl].flushing = true
|
|
219
|
+
} catch (err) {
|
|
220
|
+
destroy(this, err)
|
|
221
|
+
return false
|
|
222
|
+
}
|
|
244
223
|
}
|
|
245
224
|
|
|
246
|
-
|
|
247
|
-
this.once('ready', this.end.bind(this))
|
|
248
|
-
return
|
|
249
|
-
}
|
|
225
|
+
this[kImpl].buf += data
|
|
250
226
|
|
|
251
|
-
if (this.
|
|
252
|
-
|
|
253
|
-
|
|
227
|
+
if (this[kImpl].sync) {
|
|
228
|
+
try {
|
|
229
|
+
writeSync(this)
|
|
230
|
+
return true
|
|
231
|
+
} catch (err) {
|
|
232
|
+
destroy(this, err)
|
|
233
|
+
return false
|
|
234
|
+
}
|
|
254
235
|
}
|
|
255
236
|
|
|
256
|
-
if (this.
|
|
257
|
-
|
|
237
|
+
if (!this[kImpl].flushing) {
|
|
238
|
+
this[kImpl].flushing = true
|
|
239
|
+
setImmediate(nextFlush, this)
|
|
258
240
|
}
|
|
259
|
-
this.ending = true
|
|
260
|
-
|
|
261
|
-
this.flushSync()
|
|
262
|
-
|
|
263
|
-
let read = Atomics.load(this._state, READ_INDEX)
|
|
264
241
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
Atomics.notify(this._state, WRITE_INDEX)
|
|
269
|
-
|
|
270
|
-
// Wait for the process to complete
|
|
271
|
-
let spins = 0
|
|
272
|
-
while (read !== -1) {
|
|
273
|
-
// process._rawDebug(`read = ${read}`)
|
|
274
|
-
Atomics.wait(this._state, READ_INDEX, read, 1000)
|
|
275
|
-
read = Atomics.load(this._state, READ_INDEX)
|
|
242
|
+
this[kImpl].needDrain = this[kImpl].data.length - this[kImpl].buf.length - Atomics.load(this[kImpl].state, WRITE_INDEX) <= 0
|
|
243
|
+
return !this[kImpl].needDrain
|
|
244
|
+
}
|
|
276
245
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
246
|
+
end () {
|
|
247
|
+
if (this[kImpl].destroyed) {
|
|
248
|
+
throw new Error('the worker has exited')
|
|
280
249
|
}
|
|
281
250
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
})
|
|
285
|
-
// process._rawDebug('end finished...')
|
|
251
|
+
this[kImpl].ending = true
|
|
252
|
+
end(this)
|
|
286
253
|
}
|
|
287
254
|
|
|
288
255
|
flush (cb) {
|
|
289
|
-
if (this.
|
|
256
|
+
if (this[kImpl].destroyed) {
|
|
290
257
|
throw new Error('the worker has exited')
|
|
291
258
|
}
|
|
292
259
|
|
|
293
260
|
// TODO write all .buf
|
|
294
|
-
const writeIndex = Atomics.load(this.
|
|
295
|
-
// process._rawDebug(`(flush) readIndex (${Atomics.load(this.
|
|
296
|
-
wait(this.
|
|
261
|
+
const writeIndex = Atomics.load(this[kImpl].state, WRITE_INDEX)
|
|
262
|
+
// process._rawDebug(`(flush) readIndex (${Atomics.load(this.state, READ_INDEX)}) writeIndex (${Atomics.load(this.state, WRITE_INDEX)})`)
|
|
263
|
+
wait(this[kImpl].state, READ_INDEX, writeIndex, Infinity, (err, res) => {
|
|
297
264
|
if (err) {
|
|
298
|
-
this
|
|
299
|
-
cb
|
|
265
|
+
destroy(this, err)
|
|
266
|
+
process.nextTick(cb, err)
|
|
300
267
|
return
|
|
301
268
|
}
|
|
302
269
|
if (res === 'not-equal') {
|
|
@@ -304,108 +271,224 @@ class ThreadStream extends EventEmitter {
|
|
|
304
271
|
this.flush(cb)
|
|
305
272
|
return
|
|
306
273
|
}
|
|
307
|
-
cb
|
|
274
|
+
process.nextTick(cb)
|
|
308
275
|
})
|
|
309
276
|
}
|
|
310
277
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
// process._rawDebug('emitting drain')
|
|
315
|
-
this.needDrain = true
|
|
316
|
-
process.nextTick(drain, this)
|
|
317
|
-
}
|
|
278
|
+
flushSync () {
|
|
279
|
+
if (this[kImpl].destroyed) {
|
|
280
|
+
throw new Error('the worker has exited')
|
|
318
281
|
}
|
|
319
|
-
this.flushing = false
|
|
320
|
-
|
|
321
|
-
while (this.buf.length !== 0) {
|
|
322
|
-
const writeIndex = Atomics.load(this._state, WRITE_INDEX)
|
|
323
|
-
let leftover = this._data.length - writeIndex
|
|
324
|
-
if (leftover === 0) {
|
|
325
|
-
this._flushSync()
|
|
326
|
-
Atomics.store(this._state, READ_INDEX, 0)
|
|
327
|
-
Atomics.store(this._state, WRITE_INDEX, 0)
|
|
328
|
-
continue
|
|
329
|
-
} else if (leftover < 0) {
|
|
330
|
-
// This should never happen
|
|
331
|
-
throw new Error('overwritten')
|
|
332
|
-
}
|
|
333
282
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
this.buf = this.buf.slice(leftover)
|
|
338
|
-
// process._rawDebug('writing ' + toWrite.length)
|
|
339
|
-
this._write(toWrite, cb)
|
|
340
|
-
} else {
|
|
341
|
-
// multi-byte utf-8
|
|
342
|
-
this._flushSync()
|
|
343
|
-
Atomics.store(this._state, READ_INDEX, 0)
|
|
344
|
-
Atomics.store(this._state, WRITE_INDEX, 0)
|
|
283
|
+
writeSync(this)
|
|
284
|
+
flushSync(this)
|
|
285
|
+
}
|
|
345
286
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
// and the max utf-8 length for a char is 4 bytes.
|
|
349
|
-
while (toWriteBytes > this.buf.length) {
|
|
350
|
-
leftover = leftover / 2
|
|
351
|
-
toWrite = this.buf.slice(0, leftover)
|
|
352
|
-
toWriteBytes = Buffer.byteLength(toWrite)
|
|
353
|
-
}
|
|
354
|
-
this.buf = this.buf.slice(leftover)
|
|
355
|
-
this._write(toWrite, cb)
|
|
356
|
-
}
|
|
357
|
-
}
|
|
287
|
+
unref () {
|
|
288
|
+
this.worker.unref()
|
|
358
289
|
}
|
|
359
290
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
291
|
+
ref () {
|
|
292
|
+
this.worker.ref()
|
|
293
|
+
}
|
|
364
294
|
|
|
365
|
-
|
|
366
|
-
this.
|
|
295
|
+
get ready () {
|
|
296
|
+
return this[kImpl].ready
|
|
367
297
|
}
|
|
368
298
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
299
|
+
get destroyed () {
|
|
300
|
+
return this[kImpl].destroyed
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
get closed () {
|
|
304
|
+
return this[kImpl].closed
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
get writable () {
|
|
308
|
+
return !this[kImpl].destroyed && !this[kImpl].ending
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
get writableEnded () {
|
|
312
|
+
return this[kImpl].ending
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
get writableFinished () {
|
|
316
|
+
return this[kImpl].finished
|
|
317
|
+
}
|
|
373
318
|
|
|
374
|
-
|
|
319
|
+
get writableNeedDrain () {
|
|
320
|
+
return this[kImpl].needDrain
|
|
321
|
+
}
|
|
375
322
|
|
|
376
|
-
|
|
323
|
+
get writableObjectMode () {
|
|
324
|
+
return false
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
get writableErrored () {
|
|
328
|
+
return this[kImpl].errored
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function destroy (stream, err) {
|
|
333
|
+
if (stream[kImpl].destroyed) {
|
|
334
|
+
return
|
|
335
|
+
}
|
|
336
|
+
stream[kImpl].destroyed = true
|
|
337
|
+
|
|
338
|
+
if (err) {
|
|
339
|
+
stream[kImpl].errored = err
|
|
340
|
+
stream.emit('error', err)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (!stream.worker.exited) {
|
|
344
|
+
stream.worker.terminate()
|
|
345
|
+
.catch(() => {})
|
|
346
|
+
.then(() => {
|
|
347
|
+
stream[kImpl].closed = true
|
|
348
|
+
stream.emit('close')
|
|
349
|
+
})
|
|
350
|
+
} else {
|
|
351
|
+
setImmediate(() => {
|
|
352
|
+
stream[kImpl].closed = true
|
|
353
|
+
stream.emit('close')
|
|
354
|
+
})
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function write (stream, data, cb) {
|
|
359
|
+
// data is smaller than the shared buffer length
|
|
360
|
+
const current = Atomics.load(stream[kImpl].state, WRITE_INDEX)
|
|
361
|
+
const length = Buffer.byteLength(data)
|
|
362
|
+
stream[kImpl].data.write(data, current)
|
|
363
|
+
Atomics.store(stream[kImpl].state, WRITE_INDEX, current + length)
|
|
364
|
+
Atomics.notify(stream[kImpl].state, WRITE_INDEX)
|
|
365
|
+
cb()
|
|
366
|
+
return true
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function end (stream) {
|
|
370
|
+
if (stream[kImpl].ended || !stream[kImpl].ending || stream[kImpl].flushing) {
|
|
371
|
+
return
|
|
372
|
+
}
|
|
373
|
+
stream[kImpl].ended = true
|
|
374
|
+
|
|
375
|
+
try {
|
|
376
|
+
stream.flushSync()
|
|
377
|
+
|
|
378
|
+
let readIndex = Atomics.load(stream[kImpl].state, READ_INDEX)
|
|
379
|
+
|
|
380
|
+
// process._rawDebug('writing index')
|
|
381
|
+
Atomics.store(stream[kImpl].state, WRITE_INDEX, -1)
|
|
382
|
+
// process._rawDebug(`(end) readIndex (${Atomics.load(stream.state, READ_INDEX)}) writeIndex (${Atomics.load(stream.state, WRITE_INDEX)})`)
|
|
383
|
+
Atomics.notify(stream[kImpl].state, WRITE_INDEX)
|
|
377
384
|
|
|
385
|
+
// Wait for the process to complete
|
|
378
386
|
let spins = 0
|
|
387
|
+
while (readIndex !== -1) {
|
|
388
|
+
// process._rawDebug(`read = ${read}`)
|
|
389
|
+
Atomics.wait(stream[kImpl].state, READ_INDEX, readIndex, 1000)
|
|
390
|
+
readIndex = Atomics.load(stream[kImpl].state, READ_INDEX)
|
|
379
391
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
const readIndex = Atomics.load(this._state, READ_INDEX)
|
|
383
|
-
// process._rawDebug(`(flushSync) readIndex (${readIndex}) writeIndex (${writeIndex})`)
|
|
384
|
-
if (readIndex !== writeIndex) {
|
|
385
|
-
// TODO this timeouts for some reason.
|
|
386
|
-
Atomics.wait(this._state, READ_INDEX, readIndex, 1000)
|
|
387
|
-
} else {
|
|
388
|
-
break
|
|
392
|
+
if (readIndex === -2) {
|
|
393
|
+
throw new Error('end() failed')
|
|
389
394
|
}
|
|
390
395
|
|
|
391
396
|
if (++spins === 10) {
|
|
392
|
-
throw new Error('
|
|
397
|
+
throw new Error('end() took too long (10s)')
|
|
393
398
|
}
|
|
394
399
|
}
|
|
395
|
-
|
|
400
|
+
|
|
401
|
+
process.nextTick(() => {
|
|
402
|
+
stream[kImpl].finished = true
|
|
403
|
+
stream.emit('finish')
|
|
404
|
+
})
|
|
405
|
+
} catch (err) {
|
|
406
|
+
destroy(stream, err)
|
|
396
407
|
}
|
|
408
|
+
// process._rawDebug('end finished...')
|
|
409
|
+
}
|
|
397
410
|
|
|
398
|
-
|
|
399
|
-
|
|
411
|
+
function writeSync (stream) {
|
|
412
|
+
const cb = () => {
|
|
413
|
+
if (stream[kImpl].ending) {
|
|
414
|
+
end(stream)
|
|
415
|
+
} else if (stream[kImpl].needDrain) {
|
|
416
|
+
process.nextTick(drain, stream)
|
|
417
|
+
}
|
|
400
418
|
}
|
|
419
|
+
stream[kImpl].flushing = false
|
|
420
|
+
|
|
421
|
+
while (stream[kImpl].buf.length !== 0) {
|
|
422
|
+
const writeIndex = Atomics.load(stream[kImpl].state, WRITE_INDEX)
|
|
423
|
+
let leftover = stream[kImpl].data.length - writeIndex
|
|
424
|
+
if (leftover === 0) {
|
|
425
|
+
flushSync(stream)
|
|
426
|
+
Atomics.store(stream[kImpl].state, READ_INDEX, 0)
|
|
427
|
+
Atomics.store(stream[kImpl].state, WRITE_INDEX, 0)
|
|
428
|
+
continue
|
|
429
|
+
} else if (leftover < 0) {
|
|
430
|
+
// stream should never happen
|
|
431
|
+
throw new Error('overwritten')
|
|
432
|
+
}
|
|
401
433
|
|
|
402
|
-
|
|
403
|
-
|
|
434
|
+
let toWrite = stream[kImpl].buf.slice(0, leftover)
|
|
435
|
+
let toWriteBytes = Buffer.byteLength(toWrite)
|
|
436
|
+
if (toWriteBytes <= leftover) {
|
|
437
|
+
stream[kImpl].buf = stream[kImpl].buf.slice(leftover)
|
|
438
|
+
// process._rawDebug('writing ' + toWrite.length)
|
|
439
|
+
write(stream, toWrite, cb)
|
|
440
|
+
} else {
|
|
441
|
+
// multi-byte utf-8
|
|
442
|
+
flushSync(stream)
|
|
443
|
+
Atomics.store(stream[kImpl].state, READ_INDEX, 0)
|
|
444
|
+
Atomics.store(stream[kImpl].state, WRITE_INDEX, 0)
|
|
445
|
+
|
|
446
|
+
// Find a toWrite length that fits the buffer
|
|
447
|
+
// it must exists as the buffer is at least 4 bytes length
|
|
448
|
+
// and the max utf-8 length for a char is 4 bytes.
|
|
449
|
+
while (toWriteBytes > stream[kImpl].buf.length) {
|
|
450
|
+
leftover = leftover / 2
|
|
451
|
+
toWrite = stream[kImpl].buf.slice(0, leftover)
|
|
452
|
+
toWriteBytes = Buffer.byteLength(toWrite)
|
|
453
|
+
}
|
|
454
|
+
stream[kImpl].buf = stream[kImpl].buf.slice(leftover)
|
|
455
|
+
write(stream, toWrite, cb)
|
|
456
|
+
}
|
|
404
457
|
}
|
|
458
|
+
}
|
|
405
459
|
|
|
406
|
-
|
|
407
|
-
|
|
460
|
+
function flushSync (stream) {
|
|
461
|
+
if (stream[kImpl].flushing) {
|
|
462
|
+
throw new Error('unable to flush while flushing')
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// process._rawDebug('flushSync started')
|
|
466
|
+
|
|
467
|
+
const writeIndex = Atomics.load(stream[kImpl].state, WRITE_INDEX)
|
|
468
|
+
|
|
469
|
+
let spins = 0
|
|
470
|
+
|
|
471
|
+
// TODO handle deadlock
|
|
472
|
+
while (true) {
|
|
473
|
+
const readIndex = Atomics.load(stream[kImpl].state, READ_INDEX)
|
|
474
|
+
|
|
475
|
+
if (readIndex === -2) {
|
|
476
|
+
throw new Error('_flushSync failed')
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// process._rawDebug(`(flushSync) readIndex (${readIndex}) writeIndex (${writeIndex})`)
|
|
480
|
+
if (readIndex !== writeIndex) {
|
|
481
|
+
// TODO stream timeouts for some reason.
|
|
482
|
+
Atomics.wait(stream[kImpl].state, READ_INDEX, readIndex, 1000)
|
|
483
|
+
} else {
|
|
484
|
+
break
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (++spins === 10) {
|
|
488
|
+
throw new Error('_flushSync took too long (10s)')
|
|
489
|
+
}
|
|
408
490
|
}
|
|
491
|
+
// process._rawDebug('flushSync finished')
|
|
409
492
|
}
|
|
410
493
|
|
|
411
494
|
module.exports = ThreadStream
|
package/lib/worker.js
CHANGED
|
@@ -15,10 +15,34 @@ const state = new Int32Array(stateBuf)
|
|
|
15
15
|
const data = Buffer.from(dataBuf)
|
|
16
16
|
|
|
17
17
|
async function start () {
|
|
18
|
-
|
|
18
|
+
let fn
|
|
19
|
+
try {
|
|
20
|
+
fn = (await import(workerData.filename)).default
|
|
21
|
+
} catch (error) {
|
|
22
|
+
// A yarn user that tries to start a ThreadStream for an external module
|
|
23
|
+
// provides a filename pointing to a zip file.
|
|
24
|
+
// eg. require.resolve('pino-elasticsearch') // returns /foo/pino-elasticsearch-npm-6.1.0-0c03079478-6915435172.zip/bar.js
|
|
25
|
+
// The `import` will fail to try to load it.
|
|
26
|
+
// This catch block executes the `require` fallback to load the module correctly.
|
|
27
|
+
// In fact, yarn modifies the `require` function to manage the zipped path.
|
|
28
|
+
// More details at https://github.com/pinojs/pino/pull/1113
|
|
29
|
+
// The error codes may change based on the node.js version (ENOTDIR > 12, ERR_MODULE_NOT_FOUND <= 12 )
|
|
30
|
+
if ((error.code === 'ENOTDIR' || error.code === 'ERR_MODULE_NOT_FOUND') &&
|
|
31
|
+
workerData.filename.startsWith('file://')) {
|
|
32
|
+
fn = require(workerData.filename.replace('file://', ''))
|
|
33
|
+
} else {
|
|
34
|
+
throw error
|
|
35
|
+
}
|
|
36
|
+
}
|
|
19
37
|
destination = await fn(workerData.workerData)
|
|
20
38
|
|
|
21
39
|
destination.on('error', function (err) {
|
|
40
|
+
Atomics.store(state, WRITE_INDEX, -2)
|
|
41
|
+
Atomics.notify(state, WRITE_INDEX)
|
|
42
|
+
|
|
43
|
+
Atomics.store(state, READ_INDEX, -2)
|
|
44
|
+
Atomics.notify(state, READ_INDEX)
|
|
45
|
+
|
|
22
46
|
parentPort.postMessage({
|
|
23
47
|
code: 'ERROR',
|
|
24
48
|
err
|