thread-stream 0.11.1 → 0.13.0

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