thread-stream 0.12.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/index.js +258 -215
- package/package.json +1 -1
- package/test/base.test.js +16 -2
- package/test/commonjs-fallback.test.js +4 -2
package/index.js
CHANGED
|
@@ -12,6 +12,8 @@ const {
|
|
|
12
12
|
const buffer = require('buffer')
|
|
13
13
|
const assert = require('assert')
|
|
14
14
|
|
|
15
|
+
const kImpl = Symbol('kImpl')
|
|
16
|
+
|
|
15
17
|
// V8 limit for string size
|
|
16
18
|
const MAX_STRING = buffer.constants.MAX_STRING_LENGTH
|
|
17
19
|
|
|
@@ -50,8 +52,8 @@ function createWorker (stream, opts) {
|
|
|
50
52
|
filename: filename.indexOf('file://') === 0
|
|
51
53
|
? filename
|
|
52
54
|
: pathToFileURL(filename).href,
|
|
53
|
-
dataBuf: stream.
|
|
54
|
-
stateBuf: stream.
|
|
55
|
+
dataBuf: stream[kImpl].dataBuf,
|
|
56
|
+
stateBuf: stream[kImpl].stateBuf,
|
|
55
57
|
workerData
|
|
56
58
|
}
|
|
57
59
|
})
|
|
@@ -68,62 +70,62 @@ function createWorker (stream, opts) {
|
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
function drain (stream) {
|
|
71
|
-
assert(!stream.
|
|
72
|
-
if (stream.needDrain) {
|
|
73
|
-
stream.needDrain = false
|
|
73
|
+
assert(!stream[kImpl].sync)
|
|
74
|
+
if (stream[kImpl].needDrain) {
|
|
75
|
+
stream[kImpl].needDrain = false
|
|
74
76
|
stream.emit('drain')
|
|
75
77
|
}
|
|
76
78
|
}
|
|
77
79
|
|
|
78
80
|
function nextFlush (stream) {
|
|
79
|
-
const writeIndex = Atomics.load(stream.
|
|
80
|
-
let leftover = stream.
|
|
81
|
+
const writeIndex = Atomics.load(stream[kImpl].state, WRITE_INDEX)
|
|
82
|
+
let leftover = stream[kImpl].data.length - writeIndex
|
|
81
83
|
|
|
82
84
|
if (leftover > 0) {
|
|
83
|
-
if (stream.buf.length === 0) {
|
|
84
|
-
stream.flushing = false
|
|
85
|
+
if (stream[kImpl].buf.length === 0) {
|
|
86
|
+
stream[kImpl].flushing = false
|
|
85
87
|
|
|
86
|
-
if (stream.ending) {
|
|
87
|
-
stream
|
|
88
|
-
} else if (stream.needDrain) {
|
|
88
|
+
if (stream[kImpl].ending) {
|
|
89
|
+
end(stream)
|
|
90
|
+
} else if (stream[kImpl].needDrain) {
|
|
89
91
|
process.nextTick(drain, stream)
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
return
|
|
93
95
|
}
|
|
94
96
|
|
|
95
|
-
let toWrite = stream.buf.slice(0, leftover)
|
|
97
|
+
let toWrite = stream[kImpl].buf.slice(0, leftover)
|
|
96
98
|
let toWriteBytes = Buffer.byteLength(toWrite)
|
|
97
99
|
if (toWriteBytes <= leftover) {
|
|
98
|
-
stream.buf = stream.buf.slice(leftover)
|
|
100
|
+
stream[kImpl].buf = stream[kImpl].buf.slice(leftover)
|
|
99
101
|
// process._rawDebug('writing ' + toWrite.length)
|
|
100
|
-
stream
|
|
102
|
+
write(stream, toWrite, nextFlush.bind(null, stream))
|
|
101
103
|
} else {
|
|
102
104
|
// multi-byte utf-8
|
|
103
105
|
stream.flush(() => {
|
|
104
|
-
Atomics.store(stream.
|
|
105
|
-
Atomics.store(stream.
|
|
106
|
+
Atomics.store(stream[kImpl].state, READ_INDEX, 0)
|
|
107
|
+
Atomics.store(stream[kImpl].state, WRITE_INDEX, 0)
|
|
106
108
|
|
|
107
109
|
// Find a toWrite length that fits the buffer
|
|
108
110
|
// it must exists as the buffer is at least 4 bytes length
|
|
109
111
|
// and the max utf-8 length for a char is 4 bytes.
|
|
110
|
-
while (toWriteBytes > stream.buf.length) {
|
|
112
|
+
while (toWriteBytes > stream[kImpl].buf.length) {
|
|
111
113
|
leftover = leftover / 2
|
|
112
|
-
toWrite = stream.buf.slice(0, leftover)
|
|
114
|
+
toWrite = stream[kImpl].buf.slice(0, leftover)
|
|
113
115
|
toWriteBytes = Buffer.byteLength(toWrite)
|
|
114
116
|
}
|
|
115
|
-
stream.buf = stream.buf.slice(leftover)
|
|
116
|
-
stream
|
|
117
|
+
stream[kImpl].buf = stream[kImpl].buf.slice(leftover)
|
|
118
|
+
write(stream, toWrite, nextFlush.bind(null, stream))
|
|
117
119
|
})
|
|
118
120
|
}
|
|
119
121
|
} else if (leftover === 0) {
|
|
120
|
-
if (writeIndex === 0 && stream.buf.length === 0) {
|
|
122
|
+
if (writeIndex === 0 && stream[kImpl].buf.length === 0) {
|
|
121
123
|
// we had a flushSync in the meanwhile
|
|
122
124
|
return
|
|
123
125
|
}
|
|
124
126
|
stream.flush(() => {
|
|
125
|
-
Atomics.store(stream.
|
|
126
|
-
Atomics.store(stream.
|
|
127
|
+
Atomics.store(stream[kImpl].state, READ_INDEX, 0)
|
|
128
|
+
Atomics.store(stream[kImpl].state, WRITE_INDEX, 0)
|
|
127
129
|
nextFlush(stream)
|
|
128
130
|
})
|
|
129
131
|
} else {
|
|
@@ -148,12 +150,12 @@ function onWorkerMessage (msg) {
|
|
|
148
150
|
this.stream = new WeakRef(stream)
|
|
149
151
|
|
|
150
152
|
stream.flush(() => {
|
|
151
|
-
|
|
153
|
+
stream[kImpl].ready = true
|
|
152
154
|
stream.emit('ready')
|
|
153
155
|
})
|
|
154
156
|
break
|
|
155
157
|
case 'ERROR':
|
|
156
|
-
stream
|
|
158
|
+
destroy(stream, msg.err)
|
|
157
159
|
break
|
|
158
160
|
default:
|
|
159
161
|
throw new Error('this should not happen: ' + msg.code)
|
|
@@ -169,7 +171,7 @@ function onWorkerExit (code) {
|
|
|
169
171
|
registry.unregister(stream)
|
|
170
172
|
stream.worker.exited = true
|
|
171
173
|
stream.worker.off('exit', onWorkerExit)
|
|
172
|
-
stream
|
|
174
|
+
destroy(stream, code !== 0 ? new Error('The worker thread exited') : null)
|
|
173
175
|
}
|
|
174
176
|
|
|
175
177
|
class ThreadStream extends EventEmitter {
|
|
@@ -180,157 +182,87 @@ class ThreadStream extends EventEmitter {
|
|
|
180
182
|
throw new Error('bufferSize must at least fit a 4-byte utf-8 char')
|
|
181
183
|
}
|
|
182
184
|
|
|
183
|
-
this
|
|
184
|
-
this.
|
|
185
|
-
this.
|
|
186
|
-
this.
|
|
187
|
-
this.
|
|
188
|
-
this.
|
|
189
|
-
this.ending = false
|
|
190
|
-
this.ended = false
|
|
191
|
-
this.needDrain = false
|
|
192
|
-
this.destroyed = false
|
|
193
|
-
this.flushing = false
|
|
194
|
-
this.ready = false
|
|
195
|
-
|
|
196
|
-
this.
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
203
|
-
this.destroyed = true
|
|
204
|
-
|
|
205
|
-
if (err) {
|
|
206
|
-
this.emit('error', err)
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (!this.worker.exited) {
|
|
210
|
-
this.worker.terminate()
|
|
211
|
-
.catch(() => {})
|
|
212
|
-
.then(() => {
|
|
213
|
-
this.emit('close')
|
|
214
|
-
})
|
|
215
|
-
} else {
|
|
216
|
-
setImmediate(() => {
|
|
217
|
-
this.emit('close')
|
|
218
|
-
})
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
_write (data, cb) {
|
|
223
|
-
// data is smaller than the shared buffer length
|
|
224
|
-
const current = Atomics.load(this._state, WRITE_INDEX)
|
|
225
|
-
const length = Buffer.byteLength(data)
|
|
226
|
-
this._data.write(data, current)
|
|
227
|
-
Atomics.store(this._state, WRITE_INDEX, current + length)
|
|
228
|
-
Atomics.notify(this._state, WRITE_INDEX)
|
|
229
|
-
cb()
|
|
230
|
-
return true
|
|
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
|
|
231
204
|
}
|
|
232
205
|
|
|
233
206
|
write (data) {
|
|
234
|
-
if (this.destroyed) {
|
|
207
|
+
if (this[kImpl].destroyed) {
|
|
235
208
|
throw new Error('the worker has exited')
|
|
236
209
|
}
|
|
237
210
|
|
|
238
|
-
if (this.ending) {
|
|
211
|
+
if (this[kImpl].ending) {
|
|
239
212
|
throw new Error('the worker is ending')
|
|
240
213
|
}
|
|
241
214
|
|
|
242
|
-
if (this.flushing && this.buf.length + data.length >= MAX_STRING) {
|
|
215
|
+
if (this[kImpl].flushing && this[kImpl].buf.length + data.length >= MAX_STRING) {
|
|
243
216
|
try {
|
|
244
|
-
this
|
|
245
|
-
this.flushing = true
|
|
217
|
+
writeSync(this)
|
|
218
|
+
this[kImpl].flushing = true
|
|
246
219
|
} catch (err) {
|
|
247
|
-
this
|
|
220
|
+
destroy(this, err)
|
|
248
221
|
return false
|
|
249
222
|
}
|
|
250
223
|
}
|
|
251
224
|
|
|
252
|
-
this.buf += data
|
|
225
|
+
this[kImpl].buf += data
|
|
253
226
|
|
|
254
|
-
if (this.
|
|
227
|
+
if (this[kImpl].sync) {
|
|
255
228
|
try {
|
|
256
|
-
this
|
|
229
|
+
writeSync(this)
|
|
257
230
|
return true
|
|
258
231
|
} catch (err) {
|
|
259
|
-
this
|
|
232
|
+
destroy(this, err)
|
|
260
233
|
return false
|
|
261
234
|
}
|
|
262
235
|
}
|
|
263
236
|
|
|
264
|
-
if (!this.flushing) {
|
|
265
|
-
this.flushing = true
|
|
237
|
+
if (!this[kImpl].flushing) {
|
|
238
|
+
this[kImpl].flushing = true
|
|
266
239
|
setImmediate(nextFlush, this)
|
|
267
240
|
}
|
|
268
241
|
|
|
269
|
-
this.needDrain = this.
|
|
270
|
-
return !this.needDrain
|
|
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
|
|
271
244
|
}
|
|
272
245
|
|
|
273
246
|
end () {
|
|
274
|
-
if (this.destroyed) {
|
|
247
|
+
if (this[kImpl].destroyed) {
|
|
275
248
|
throw new Error('the worker has exited')
|
|
276
249
|
}
|
|
277
250
|
|
|
278
|
-
this.ending = true
|
|
279
|
-
this
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
_end () {
|
|
283
|
-
if (this.ended || !this.ending || this.flushing) {
|
|
284
|
-
return
|
|
285
|
-
}
|
|
286
|
-
this.ended = true
|
|
287
|
-
|
|
288
|
-
try {
|
|
289
|
-
this.flushSync()
|
|
290
|
-
|
|
291
|
-
let readIndex = Atomics.load(this._state, READ_INDEX)
|
|
292
|
-
|
|
293
|
-
// process._rawDebug('writing index')
|
|
294
|
-
Atomics.store(this._state, WRITE_INDEX, -1)
|
|
295
|
-
// process._rawDebug(`(end) readIndex (${Atomics.load(this._state, READ_INDEX)}) writeIndex (${Atomics.load(this._state, WRITE_INDEX)})`)
|
|
296
|
-
Atomics.notify(this._state, WRITE_INDEX)
|
|
297
|
-
|
|
298
|
-
// Wait for the process to complete
|
|
299
|
-
let spins = 0
|
|
300
|
-
while (readIndex !== -1) {
|
|
301
|
-
// process._rawDebug(`read = ${read}`)
|
|
302
|
-
Atomics.wait(this._state, READ_INDEX, readIndex, 1000)
|
|
303
|
-
readIndex = Atomics.load(this._state, READ_INDEX)
|
|
304
|
-
|
|
305
|
-
if (readIndex === -2) {
|
|
306
|
-
throw new Error('end() failed')
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
if (++spins === 10) {
|
|
310
|
-
throw new Error('end() took too long (10s)')
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
process.nextTick(() => {
|
|
315
|
-
this.emit('finish')
|
|
316
|
-
})
|
|
317
|
-
} catch (err) {
|
|
318
|
-
this._destroy(err)
|
|
319
|
-
}
|
|
320
|
-
// process._rawDebug('end finished...')
|
|
251
|
+
this[kImpl].ending = true
|
|
252
|
+
end(this)
|
|
321
253
|
}
|
|
322
254
|
|
|
323
255
|
flush (cb) {
|
|
324
|
-
if (this.destroyed) {
|
|
256
|
+
if (this[kImpl].destroyed) {
|
|
325
257
|
throw new Error('the worker has exited')
|
|
326
258
|
}
|
|
327
259
|
|
|
328
260
|
// TODO write all .buf
|
|
329
|
-
const writeIndex = Atomics.load(this.
|
|
330
|
-
// process._rawDebug(`(flush) readIndex (${Atomics.load(this.
|
|
331
|
-
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) => {
|
|
332
264
|
if (err) {
|
|
333
|
-
this
|
|
265
|
+
destroy(this, err)
|
|
334
266
|
process.nextTick(cb, err)
|
|
335
267
|
return
|
|
336
268
|
}
|
|
@@ -343,109 +275,220 @@ class ThreadStream extends EventEmitter {
|
|
|
343
275
|
})
|
|
344
276
|
}
|
|
345
277
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
this._end()
|
|
350
|
-
} else if (this.needDrain) {
|
|
351
|
-
process.nextTick(drain, this)
|
|
352
|
-
}
|
|
278
|
+
flushSync () {
|
|
279
|
+
if (this[kImpl].destroyed) {
|
|
280
|
+
throw new Error('the worker has exited')
|
|
353
281
|
}
|
|
354
|
-
this.flushing = false
|
|
355
|
-
|
|
356
|
-
while (this.buf.length !== 0) {
|
|
357
|
-
const writeIndex = Atomics.load(this._state, WRITE_INDEX)
|
|
358
|
-
let leftover = this._data.length - writeIndex
|
|
359
|
-
if (leftover === 0) {
|
|
360
|
-
this._flushSync()
|
|
361
|
-
Atomics.store(this._state, READ_INDEX, 0)
|
|
362
|
-
Atomics.store(this._state, WRITE_INDEX, 0)
|
|
363
|
-
continue
|
|
364
|
-
} else if (leftover < 0) {
|
|
365
|
-
// This should never happen
|
|
366
|
-
throw new Error('overwritten')
|
|
367
|
-
}
|
|
368
282
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
this.buf = this.buf.slice(leftover)
|
|
373
|
-
// process._rawDebug('writing ' + toWrite.length)
|
|
374
|
-
this._write(toWrite, cb)
|
|
375
|
-
} else {
|
|
376
|
-
// multi-byte utf-8
|
|
377
|
-
this._flushSync()
|
|
378
|
-
Atomics.store(this._state, READ_INDEX, 0)
|
|
379
|
-
Atomics.store(this._state, WRITE_INDEX, 0)
|
|
283
|
+
writeSync(this)
|
|
284
|
+
flushSync(this)
|
|
285
|
+
}
|
|
380
286
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
// and the max utf-8 length for a char is 4 bytes.
|
|
384
|
-
while (toWriteBytes > this.buf.length) {
|
|
385
|
-
leftover = leftover / 2
|
|
386
|
-
toWrite = this.buf.slice(0, leftover)
|
|
387
|
-
toWriteBytes = Buffer.byteLength(toWrite)
|
|
388
|
-
}
|
|
389
|
-
this.buf = this.buf.slice(leftover)
|
|
390
|
-
this._write(toWrite, cb)
|
|
391
|
-
}
|
|
392
|
-
}
|
|
287
|
+
unref () {
|
|
288
|
+
this.worker.unref()
|
|
393
289
|
}
|
|
394
290
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
}
|
|
291
|
+
ref () {
|
|
292
|
+
this.worker.ref()
|
|
293
|
+
}
|
|
399
294
|
|
|
400
|
-
|
|
401
|
-
this.
|
|
295
|
+
get ready () {
|
|
296
|
+
return this[kImpl].ready
|
|
402
297
|
}
|
|
403
298
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
}
|
|
299
|
+
get destroyed () {
|
|
300
|
+
return this[kImpl].destroyed
|
|
301
|
+
}
|
|
408
302
|
|
|
409
|
-
|
|
303
|
+
get closed () {
|
|
304
|
+
return this[kImpl].closed
|
|
305
|
+
}
|
|
410
306
|
|
|
411
|
-
|
|
307
|
+
get writable () {
|
|
308
|
+
return !this[kImpl].destroyed && !this[kImpl].ending
|
|
309
|
+
}
|
|
412
310
|
|
|
413
|
-
|
|
311
|
+
get writableEnded () {
|
|
312
|
+
return this[kImpl].ending
|
|
313
|
+
}
|
|
414
314
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
315
|
+
get writableFinished () {
|
|
316
|
+
return this[kImpl].finished
|
|
317
|
+
}
|
|
418
318
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
319
|
+
get writableNeedDrain () {
|
|
320
|
+
return this[kImpl].needDrain
|
|
321
|
+
}
|
|
322
|
+
|
|
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
|
|
422
374
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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)
|
|
384
|
+
|
|
385
|
+
// Wait for the process to complete
|
|
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)
|
|
391
|
+
|
|
392
|
+
if (readIndex === -2) {
|
|
393
|
+
throw new Error('end() failed')
|
|
429
394
|
}
|
|
430
395
|
|
|
431
396
|
if (++spins === 10) {
|
|
432
|
-
throw new Error('
|
|
397
|
+
throw new Error('end() took too long (10s)')
|
|
433
398
|
}
|
|
434
399
|
}
|
|
435
|
-
|
|
400
|
+
|
|
401
|
+
process.nextTick(() => {
|
|
402
|
+
stream[kImpl].finished = true
|
|
403
|
+
stream.emit('finish')
|
|
404
|
+
})
|
|
405
|
+
} catch (err) {
|
|
406
|
+
destroy(stream, err)
|
|
407
|
+
}
|
|
408
|
+
// process._rawDebug('end finished...')
|
|
409
|
+
}
|
|
410
|
+
|
|
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
|
+
}
|
|
436
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
|
+
}
|
|
437
433
|
|
|
438
|
-
|
|
439
|
-
|
|
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
|
+
}
|
|
440
457
|
}
|
|
458
|
+
}
|
|
441
459
|
|
|
442
|
-
|
|
443
|
-
|
|
460
|
+
function flushSync (stream) {
|
|
461
|
+
if (stream[kImpl].flushing) {
|
|
462
|
+
throw new Error('unable to flush while flushing')
|
|
444
463
|
}
|
|
445
464
|
|
|
446
|
-
|
|
447
|
-
|
|
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
|
+
}
|
|
448
490
|
}
|
|
491
|
+
// process._rawDebug('flushSync finished')
|
|
449
492
|
}
|
|
450
493
|
|
|
451
494
|
module.exports = ThreadStream
|
package/package.json
CHANGED
package/test/base.test.js
CHANGED
|
@@ -9,7 +9,7 @@ const { MessageChannel } = require('worker_threads')
|
|
|
9
9
|
const { once } = require('events')
|
|
10
10
|
|
|
11
11
|
test('base sync=true', function (t) {
|
|
12
|
-
t.plan(
|
|
12
|
+
t.plan(15)
|
|
13
13
|
|
|
14
14
|
const dest = file()
|
|
15
15
|
const stream = new ThreadStream({
|
|
@@ -18,23 +18,32 @@ test('base sync=true', function (t) {
|
|
|
18
18
|
sync: true
|
|
19
19
|
})
|
|
20
20
|
|
|
21
|
+
t.same(stream.writableObjectMode, false)
|
|
22
|
+
|
|
23
|
+
t.same(stream.writableFinished, false)
|
|
21
24
|
stream.on('finish', () => {
|
|
25
|
+
t.same(stream.writableFinished, true)
|
|
22
26
|
readFile(dest, 'utf8', (err, data) => {
|
|
23
27
|
t.error(err)
|
|
24
28
|
t.equal(data, 'hello world\nsomething else\n')
|
|
25
29
|
})
|
|
26
30
|
})
|
|
27
31
|
|
|
32
|
+
t.same(stream.closed, false)
|
|
28
33
|
stream.on('close', () => {
|
|
34
|
+
t.same(stream.closed, true)
|
|
29
35
|
t.notOk(stream.writable)
|
|
30
36
|
t.pass('close emitted')
|
|
31
37
|
})
|
|
32
38
|
|
|
39
|
+
t.same(stream.writableNeedDrain, false)
|
|
33
40
|
t.ok(stream.write('hello world\n'))
|
|
34
41
|
t.ok(stream.write('something else\n'))
|
|
35
42
|
t.ok(stream.writable)
|
|
36
43
|
|
|
44
|
+
t.same(stream.writableEnded, false)
|
|
37
45
|
stream.end()
|
|
46
|
+
t.same(stream.writableEnded, true)
|
|
38
47
|
})
|
|
39
48
|
|
|
40
49
|
test('overflow sync=true', function (t) {
|
|
@@ -87,6 +96,8 @@ test('overflow sync=false', function (t) {
|
|
|
87
96
|
|
|
88
97
|
let count = 0
|
|
89
98
|
|
|
99
|
+
t.same(stream.writableNeedDrain, false)
|
|
100
|
+
|
|
90
101
|
// Write 10 chars, 20 times
|
|
91
102
|
function write () {
|
|
92
103
|
if (count++ === 20) {
|
|
@@ -95,7 +106,9 @@ test('overflow sync=false', function (t) {
|
|
|
95
106
|
return
|
|
96
107
|
}
|
|
97
108
|
|
|
98
|
-
stream.write('aaaaaaaaaa')
|
|
109
|
+
if (!stream.write('aaaaaaaaaa')) {
|
|
110
|
+
t.same(stream.writableNeedDrain, true)
|
|
111
|
+
}
|
|
99
112
|
// do not wait for drain event
|
|
100
113
|
setImmediate(write)
|
|
101
114
|
}
|
|
@@ -103,6 +116,7 @@ test('overflow sync=false', function (t) {
|
|
|
103
116
|
write()
|
|
104
117
|
|
|
105
118
|
stream.on('drain', () => {
|
|
119
|
+
t.same(stream.writableNeedDrain, false)
|
|
106
120
|
t.pass('drain')
|
|
107
121
|
})
|
|
108
122
|
|
|
@@ -6,7 +6,7 @@ const ThreadStream = require('..')
|
|
|
6
6
|
const isYarnPnp = process.versions.pnp !== undefined
|
|
7
7
|
|
|
8
8
|
test('yarn module resolution', { skip: !isYarnPnp }, t => {
|
|
9
|
-
t.plan(
|
|
9
|
+
t.plan(6)
|
|
10
10
|
|
|
11
11
|
const modulePath = require.resolve('pino-elasticsearch')
|
|
12
12
|
t.match(modulePath, /.*\.zip.*/)
|
|
@@ -17,7 +17,9 @@ test('yarn module resolution', { skip: !isYarnPnp }, t => {
|
|
|
17
17
|
sync: true
|
|
18
18
|
})
|
|
19
19
|
|
|
20
|
-
stream.
|
|
20
|
+
t.same(stream.writableErrored, null)
|
|
21
|
+
stream.on('error', (err) => {
|
|
22
|
+
t.same(stream.writableErrored, err)
|
|
21
23
|
t.pass('error emitted')
|
|
22
24
|
})
|
|
23
25
|
|