teyyare 0.2.6 → 0.2.7

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/bin/teyyare.js CHANGED
@@ -56,7 +56,7 @@ async function main() {
56
56
  }
57
57
 
58
58
  if (command === '--version' || command === '-v' || command === 'version') {
59
- console.log('0.2.6');
59
+ console.log('0.2.7');
60
60
  process.exit(0);
61
61
  }
62
62
 
@@ -170,7 +170,7 @@ Peak RSS: ${(snap.peakRSS / (1024 * 1024)).toFixed(2)} MB
170
170
  `);
171
171
  process.exit(0);
172
172
  } else if (command === 'status') {
173
- console.log('\x1b[1m\x1b[36mTeyyare\x1b[0m v0.2.6 status: ready');
173
+ console.log('\x1b[1m\x1b[36mTeyyare\x1b[0m v0.2.7 status: ready');
174
174
  } else {
175
175
  console.error(`Unknown command: ${command}`);
176
176
  printUsage();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teyyare",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "High-performance resumable file transfer engine and CLI built on muttafa and raptiye",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/index.js CHANGED
@@ -141,8 +141,14 @@ TCPTransport.prototype._attachSocketReader = function(socket) {
141
141
  if (idx !== -1) {
142
142
  pool.splice(idx, 1);
143
143
  }
144
+ if (pool.length === 0 && this.onPeerDisconnect) {
145
+ this.onPeerDisconnect(peer);
146
+ }
144
147
  }
145
148
  }
149
+ if (this._inboundSockets.size === 0 && this.onPeerDisconnect) {
150
+ this.onPeerDisconnect(1);
151
+ }
146
152
  });
147
153
 
148
154
  socket.on('error', () => {
@@ -458,6 +464,15 @@ export async function serve({
458
464
 
459
465
  let activeSender = null;
460
466
 
467
+ transport.onPeerDisconnect = async (peer) => {
468
+ if (activeSender) {
469
+ const s = activeSender;
470
+ activeSender = null;
471
+ await s.abort();
472
+ }
473
+ replication.reset();
474
+ };
475
+
461
476
  replication.onBatch(async (msg) => {
462
477
  if (!msg.buffers || msg.buffers.length < 2) return;
463
478
  const [metaBuf, payloadBuf] = msg.buffers;
@@ -481,8 +496,9 @@ export async function serve({
481
496
 
482
497
  // Abort any existing running sender before starting new transfer
483
498
  if (activeSender) {
484
- activeSender.abort();
499
+ const s = activeSender;
485
500
  activeSender = null;
501
+ await s.abort();
486
502
  }
487
503
 
488
504
  const manifest = await TransferManifest.build(resolved);
@@ -534,6 +550,12 @@ export async function serve({
534
550
  receiver,
535
551
  metrics,
536
552
  async close() {
553
+ if (activeSender) {
554
+ const s = activeSender;
555
+ activeSender = null;
556
+ await s.abort();
557
+ }
558
+ await receiver.close();
537
559
  metrics.stop();
538
560
  await transport.close();
539
561
  }
@@ -600,6 +622,7 @@ export async function send({
600
622
  try {
601
623
  await sender.transfer();
602
624
  } finally {
625
+ await sender.abort();
603
626
  metrics.stop();
604
627
  await transport.close();
605
628
  }
@@ -687,6 +710,7 @@ export async function pull({
687
710
  await receiver.waitForCompletion();
688
711
  } finally {
689
712
  if (progress) progress.stop();
713
+ await receiver.close();
690
714
  metrics.stop();
691
715
  await transport.close();
692
716
  }
@@ -370,6 +370,7 @@ export class ReceiverPipeline {
370
370
  // Sync disk buffers
371
371
  await file.fileHandle.sync();
372
372
  await file.fileHandle.close();
373
+ file.fileHandle = null;
373
374
 
374
375
  // Verify whole-file checksum if available
375
376
  const expectedSha = file.sha256;
@@ -389,13 +390,29 @@ export class ReceiverPipeline {
389
390
  this.completedFiles.add(file.destPath);
390
391
  }
391
392
 
393
+ async close() {
394
+ for (const [fileId, file] of this.activeFiles.entries()) {
395
+ if (file.fileHandle) {
396
+ try {
397
+ await file.fileHandle.close();
398
+ } catch {}
399
+ file.fileHandle = null;
400
+ }
401
+ }
402
+ this.activeFiles.clear();
403
+ this._pendingChunks.clear();
404
+ }
405
+
392
406
  async _computeFileSha256(filePath) {
393
407
  return new Promise((resolve, reject) => {
394
408
  const hash = crypto.createHash('sha256');
395
409
  const stream = createReadStream(filePath);
396
410
  stream.on('data', (d) => hash.update(d));
397
411
  stream.on('end', () => resolve(hash.digest('hex')));
398
- stream.on('error', reject);
412
+ stream.on('error', (err) => {
413
+ stream.destroy(err);
414
+ reject(err);
415
+ });
399
416
  });
400
417
  }
401
418
  }
@@ -141,14 +141,27 @@ export class SenderPipeline {
141
141
  }
142
142
  }
143
143
  });
144
+ this._activeHandle = null;
145
+ this._abortWaiters = new Set();
144
146
  this.aborted = false;
145
147
  }
146
148
 
147
- abort() {
149
+ async abort() {
148
150
  this.aborted = true;
151
+ for (const waiter of this._abortWaiters) {
152
+ waiter();
153
+ }
154
+ this._abortWaiters.clear();
155
+ if (this._activeHandle) {
156
+ const h = this._activeHandle;
157
+ this._activeHandle = null;
158
+ try {
159
+ await h.close();
160
+ } catch {}
161
+ }
149
162
  if (this.engine) {
150
163
  try {
151
- this.engine.halt();
164
+ this.engine.reset();
152
165
  } catch {}
153
166
  }
154
167
  }
@@ -157,7 +170,19 @@ export class SenderPipeline {
157
170
  if (this.aborted) return;
158
171
  while (this.engine.isBackpressured()) {
159
172
  if (this.aborted) return;
160
- await this.engine.ready();
173
+ await new Promise((resolve) => {
174
+ let done = false;
175
+ const waiter = () => {
176
+ if (!done) {
177
+ done = true;
178
+ this._abortWaiters.delete(waiter);
179
+ resolve();
180
+ }
181
+ };
182
+ this._abortWaiters.add(waiter);
183
+ this.engine.ready().then(waiter, waiter);
184
+ });
185
+ if (this.aborted) return;
161
186
  await new Promise((r) => setImmediate(r));
162
187
  }
163
188
  if (this.aborted) return;
@@ -216,6 +241,7 @@ export class SenderPipeline {
216
241
 
217
242
  if (file.size > 0) {
218
243
  const handle = await fs.open(filePath, 'r');
244
+ this._activeHandle = handle;
219
245
  try {
220
246
  for (let i = 0; i < totalFileChunks; i++) {
221
247
  if (this.aborted) break;
@@ -282,7 +308,10 @@ export class SenderPipeline {
282
308
  }
283
309
  }
284
310
  } finally {
285
- await handle.close();
311
+ this._activeHandle = null;
312
+ try {
313
+ await handle.close();
314
+ } catch {}
286
315
  }
287
316
  }
288
317