[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
166
components/terminal/runtime/terminalFlowConstants.test.ts
Normal file
166
components/terminal/runtime/terminalFlowConstants.test.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
import { createRequire } from "node:module";
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import terminalFlowConstantsJson from "../../../infrastructure/config/terminalFlowConstants.json";
|
||||
import {
|
||||
FLOW_CHAR_COUNT_ACK_SIZE,
|
||||
FLOW_HIGH_WATER_MARK,
|
||||
FLOW_LOW_WATER_MARK,
|
||||
MAX_PENDING_WRITE_COALESCE_BYTES,
|
||||
MAX_PENDING_WRITE_COALESCE_BYTES_FLOOD,
|
||||
MAX_TERMINAL_PLAIN_WRITE_CHUNK_BYTES,
|
||||
MAX_TERMINAL_UNBROKEN_WRITE_CHUNK_BYTES,
|
||||
MAX_TERMINAL_WRITE_QUEUE_DRAIN_BYTES,
|
||||
TERMINAL_AUX_LONG_LINE_SCAN_LIMIT_CHARS,
|
||||
TERMINAL_LONG_LINE_PRESSURE_BYTES,
|
||||
XTERM_WRITE_CALLBACK_BATCH_BYTES,
|
||||
XTERM_WRITE_CALLBACK_FAST_PATH_MAX_BYTES,
|
||||
} from "./terminalFlowConstants.ts";
|
||||
import { createOutputFlowController } from "./outputFlowController.ts";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const sharedConstantsCjs = require("../../../infrastructure/config/terminalFlowConstants.cjs") as typeof terminalFlowConstantsJson;
|
||||
|
||||
test("renderer flow constants match shared terminalFlowConstants.json", () => {
|
||||
assert.equal(FLOW_HIGH_WATER_MARK, terminalFlowConstantsJson.FLOW_HIGH_WATER_MARK);
|
||||
assert.equal(FLOW_LOW_WATER_MARK, terminalFlowConstantsJson.FLOW_LOW_WATER_MARK);
|
||||
assert.equal(FLOW_CHAR_COUNT_ACK_SIZE, terminalFlowConstantsJson.FLOW_CHAR_COUNT_ACK_SIZE);
|
||||
assert.equal(
|
||||
MAX_PENDING_WRITE_COALESCE_BYTES,
|
||||
terminalFlowConstantsJson.MAX_PENDING_WRITE_COALESCE_BYTES,
|
||||
);
|
||||
assert.equal(
|
||||
MAX_PENDING_WRITE_COALESCE_BYTES_FLOOD,
|
||||
terminalFlowConstantsJson.MAX_PENDING_WRITE_COALESCE_BYTES_FLOOD,
|
||||
);
|
||||
assert.equal(
|
||||
MAX_TERMINAL_PLAIN_WRITE_CHUNK_BYTES,
|
||||
terminalFlowConstantsJson.MAX_TERMINAL_PLAIN_WRITE_CHUNK_BYTES,
|
||||
);
|
||||
assert.equal(
|
||||
MAX_TERMINAL_UNBROKEN_WRITE_CHUNK_BYTES,
|
||||
terminalFlowConstantsJson.MAX_TERMINAL_UNBROKEN_WRITE_CHUNK_BYTES,
|
||||
);
|
||||
assert.equal(
|
||||
MAX_TERMINAL_WRITE_QUEUE_DRAIN_BYTES,
|
||||
terminalFlowConstantsJson.MAX_TERMINAL_WRITE_QUEUE_DRAIN_BYTES,
|
||||
);
|
||||
assert.equal(
|
||||
TERMINAL_LONG_LINE_PRESSURE_BYTES,
|
||||
terminalFlowConstantsJson.TERMINAL_LONG_LINE_PRESSURE_BYTES,
|
||||
);
|
||||
assert.equal(
|
||||
TERMINAL_AUX_LONG_LINE_SCAN_LIMIT_CHARS,
|
||||
terminalFlowConstantsJson.TERMINAL_AUX_LONG_LINE_SCAN_LIMIT_CHARS,
|
||||
);
|
||||
assert.equal(
|
||||
XTERM_WRITE_CALLBACK_FAST_PATH_MAX_BYTES,
|
||||
terminalFlowConstantsJson.XTERM_WRITE_CALLBACK_FAST_PATH_MAX_BYTES,
|
||||
);
|
||||
assert.equal(
|
||||
XTERM_WRITE_CALLBACK_BATCH_BYTES,
|
||||
terminalFlowConstantsJson.XTERM_WRITE_CALLBACK_BATCH_BYTES,
|
||||
);
|
||||
assert.deepEqual(sharedConstantsCjs, terminalFlowConstantsJson);
|
||||
assert.ok(FLOW_CHAR_COUNT_ACK_SIZE <= FLOW_LOW_WATER_MARK);
|
||||
assert.ok(MAX_PENDING_WRITE_COALESCE_BYTES_FLOOD < MAX_PENDING_WRITE_COALESCE_BYTES);
|
||||
});
|
||||
|
||||
test("terminal flood limits keep interactive acks responsive", () => {
|
||||
assert.ok(FLOW_LOW_WATER_MARK <= 8 * 1024);
|
||||
assert.ok(FLOW_CHAR_COUNT_ACK_SIZE <= 4 * 1024);
|
||||
// Flood coalesce must stay below bulk so TUI frames can interleave, but stay
|
||||
// large enough that plain-text dumps (#1961) do not collapse into 8KB shards.
|
||||
assert.ok(MAX_PENDING_WRITE_COALESCE_BYTES_FLOOD <= 256 * 1024);
|
||||
assert.ok(MAX_PENDING_WRITE_COALESCE_BYTES_FLOOD >= 64 * 1024);
|
||||
assert.ok(MAX_TERMINAL_PLAIN_WRITE_CHUNK_BYTES <= FLOW_HIGH_WATER_MARK);
|
||||
// Unbroken-line shards should stay near Tabby's ~100KB PTY chunk size so
|
||||
// long dumps stream smoothly instead of 4KB + setTimeout(0) stuttering.
|
||||
assert.ok(MAX_TERMINAL_UNBROKEN_WRITE_CHUNK_BYTES >= 64 * 1024);
|
||||
assert.ok(MAX_TERMINAL_UNBROKEN_WRITE_CHUNK_BYTES <= 256 * 1024);
|
||||
assert.ok(MAX_TERMINAL_WRITE_QUEUE_DRAIN_BYTES <= FLOW_HIGH_WATER_MARK);
|
||||
// Drain enough per event-loop turn that a 1MB high-water backlog does not
|
||||
// require dozens of setTimeout(0) yields before SSH can resume.
|
||||
assert.ok(MAX_TERMINAL_WRITE_QUEUE_DRAIN_BYTES >= 256 * 1024);
|
||||
assert.ok(MAX_TERMINAL_WRITE_QUEUE_DRAIN_BYTES >= MAX_TERMINAL_UNBROKEN_WRITE_CHUNK_BYTES);
|
||||
// Long-line pressure can trip earlier than the write shard size so highlight
|
||||
// / gutter work throttles before bulk parse cost peaks.
|
||||
assert.ok(TERMINAL_LONG_LINE_PRESSURE_BYTES >= 32 * 1024);
|
||||
assert.ok(TERMINAL_AUX_LONG_LINE_SCAN_LIMIT_CHARS >= TERMINAL_LONG_LINE_PRESSURE_BYTES);
|
||||
assert.ok(XTERM_WRITE_CALLBACK_BATCH_BYTES <= FLOW_HIGH_WATER_MARK);
|
||||
});
|
||||
|
||||
test("terminal bulk output keeps large IPC coalesce but Tabby-sized xterm shards", () => {
|
||||
const bulkCoalesceFloorBytes = 1024 * 1024;
|
||||
assert.ok(
|
||||
MAX_PENDING_WRITE_COALESCE_BYTES >= bulkCoalesceFloorBytes,
|
||||
`MAX_PENDING_WRITE_COALESCE_BYTES (${MAX_PENDING_WRITE_COALESCE_BYTES}) should still batch multi-MB IPC into large flushes`,
|
||||
);
|
||||
// xterm write shards stay near Tabby's ~128KB FlowControl threshold so
|
||||
// multi-line floods (seq/logs) leave the event loop between slices.
|
||||
assert.ok(MAX_TERMINAL_PLAIN_WRITE_CHUNK_BYTES >= 64 * 1024);
|
||||
assert.ok(MAX_TERMINAL_PLAIN_WRITE_CHUNK_BYTES <= 256 * 1024);
|
||||
assert.equal(
|
||||
MAX_TERMINAL_PLAIN_WRITE_CHUNK_BYTES,
|
||||
MAX_TERMINAL_UNBROKEN_WRITE_CHUNK_BYTES,
|
||||
);
|
||||
});
|
||||
|
||||
test("terminal flow allows a large TUI repaint before applying back-pressure", () => {
|
||||
const events: string[] = [];
|
||||
const controller = createOutputFlowController({
|
||||
highWaterMark: FLOW_HIGH_WATER_MARK,
|
||||
lowWaterMark: FLOW_LOW_WATER_MARK,
|
||||
onPause: () => events.push("pause"),
|
||||
onResume: () => events.push("resume"),
|
||||
});
|
||||
|
||||
const tuiFrameBytes = 80 * 1024;
|
||||
const chunkBytes = 4 * 1024;
|
||||
for (let received = 0; received < tuiFrameBytes; received += chunkBytes) {
|
||||
controller.received(chunkBytes);
|
||||
}
|
||||
|
||||
assert.deepEqual(events, []);
|
||||
});
|
||||
|
||||
test("flow high-water mark stays clear of the ssh2 channel window (issue #1961)", () => {
|
||||
// Pausing the source stream for SSH means calling ssh2 channel pause(),
|
||||
// which stops the remote from sending until resume() + a full round-trip.
|
||||
// ssh2's own channel window is 2MB (WINDOW_THRESHOLD 1MB), so a small
|
||||
// high-water mark makes Netcatty pause/resume dozens of times during a
|
||||
// multi-MB dump (e.g. `tail -2000f big.log`). Each cycle costs ~1 RTT, so
|
||||
// on a WAN link the dump crawls (reported ~20s vs ~2s in other clients).
|
||||
// Keep the high-water mark near the ssh2 window so bulk output flows in a
|
||||
// handful of pause cycles instead of dozens.
|
||||
const SSH2_CHANNEL_WINDOW_THRESHOLD_BYTES = 1024 * 1024;
|
||||
assert.ok(
|
||||
FLOW_HIGH_WATER_MARK >= SSH2_CHANNEL_WINDOW_THRESHOLD_BYTES,
|
||||
`FLOW_HIGH_WATER_MARK (${FLOW_HIGH_WATER_MARK}) should be at least the ssh2 window threshold (${SSH2_CHANNEL_WINDOW_THRESHOLD_BYTES})`,
|
||||
);
|
||||
|
||||
// A 4MB bulk dump should trigger only a few pause cycles, not dozens.
|
||||
const events: string[] = [];
|
||||
const controller = createOutputFlowController({
|
||||
highWaterMark: FLOW_HIGH_WATER_MARK,
|
||||
lowWaterMark: FLOW_LOW_WATER_MARK,
|
||||
onPause: () => events.push("pause"),
|
||||
onResume: () => events.push("resume"),
|
||||
});
|
||||
const totalBytes = 4 * 1024 * 1024;
|
||||
const chunkBytes = 32 * 1024; // ssh2 PACKET_SIZE
|
||||
let delivered = 0;
|
||||
let pauses = 0;
|
||||
for (let sent = 0; sent < totalBytes; sent += chunkBytes) {
|
||||
controller.received(chunkBytes);
|
||||
if (controller.isPaused()) {
|
||||
pauses += 1;
|
||||
// Renderer catches up and drains the backlog before the source resumes.
|
||||
controller.written(controller.pendingBytes());
|
||||
delivered = sent + chunkBytes;
|
||||
}
|
||||
}
|
||||
controller.written(totalBytes - delivered);
|
||||
assert.ok(pauses <= 8, `expected few pause cycles for a 4MB dump, got ${pauses}`);
|
||||
});
|
||||
Reference in New Issue
Block a user