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/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._dataBuf,
46
- stateBuf: stream._stateBuf,
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.needDrain = false
64
- stream.emit('drain')
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._state, WRITE_INDEX)
69
- let leftover = stream._data.length - writeIndex
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
- if (!stream.needDrain) {
75
- // process._rawDebug('emitting drain')
76
- stream.needDrain = true
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._write(toWrite, nextFlush.bind(null, 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._state, READ_INDEX, 0)
92
- Atomics.store(stream._state, WRITE_INDEX, 0)
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._write(toWrite, nextFlush.bind(null, 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._state, READ_INDEX, 0)
113
- Atomics.store(stream._state, WRITE_INDEX, 0)
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
- if (stream._sync) {
136
- stream.ready = true
137
- stream.flushSync()
151
+
152
+ stream.flush(() => {
153
+ stream[kImpl].ready = true
138
154
  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
- }
155
+ })
148
156
  break
149
157
  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
- })
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.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
- })
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._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
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.closed) {
207
+ if (this[kImpl].destroyed) {
219
208
  throw new Error('the worker has exited')
220
209
  }
221
210
 
222
- if (!this.ready || this.flushing) {
223
- this.buf += data
224
- return this._hasSpace()
211
+ if (this[kImpl].ending) {
212
+ throw new Error('the worker is ending')
225
213
  }
226
214
 
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')
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
- if (!this.ready) {
247
- this.once('ready', this.end.bind(this))
248
- return
249
- }
225
+ this[kImpl].buf += data
250
226
 
251
- if (this.flushing) {
252
- this.once('drain', this.end.bind(this))
253
- return
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.ending) {
257
- return
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
- // 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)
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
- if (++spins === 10) {
278
- throw new Error('end() took too long (10s)')
279
- }
246
+ end () {
247
+ if (this[kImpl].destroyed) {
248
+ throw new Error('the worker has exited')
280
249
  }
281
250
 
282
- process.nextTick(() => {
283
- this.emit('finish')
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.closed) {
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._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) => {
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.emit('error', err)
299
- cb(err)
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
- _writeSync () {
312
- const cb = () => {
313
- if (!this.needDrain) {
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
- 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)
283
+ writeSync(this)
284
+ flushSync(this)
285
+ }
345
286
 
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
- }
287
+ unref () {
288
+ this.worker.unref()
358
289
  }
359
290
 
360
- flushSync () {
361
- if (this.closed) {
362
- throw new Error('the worker has exited')
363
- }
291
+ ref () {
292
+ this.worker.ref()
293
+ }
364
294
 
365
- this._writeSync()
366
- this._flushSync()
295
+ get ready () {
296
+ return this[kImpl].ready
367
297
  }
368
298
 
369
- _flushSync () {
370
- if (this.flushing) {
371
- throw new Error('unable to flush while flushing')
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
- // process._rawDebug('flushSync started')
319
+ get writableNeedDrain () {
320
+ return this[kImpl].needDrain
321
+ }
375
322
 
376
- const writeIndex = Atomics.load(this._state, WRITE_INDEX)
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
- // 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
392
+ if (readIndex === -2) {
393
+ throw new Error('end() failed')
389
394
  }
390
395
 
391
396
  if (++spins === 10) {
392
- throw new Error('_flushSync took too long (10s)')
397
+ throw new Error('end() took too long (10s)')
393
398
  }
394
399
  }
395
- // process._rawDebug('flushSync finished')
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
- unref () {
399
- this.worker.unref()
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
- ref () {
403
- this.worker.ref()
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
- get writable () {
407
- return !this.closed
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
- const fn = (await import(workerData.filename)).default
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