bdy 1.24.2-stage → 1.24.3-dev
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/distTs/package.json +1 -1
- package/distTs/src/index.js +8 -0
- package/distTs/src/sshChannelWindow.js +66 -0
- package/package.json +1 -1
package/distTs/package.json
CHANGED
package/distTs/src/index.js
CHANGED
|
@@ -3,9 +3,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
// FIRST, and it has to stay first: it raises the ssh2 channel window, and ssh2 reads that constant
|
|
7
|
+
// once, when it is loaded. Any import above this line that reaches ssh2 would pin the window at
|
|
8
|
+
// ssh2's 2MB default and cap every upload through a tunnel. See sshChannelWindow.ts.
|
|
9
|
+
const sshChannelWindow_1 = require("./sshChannelWindow");
|
|
6
10
|
const output_1 = __importDefault(require("./output"));
|
|
7
11
|
const logger_1 = __importDefault(require("./logger"));
|
|
8
12
|
const stream_1 = __importDefault(require("stream"));
|
|
13
|
+
if (!sshChannelWindow_1.sshChannelWindow.applied) {
|
|
14
|
+
// Not fatal: uploads through a tunnel just keep ssh2's 2MB-per-round-trip ceiling.
|
|
15
|
+
logger_1.default.debug(`SSH channel window unchanged - ${sshChannelWindow_1.sshChannelWindow.reason}`);
|
|
16
|
+
}
|
|
9
17
|
output_1.default.muteNodeWarnings();
|
|
10
18
|
stream_1.default.setDefaultHighWaterMark(false, 65536);
|
|
11
19
|
process.title = 'bdy';
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sshChannelWindow = void 0;
|
|
4
|
+
// The SSH channel window, raised before anything in this process loads ssh2.
|
|
5
|
+
//
|
|
6
|
+
// Throughput over one SSH channel is window/RTT, and ssh2 ships a 2 MB window (lib/Channel.js) that is
|
|
7
|
+
// a module constant, not an option. The agent is the RECEIVING end of an upload - data flows worker ->
|
|
8
|
+
// agent - so it is this side's window that decides how fast anything can be uploaded through a tunnel.
|
|
9
|
+
//
|
|
10
|
+
// Measured with a real OpenSSH sftp upload through a tunnel at 60 ms RTT: 11.7 MB/s with 2 MB here,
|
|
11
|
+
// 18.2 MB/s with 8 MB, against 19.0 MB/s for the same client and server with no tunnel in between - so
|
|
12
|
+
// 8 MB removes almost all of the tunnel's penalty. Raising it on the WORKER alone changes nothing at
|
|
13
|
+
// all (measured: still 11.7 MB/s), which is why this half exists. Past 8 MB the inner SSH becomes the
|
|
14
|
+
// ceiling and there is nothing left to win: 32 MB measured 18.7 MB/s.
|
|
15
|
+
//
|
|
16
|
+
// Memory: a receive window only becomes resident when the consumer is slower than the sender, bounded
|
|
17
|
+
// by the window per busy channel. On an agent that is a handful of channels on one machine, so there
|
|
18
|
+
// is no knob here - unlike the worker, which multiplies it by every concurrent transfer on an edge and
|
|
19
|
+
// reads SSH_CHANNEL_WINDOW for exactly that reason.
|
|
20
|
+
const SSH_CHANNEL_WINDOW = 8 * 1024 * 1024;
|
|
21
|
+
// Why a mutation rather than a patched dependency: client.js and server.js read these two constants
|
|
22
|
+
// ONCE, by destructuring lib/Channel.js at module load, so replacing them before ssh2 is first
|
|
23
|
+
// required is enough. And ssh2 is a bundleDependency published straight out of node_modules with
|
|
24
|
+
// `npm ci --ignore-scripts`, so a patch step would be one forgotten command away from silently
|
|
25
|
+
// shipping the 2 MB default - whereas this is source, and it travels with the build.
|
|
26
|
+
//
|
|
27
|
+
// Imported FIRST in src/index.ts, which is the only entry point (bin/cli.js requires it) and already
|
|
28
|
+
// the home of process-wide setup like stream.setDefaultHighWaterMark. Requiring only Channel.js here
|
|
29
|
+
// does not pull in client.js or server.js, so the constants are still unread at this point.
|
|
30
|
+
// What happened, for src/index.ts to log once the logger exists. This module deliberately imports
|
|
31
|
+
// NOTHING - logger pulls in ./utils, and the first import of the process reaching that far could drag
|
|
32
|
+
// ssh2 in with it and silence the very thing being set here.
|
|
33
|
+
exports.sshChannelWindow = { bytes: SSH_CHANNEL_WINDOW, applied: false, reason: '' };
|
|
34
|
+
const applyChannelWindow = () => {
|
|
35
|
+
try {
|
|
36
|
+
// If ssh2 is already loaded the constants have been read and this can no longer take effect. It
|
|
37
|
+
// means a new module started importing ssh2 above this line - the symptom would otherwise be
|
|
38
|
+
// nothing but slower uploads.
|
|
39
|
+
if (Object.keys(require.cache).some((f) => f.includes(`ssh2${'/'}lib${'/'}client.js`))) {
|
|
40
|
+
exports.sshChannelWindow.reason = 'ssh2 was already loaded, so its constants had been read';
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const channel = require('ssh2/lib/Channel.js');
|
|
44
|
+
if (typeof channel.MAX_WINDOW !== 'number' || typeof channel.WINDOW_THRESHOLD !== 'number') {
|
|
45
|
+
exports.sshChannelWindow.reason = 'ssh2 no longer exposes MAX_WINDOW/WINDOW_THRESHOLD';
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (SSH_CHANNEL_WINDOW < channel.MAX_WINDOW) {
|
|
49
|
+
exports.sshChannelWindow.reason = `ssh2 already defaults to ${channel.MAX_WINDOW} bytes`;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
channel.MAX_WINDOW = SSH_CHANNEL_WINDOW;
|
|
53
|
+
// ssh2 tops the window up once half of it is spent. Left at its own 1 MB it would top up only in
|
|
54
|
+
// the last eighth of an 8 MB window, which is exactly when a fast sender runs dry.
|
|
55
|
+
channel.WINDOW_THRESHOLD = Math.floor(SSH_CHANNEL_WINDOW / 2);
|
|
56
|
+
exports.sshChannelWindow.applied = true;
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
// ssh2 moved, or is not resolvable from here: keep the tunnel working on ssh2's defaults rather
|
|
60
|
+
// than failing to start over a throughput knob. Recorded rather than swallowed - the symptom is
|
|
61
|
+
// otherwise nothing but slower uploads, which is how this cost went unnoticed in the first place.
|
|
62
|
+
exports.sshChannelWindow.reason = `${err?.message || err}`;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
applyChannelWindow();
|
|
66
|
+
exports.default = SSH_CHANNEL_WINDOW;
|