teyyare 0.2.2 → 0.2.3
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 +8 -1
- package/package.json +1 -1
- package/src/index.js +112 -0
- package/src/sender/pipeline.js +3 -3
package/bin/teyyare.js
CHANGED
|
@@ -50,6 +50,11 @@ async function main() {
|
|
|
50
50
|
process.exit(0);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
if (command === '--version' || command === '-v' || command === 'version') {
|
|
54
|
+
console.log('0.2.3');
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
|
|
53
58
|
if (command === 'serve') {
|
|
54
59
|
let port = 7421;
|
|
55
60
|
let dir = process.cwd();
|
|
@@ -105,6 +110,7 @@ Backpressure: ${snap.backpressureCount}
|
|
|
105
110
|
Protocol overhead:${snap.protocolOverheadPercent.toFixed(2)}%
|
|
106
111
|
Peak RSS: ${(snap.peakRSS / (1024 * 1024)).toFixed(2)} MB
|
|
107
112
|
`);
|
|
113
|
+
process.exit(0);
|
|
108
114
|
} else if (command === 'pull') {
|
|
109
115
|
const source = args[1];
|
|
110
116
|
const target = args[2] || './';
|
|
@@ -135,8 +141,9 @@ Backpressure: ${snap.backpressureCount}
|
|
|
135
141
|
Protocol overhead:${snap.protocolOverheadPercent.toFixed(2)}%
|
|
136
142
|
Peak RSS: ${(snap.peakRSS / (1024 * 1024)).toFixed(2)} MB
|
|
137
143
|
`);
|
|
144
|
+
process.exit(0);
|
|
138
145
|
} else if (command === 'status') {
|
|
139
|
-
console.log('\x1b[1m\x1b[36mTeyyare\x1b[0m v0.2.
|
|
146
|
+
console.log('\x1b[1m\x1b[36mTeyyare\x1b[0m v0.2.3 status: ready');
|
|
140
147
|
} else {
|
|
141
148
|
console.error(`Unknown command: ${command}`);
|
|
142
149
|
printUsage();
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { existsSync } from 'node:fs';
|
|
7
7
|
import { TCPTransport, ReplicationPipeline } from 'raptiye';
|
|
8
|
+
import { readHeader, decodeMessage, HEADER_SIZE } from 'raptiye/src/protocol/wire.js';
|
|
8
9
|
import { SenderPipeline } from './sender/pipeline.js';
|
|
9
10
|
import { ReceiverPipeline } from './receiver/pipeline.js';
|
|
10
11
|
import { TransferManifest } from './manifest/manifest.js';
|
|
@@ -22,6 +23,116 @@ import {
|
|
|
22
23
|
unpackPullError
|
|
23
24
|
} from './core/protocol.js';
|
|
24
25
|
|
|
26
|
+
// High-performance streaming TCP framer patch for TCPTransport
|
|
27
|
+
// Eliminates O(N^2) Buffer.concat and ensures clean socket lifecycle management
|
|
28
|
+
TCPTransport.prototype._attachSocketReader = function(socket) {
|
|
29
|
+
socket.setNoDelay(true);
|
|
30
|
+
const chunks = [];
|
|
31
|
+
let bufferedLength = 0;
|
|
32
|
+
|
|
33
|
+
socket.on('data', (chunk) => {
|
|
34
|
+
chunks.push(chunk);
|
|
35
|
+
bufferedLength += chunk.length;
|
|
36
|
+
|
|
37
|
+
while (bufferedLength >= HEADER_SIZE) {
|
|
38
|
+
let headerBuf = chunks[0];
|
|
39
|
+
if (headerBuf.length < HEADER_SIZE) {
|
|
40
|
+
const tempHeader = Buffer.allocUnsafe(HEADER_SIZE);
|
|
41
|
+
let off = 0;
|
|
42
|
+
for (let i = 0; off < HEADER_SIZE && i < chunks.length; i++) {
|
|
43
|
+
const c = chunks[i];
|
|
44
|
+
const toCopy = Math.min(c.length, HEADER_SIZE - off);
|
|
45
|
+
tempHeader.set(c.subarray(0, toCopy), off);
|
|
46
|
+
off += toCopy;
|
|
47
|
+
}
|
|
48
|
+
headerBuf = tempHeader;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let header;
|
|
52
|
+
try {
|
|
53
|
+
header = readHeader(headerBuf, 0);
|
|
54
|
+
} catch (err) {
|
|
55
|
+
socket.destroy(err);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const totalLength = HEADER_SIZE + header.payloadLength;
|
|
60
|
+
if (bufferedLength < totalLength) {
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let rawFrame;
|
|
65
|
+
if (chunks[0].length >= totalLength) {
|
|
66
|
+
rawFrame = chunks[0].subarray(0, totalLength);
|
|
67
|
+
if (chunks[0].length === totalLength) {
|
|
68
|
+
chunks.shift();
|
|
69
|
+
} else {
|
|
70
|
+
chunks[0] = chunks[0].subarray(totalLength);
|
|
71
|
+
}
|
|
72
|
+
bufferedLength -= totalLength;
|
|
73
|
+
} else {
|
|
74
|
+
rawFrame = Buffer.allocUnsafe(totalLength);
|
|
75
|
+
let dstOff = 0;
|
|
76
|
+
let remaining = totalLength;
|
|
77
|
+
while (remaining > 0) {
|
|
78
|
+
const head = chunks[0];
|
|
79
|
+
if (head.length <= remaining) {
|
|
80
|
+
rawFrame.set(head, dstOff);
|
|
81
|
+
dstOff += head.length;
|
|
82
|
+
remaining -= head.length;
|
|
83
|
+
chunks.shift();
|
|
84
|
+
} else {
|
|
85
|
+
rawFrame.set(head.subarray(0, remaining), dstOff);
|
|
86
|
+
dstOff += remaining;
|
|
87
|
+
chunks[0] = head.subarray(remaining);
|
|
88
|
+
remaining = 0;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
bufferedLength -= totalLength;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const frame = new Uint8Array(rawFrame.buffer, rawFrame.byteOffset, rawFrame.byteLength);
|
|
96
|
+
const msg = decodeMessage(frame, 0, true);
|
|
97
|
+
if (msg.sourceNode) {
|
|
98
|
+
this._outboundSockets.set(msg.sourceNode, socket);
|
|
99
|
+
}
|
|
100
|
+
if (this._messageHandler) {
|
|
101
|
+
this._messageHandler(msg.sourceNode, msg);
|
|
102
|
+
}
|
|
103
|
+
} catch (err) {
|
|
104
|
+
console.error(`[TCPTransport ${this.id}] Error decoding frame:`, err.message);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
socket.on('close', () => {
|
|
110
|
+
this._inboundSockets.delete(socket);
|
|
111
|
+
for (const [peer, s] of this._outboundSockets.entries()) {
|
|
112
|
+
if (s === socket) {
|
|
113
|
+
this._outboundSockets.delete(peer);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
socket.on('error', () => {
|
|
119
|
+
socket.destroy();
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Reset stalled inflight receipts on connection drop or before starting new transfer
|
|
124
|
+
ReplicationPipeline.prototype.reset = function(err = new Error('Replication pipeline reset')) {
|
|
125
|
+
for (const pending of this._pendingReceipts.values()) {
|
|
126
|
+
pending.reject(err);
|
|
127
|
+
}
|
|
128
|
+
this._pendingReceipts.clear();
|
|
129
|
+
this.inflightBatches = 0;
|
|
130
|
+
this.inflightBytes = 0;
|
|
131
|
+
this.stats.inflightBatches = 0;
|
|
132
|
+
this.stats.inflightBytes = 0;
|
|
133
|
+
this._notifyCapacity();
|
|
134
|
+
};
|
|
135
|
+
|
|
25
136
|
export {
|
|
26
137
|
SenderPipeline,
|
|
27
138
|
ReceiverPipeline,
|
|
@@ -108,6 +219,7 @@ export async function serve({
|
|
|
108
219
|
}
|
|
109
220
|
|
|
110
221
|
const manifest = await TransferManifest.build(resolved);
|
|
222
|
+
replication.reset();
|
|
111
223
|
const sender = new SenderPipeline({
|
|
112
224
|
replication,
|
|
113
225
|
manifest,
|
package/src/sender/pipeline.js
CHANGED
|
@@ -102,10 +102,10 @@ export class SenderPipeline {
|
|
|
102
102
|
this.replication = replication;
|
|
103
103
|
this.manifest = manifest;
|
|
104
104
|
this.chunkSize = options.chunkSize || 1024 * 1024;
|
|
105
|
-
this.maxEntries = options.maxEntries ||
|
|
106
|
-
this.maxBytes = options.maxBytes ||
|
|
105
|
+
this.maxEntries = options.maxEntries || 4;
|
|
106
|
+
this.maxBytes = options.maxBytes || 4 * 1024 * 1024;
|
|
107
107
|
this.maxImmutableBuffers = options.maxImmutableBuffers || 8;
|
|
108
|
-
this.maxConcurrentFlushes = options.maxConcurrentFlushes || 4;
|
|
108
|
+
this.maxConcurrentFlushes = options.maxConcurrentFlushes || Math.min(this.maxImmutableBuffers, 4);
|
|
109
109
|
this.metrics = metrics;
|
|
110
110
|
this.progress = progress;
|
|
111
111
|
this.knownResumeState = knownResumeState;
|