socketduct 0.1.25 → 0.1.26
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/AGENTS.md +2 -2
- package/changelog.d/20261010130509-bound-log-frame-compression-memory.md +1 -0
- package/docs/runbooks/jsonl-integrity-rollout.md +8 -6
- package/package.json +2 -2
- package/scripts/test.js +9 -14
- package/src/logging/compressed-log-writer.js +1 -1
- package/src/logging/zstd-frame-codec.js +15 -1
- package/test/e2e/gateway-process.test.js +93 -92
- package/test/e2e/logger-shutdown-process.test.js +40 -37
- package/test/e2e/reverse-client-recovery-process.test.js +107 -103
- package/test/e2e/reverse-gateway-process.test.js +328 -318
- package/test/integration/initial-target-recovery.test.js +333 -334
- package/test/integration/logging-cli.test.js +121 -119
- package/test/integration/logging-compressed-recovery.test.js +96 -95
- package/test/integration/multiplexed-compatibility.test.js +178 -177
- package/test/integration/multiplexed-corrections.test.js +360 -359
- package/test/integration/multiplexed-failures.test.js +253 -251
- package/test/integration/multiplexed-transport-set.test.js +334 -322
- package/test/integration/multiplexed-transport.test.js +135 -134
- package/test/integration/reverse-gateway-stream-id-reset.test.js +90 -89
- package/test/integration/reverse-gateway.test.js +2309 -2218
- package/test/integration/reverse-resume-continuity.test.js +219 -218
- package/test/integration/reverse-route-class.test.js +358 -341
- package/test/integration/socketduct-http-agent.test.js +314 -312
- package/test/integration/socketduct-https-agent.test.js +516 -485
- package/test/integration/stream-compression.test.js +862 -824
- package/test/integration/tcp-forwarding.test.js +2550 -2447
- package/test/integration/tls-forwarding.test.js +593 -592
- package/test/unit/auth/auth.test.js +43 -44
- package/test/unit/auth/static-token-store.test.js +9 -8
- package/test/unit/client/connect.test.js +41 -40
- package/test/unit/client/deferred-virtual-socket.test.js +95 -94
- package/test/unit/client/multiplexed-spool-budget.test.js +155 -154
- package/test/unit/client/multiplexed-transport-set.test.js +830 -748
- package/test/unit/client/socketduct-socket.test.js +768 -771
- package/test/unit/client/virtual-socket.test.js +198 -168
- package/test/unit/config/docker-gateway-files.test.js +224 -224
- package/test/unit/config/environment.test.js +472 -492
- package/test/unit/index.test.js +53 -52
- package/test/unit/logging/compressed-log-worker.test.js +23 -22
- package/test/unit/logging/compressed-log-writer.test.js +155 -154
- package/test/unit/logging/log-retention.test.js +52 -47
- package/test/unit/logging/log-segment-index.test.js +84 -81
- package/test/unit/logging/zstd-frame-codec.test.js +28 -19
- package/test/unit/protocol/compression.test.js +254 -298
- package/test/unit/protocol/frame.test.js +280 -276
- package/test/unit/protocol/v3-compression.test.js +21 -23
- package/test/unit/protocol/v3-frame.test.js +42 -41
- package/test/unit/release-configuration.test.js +15 -12
- package/test/unit/reverse/disconnect-attribution.test.js +65 -64
- package/test/unit/reverse-terminal-outcomes.test.js +53 -50
- package/test/unit/server/control-handler.test.js +232 -231
- package/test/unit/server/primary.test.js +51 -49
- package/test/unit/server/relay-runtime.test.js +280 -294
- package/test/unit/sessions/session.test.js +48 -62
- package/test/unit/spool/spool-file.test.js +401 -385
package/test/unit/index.test.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import test from "node:test"
|
|
1
|
+
import {describe, expect, it} from "@velocious/testing"
|
|
3
2
|
|
|
4
3
|
import {
|
|
5
4
|
connectSocketduct,
|
|
@@ -23,61 +22,63 @@ import {connectSocketduct as directConnectSocketduct, SocketductSocket as Direct
|
|
|
23
22
|
import {createJsonlFileLogger as directCreateJsonlFileLogger} from "../../src/logging/structured-logger.js"
|
|
24
23
|
import {createCompressedJsonlFileLogger as directCreateCompressedJsonlFileLogger} from "../../src/logging/compressed-log-writer.js"
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
describe("public index exports", () => {
|
|
26
|
+
it("getStartupSummary returns the Socketduct service identity and default mode", () => {
|
|
27
|
+
expect(getStartupSummary()).toEqual({
|
|
28
|
+
name: "Socketduct",
|
|
29
|
+
runtime: "node",
|
|
30
|
+
mode: "single-worker"
|
|
31
|
+
})
|
|
31
32
|
})
|
|
32
|
-
})
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
})
|
|
34
|
+
it("index exports the public SocketductSocket client API", () => {
|
|
35
|
+
expect(SocketductSocket).toBe(DirectSocketductSocket)
|
|
36
|
+
expect(connectSocketduct).toBe(directConnectSocketduct)
|
|
37
|
+
expect(createSocketductConnectionFactory).toBe(directCreateSocketductConnectionFactory)
|
|
38
|
+
expect(createJsonlFileLogger).toBe(directCreateJsonlFileLogger)
|
|
39
|
+
expect(createCompressedJsonlFileLogger).toBe(directCreateCompressedJsonlFileLogger)
|
|
40
|
+
expect(SocketductHttpAgent).toBe(DirectSocketductHttpAgent)
|
|
41
|
+
})
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
it("package exports expose dedicated HTTP and HTTPS adapter entrypoints without an HTTPS barrel export", async () => {
|
|
44
|
+
const socketduct = await import("socketduct")
|
|
45
|
+
const socketductHttp = await import("socketduct/http")
|
|
46
|
+
const socketductHttps = await import("socketduct/https")
|
|
47
|
+
const {SocketductHttpsAgent} = await import("../../src/client/https-agent.js")
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
})
|
|
49
|
+
expect(socketduct.SocketductHttpAgent).toBe(DirectSocketductHttpAgent)
|
|
50
|
+
expect(socketductHttp.SocketductHttpAgent).toBe(DirectSocketductHttpAgent)
|
|
51
|
+
expect(socketductHttps.SocketductHttpsAgent).toBe(SocketductHttpsAgent)
|
|
52
|
+
expect("SocketductHttpsAgent" in socketduct).toBe(false)
|
|
53
|
+
})
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
it("package exports the protocol-v3 pooled connection and relay entrypoints", async () => {
|
|
56
|
+
const multiplexed = await import("socketduct/multiplexed")
|
|
57
|
+
const relay = await import("socketduct/relay")
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
})
|
|
59
|
+
expect(multiplexed.MultiplexedSocketductConnection).toBe(MultiplexedSocketductConnection)
|
|
60
|
+
expect(multiplexed.MultiplexedSocketductTransportSet).toBe(MultiplexedSocketductTransportSet)
|
|
61
|
+
expect(multiplexed.SocketductRecoveryLookupError).toBe(SocketductRecoveryLookupError)
|
|
62
|
+
expect(createMultiplexedSocketductConnection({
|
|
63
|
+
relay: {host: "127.0.0.1", port: 3100, token: "test-token"}
|
|
64
|
+
}) instanceof MultiplexedSocketductConnection).toBe(true)
|
|
65
|
+
expect(createMultiplexedSocketductTransportSet({
|
|
66
|
+
connectionId: "process_identity_01",
|
|
67
|
+
spoolDirectory: "/tmp/socketduct-index-test",
|
|
68
|
+
maxTotalBufferBytes: 1024,
|
|
69
|
+
maxTotalSpoolBytes: 1024,
|
|
70
|
+
maxTotalQueuedBytes: 2048,
|
|
71
|
+
gateways: [{gatewayId: "starlink", endpoint: {relay: {host: "127.0.0.1", port: 3100, token: "test-token"}}}]
|
|
72
|
+
}) instanceof MultiplexedSocketductTransportSet).toBe(true)
|
|
73
|
+
expect(typeof relay.startMultiplexedTcpRelay).toBe("function")
|
|
74
|
+
})
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
76
|
+
it("package exports native reverse hub, gateway, and client entrypoints", async () => {
|
|
77
|
+
const reverse = await import("socketduct/reverse")
|
|
78
|
+
const relay = await import("socketduct/relay")
|
|
79
|
+
expect(reverse.OutboundReverseGateway).toBe(OutboundReverseGateway)
|
|
80
|
+
expect(reverse.ReverseGatewayConnection).toBe(ReverseGatewayConnection)
|
|
81
|
+
expect(typeof reverse.startReverseGatewayHub).toBe("function")
|
|
82
|
+
expect(relay.startReverseGatewayHub).toBe(reverse.startReverseGatewayHub)
|
|
83
|
+
})
|
|
83
84
|
})
|
|
@@ -1,29 +1,30 @@
|
|
|
1
|
-
import
|
|
2
|
-
import test from "node:test"
|
|
1
|
+
import {describe, expect, it} from "@velocious/testing"
|
|
3
2
|
|
|
4
3
|
import {writeAll} from "../../../src/logging/compressed-log-worker.js"
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
5
|
+
describe("compressed log worker", () => {
|
|
6
|
+
it("worker positional writes preserve every byte across all short-write offsets", async () => {
|
|
7
|
+
const source = Buffer.from("short-write-safe-durable-frame")
|
|
8
|
+
for (let maximum = 1; maximum <= source.length; maximum += 1) {
|
|
9
|
+
const destination = Buffer.alloc(source.length + 7)
|
|
10
|
+
/** @type {number[]} */
|
|
11
|
+
const positions = []
|
|
12
|
+
const handle = {
|
|
13
|
+
async write(/** @type {Buffer} */ bytes, /** @type {number} */ offset, /** @type {number} */ length, /** @type {number} */ position) {
|
|
14
|
+
const bytesWritten = Math.min(maximum, length)
|
|
15
|
+
positions.push(position)
|
|
16
|
+
Buffer.from(bytes).copy(destination, position, offset, offset + bytesWritten)
|
|
17
|
+
return {bytesWritten, buffer: bytes}
|
|
18
|
+
}
|
|
18
19
|
}
|
|
20
|
+
await writeAll(/** @type {import('node:fs/promises').FileHandle} */ (handle), source, 7)
|
|
21
|
+
expect(destination.subarray(7).equals(source)).toBe(true)
|
|
22
|
+
expect(positions).toEqual(Array.from({length: Math.ceil(source.length / maximum)}, (_, index) => 7 + index * maximum))
|
|
19
23
|
}
|
|
20
|
-
|
|
21
|
-
assert.deepEqual(destination.subarray(7), source)
|
|
22
|
-
assert.deepEqual(positions, Array.from({length: Math.ceil(source.length / maximum)}, (_, index) => 7 + index * maximum))
|
|
23
|
-
}
|
|
24
|
-
})
|
|
24
|
+
})
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
it("worker positional writes fail loudly on zero progress", async () => {
|
|
27
|
+
const handle = {async write(/** @type {Buffer} */ bytes) { return {bytesWritten: 0, buffer: bytes} }}
|
|
28
|
+
await expect(writeAll(/** @type {import('node:fs/promises').FileHandle} */ (handle), Buffer.from("frame"), 0)).rejects.toThrow(/no progress/)
|
|
29
|
+
})
|
|
29
30
|
})
|
|
@@ -1,175 +1,176 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {describe, expect, it, withTestCleanup} from "@velocious/testing"
|
|
2
2
|
import {mkdtemp, readdir, rm, stat} from "node:fs/promises"
|
|
3
3
|
import {tmpdir} from "node:os"
|
|
4
4
|
import {join} from "node:path"
|
|
5
|
-
import test from "node:test"
|
|
6
5
|
|
|
7
6
|
import {readCompressedLogDirectory} from "../../../src/logging/compressed-log-reader.js"
|
|
8
7
|
import {createCompressedJsonlFileLogger} from "../../../src/logging/compressed-log-writer.js"
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
9
|
+
describe("compressed log writer", () => {
|
|
10
|
+
it("compressed logger durably acknowledges ordered records and closes asynchronously", async () => withTestCleanup(async (after) => {
|
|
11
|
+
const root = await mkdtemp(join(tmpdir(), "socketduct-compressed-writer-"))
|
|
12
|
+
const directory = join(root, "logs")
|
|
13
|
+
let tick = 0
|
|
14
|
+
const logger = createCompressedJsonlFileLogger({
|
|
15
|
+
directory,
|
|
16
|
+
role: "reverse-hub",
|
|
17
|
+
now: () => new Date(1_795_000_000_000 + tick++),
|
|
18
|
+
frameMaxBytes: 256,
|
|
19
|
+
flushIntervalMs: 60_000,
|
|
20
|
+
criticalFlushIntervalMs: 60_000,
|
|
21
|
+
maxSegmentBytes: 2048
|
|
22
|
+
})
|
|
23
|
+
after(async () => { await logger.close().catch(() => {}); await rm(root, {recursive: true, force: true}) })
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
expect(logger.log("process.started", {runtimeMode: "reverse-hub", token: "must-not-survive"})).toBe("accepted")
|
|
26
|
+
expect(logger.log("runtime.status", {virtualStreams: 4})).toBe("accepted")
|
|
27
|
+
expect(logger.log("runtime.status", {virtualStreams: 5})).toBe("accepted")
|
|
28
|
+
expect(logger.status().acceptedHighWater).toBe("3")
|
|
29
|
+
await logger.flush()
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
const durable = logger.status()
|
|
32
|
+
expect(Number(durable.durableHighWater) >= 3).toBeTruthy()
|
|
33
|
+
expect(durable.health).toBe("healthy")
|
|
34
|
+
const result = await readCompressedLogDirectory(directory)
|
|
35
|
+
expect(result.incomplete).toBe(false)
|
|
36
|
+
expect(result.records.map((record) => record.eventSequence)).toEqual(Array.from({length: result.records.length}, (_, index) => String(index + 1)))
|
|
37
|
+
expect(result.records.filter((record) => !String(record.event).startsWith("log_")).map((record) => record.event)).toEqual(["process.started", "runtime.status", "runtime.status"])
|
|
38
|
+
expect(new Set(result.records.map((record) => record.writerEpoch)).size).toBe(1)
|
|
39
|
+
expect(JSON.stringify(result.records)).not.toMatch(/must-not-survive/)
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
const filesBeforeClose = await readdir(directory)
|
|
42
|
+
expect(filesBeforeClose.some((name) => name.endsWith(".jsonl.zst"))).toBeTruthy()
|
|
43
|
+
expect(filesBeforeClose.some((name) => name.endsWith(".jsonl.zst.idx"))).toBeTruthy()
|
|
44
|
+
expect(filesBeforeClose.some((name) => name.endsWith(".jsonl"))).toBe(false)
|
|
45
|
+
expect((await stat(directory)).mode & 0o777).toBe(0o700)
|
|
46
|
+
for (const name of filesBeforeClose) expect((await stat(join(directory, name))).mode & 0o777).toBe(0o600)
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
})
|
|
48
|
+
const firstClose = logger.close()
|
|
49
|
+
const secondClose = logger.close()
|
|
50
|
+
expect(firstClose).toBe(secondClose)
|
|
51
|
+
const finalStatus = await firstClose
|
|
52
|
+
expect(finalStatus.state).toBe("closed")
|
|
53
|
+
expect(logger.log("process.started", {runtimeMode: "reverse-hub"})).toBe("dropped")
|
|
54
|
+
expect((await readdir(directory)).some((name) => name.endsWith(".jsonl.zst.sealed"))).toBeTruthy()
|
|
55
|
+
const closed = await readCompressedLogDirectory(directory)
|
|
56
|
+
expect(closed.records.some((record) => record.event === "log_batch_accepted")).toBeTruthy()
|
|
57
|
+
expect(closed.records.some((record) => record.event === "log_frame_committed")).toBeTruthy()
|
|
58
|
+
expect(closed.records.at(-1)?.event).toBe("log_closed")
|
|
59
|
+
}))
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
61
|
+
it("compressed logger enforces one shared bounded queue with a critical reserve", async () => withTestCleanup(async (after) => {
|
|
62
|
+
const root = await mkdtemp(join(tmpdir(), "socketduct-compressed-pressure-"))
|
|
63
|
+
const directory = join(root, "logs")
|
|
64
|
+
// The reserve must cover the critical event's conservative charge (line bytes × 3 + 128);
|
|
65
|
+
// an accepted-but-undelivered sequence can never be evicted without tearing the durable journal.
|
|
66
|
+
const logger = createCompressedJsonlFileLogger({
|
|
67
|
+
directory,
|
|
68
|
+
role: "reverse-client",
|
|
69
|
+
maxQueueBytes: 4096,
|
|
70
|
+
criticalReserveBytes: 2048,
|
|
71
|
+
frameMaxBytes: 1024,
|
|
72
|
+
flushIntervalMs: 60_000,
|
|
73
|
+
criticalFlushIntervalMs: 60_000
|
|
74
|
+
})
|
|
75
|
+
after(async () => { await logger.close().catch(() => {}); await rm(root, {recursive: true, force: true}) })
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
})
|
|
77
|
+
const outcomes = Array.from({length: 40}, () => logger.log("runtime.status", {virtualStreams: 1}))
|
|
78
|
+
expect(outcomes.includes("dropped") || outcomes.includes("coalesced")).toBeTruthy()
|
|
79
|
+
expect(logger.status().queuedBytes <= 4096).toBeTruthy()
|
|
80
|
+
expect(logger.log("transport.disconnected", {
|
|
81
|
+
clientConnectionGeneration: 1,
|
|
82
|
+
disconnectIncidentId: "AAAAAAAAAAAAAAAAAAAAAA",
|
|
83
|
+
disconnectReason: "remote_eof",
|
|
84
|
+
incidentOrigin: "reverse_client",
|
|
85
|
+
physicalRole: "client",
|
|
86
|
+
fanOutCount: 1,
|
|
87
|
+
resumeSuccessCount: 0,
|
|
88
|
+
terminalStreamCount: 0
|
|
89
|
+
})).toBe("accepted")
|
|
90
|
+
expect(logger.status().queuedBytes <= 4096).toBeTruthy()
|
|
91
|
+
await logger.flush()
|
|
92
|
+
}))
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
94
|
+
it("compressed logger caps worker restarts and retains only bounded critical evidence after failure", async () => withTestCleanup(async (after) => {
|
|
95
|
+
const root = await mkdtemp(join(tmpdir(), "socketduct-compressed-worker-death-"))
|
|
96
|
+
const directory = join(root, "logs")
|
|
97
|
+
const logger = createCompressedJsonlFileLogger({
|
|
98
|
+
directory,
|
|
99
|
+
role: "reverse-hub",
|
|
100
|
+
frameMaxBytes: 256,
|
|
101
|
+
maxSegmentBytes: 1024,
|
|
102
|
+
maxQueueBytes: 65_536,
|
|
103
|
+
criticalReserveBytes: 8192,
|
|
104
|
+
flushIntervalMs: 60_000,
|
|
105
|
+
criticalFlushIntervalMs: 60_000,
|
|
106
|
+
stderr: {write: () => true}
|
|
107
|
+
})
|
|
108
|
+
after(async () => { await logger.close().catch(() => {}); await rm(root, {recursive: true, force: true}) })
|
|
109
|
+
logger.log("process.started", {runtimeMode: "reverse-hub"})
|
|
110
|
+
await logger.flush()
|
|
111
|
+
await rm(directory, {recursive: true, force: true})
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
})
|
|
113
|
+
for (let index = 0; index < 20; index += 1) logger.log("process.started", {runtimeMode: "reverse-hub"})
|
|
114
|
+
await expect(logger.flush()).rejects.toThrow()
|
|
115
|
+
expect(logger.status().state).toBe("failed")
|
|
116
|
+
expect(logger.status().workerRestarts).toBe(3)
|
|
117
|
+
expect(logger.log("runtime.status", {virtualStreams: 1})).toBe("dropped")
|
|
118
|
+
expect(logger.log("process.started", {runtimeMode: "reverse-hub"})).toBe("accepted")
|
|
119
|
+
expect(logger.status().allocatedBytes <= 65_536).toBeTruthy()
|
|
120
|
+
}))
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
})
|
|
122
|
+
it("compressed logger flush is a caller-time durability barrier", async () => withTestCleanup(async (after) => {
|
|
123
|
+
const root = await mkdtemp(join(tmpdir(), "socketduct-compressed-barrier-"))
|
|
124
|
+
const logger = createCompressedJsonlFileLogger({
|
|
125
|
+
directory: join(root, "logs"),
|
|
126
|
+
role: "reverse-hub",
|
|
127
|
+
frameMaxBytes: 256,
|
|
128
|
+
flushIntervalMs: 60_000,
|
|
129
|
+
criticalFlushIntervalMs: 60_000,
|
|
130
|
+
maxQueueBytes: 1_000_000,
|
|
131
|
+
criticalReserveBytes: 1024
|
|
132
|
+
})
|
|
133
|
+
after(async () => { await logger.close().catch(() => {}); await rm(root, {recursive: true, force: true}) })
|
|
134
|
+
await logger.flush()
|
|
135
|
+
expect(logger.log("process.started", {runtimeMode: "reverse-hub"})).toBe("accepted")
|
|
136
|
+
const barrier = logger.flush()
|
|
137
|
+
for (let index = 0; index < 8; index += 1) expect(logger.log("process.started", {runtimeMode: "reverse-hub"})).toBe("accepted")
|
|
138
|
+
await barrier
|
|
139
|
+
const status = logger.status()
|
|
140
|
+
expect(BigInt(status.durableHighWater) >= 1n).toBeTruthy()
|
|
141
|
+
expect(BigInt(status.durableHighWater) < BigInt(status.acceptedHighWater)).toBeTruthy()
|
|
142
|
+
}))
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
144
|
+
it("compressed logger flushes immediately at frame size and critical deadlines do not slide", async () => withTestCleanup(async (after) => {
|
|
145
|
+
const root = await mkdtemp(join(tmpdir(), "socketduct-compressed-deadline-"))
|
|
146
|
+
const directory = join(root, "logs")
|
|
147
|
+
const logger = createCompressedJsonlFileLogger({
|
|
148
|
+
directory,
|
|
149
|
+
role: "reverse-hub",
|
|
150
|
+
frameMaxBytes: 256,
|
|
151
|
+
flushIntervalMs: 60_000,
|
|
152
|
+
criticalFlushIntervalMs: 40,
|
|
153
|
+
maxQueueBytes: 1_000_000,
|
|
154
|
+
criticalReserveBytes: 1024
|
|
155
|
+
})
|
|
156
|
+
after(async () => { await logger.close().catch(() => {}); await rm(root, {recursive: true, force: true}) })
|
|
157
|
+
await logger.flush()
|
|
158
158
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
expect(logger.log("runtime.status", {virtualStreams: 1})).toBe("accepted")
|
|
160
|
+
expect(logger.log("process.started", {runtimeMode: "reverse-hub"})).toBe("accepted")
|
|
161
|
+
await waitUntil(() => BigInt(logger.status().durableHighWater) > 0n, 1_000)
|
|
162
162
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
163
|
+
await logger.flush()
|
|
164
|
+
const before = BigInt(logger.status().durableHighWater)
|
|
165
|
+
const producer = setInterval(() => logger.log("process.started", {runtimeMode: "reverse-hub"}), 5)
|
|
166
|
+
try {
|
|
167
|
+
await waitUntil(() => BigInt(logger.status().durableHighWater) > before, 500)
|
|
168
|
+
} finally {
|
|
169
|
+
clearInterval(producer)
|
|
170
|
+
}
|
|
171
|
+
const durable = await readCompressedLogDirectory(directory)
|
|
172
|
+
expect(durable.records.some((record) => record.event === "runtime.status")).toBeTruthy()
|
|
173
|
+
}))
|
|
173
174
|
})
|
|
174
175
|
|
|
175
176
|
/**
|
|
@@ -179,7 +180,7 @@ test("compressed logger flushes immediately at frame size and critical deadlines
|
|
|
179
180
|
async function waitUntil(predicate, timeoutMs) {
|
|
180
181
|
const deadline = Date.now() + timeoutMs
|
|
181
182
|
while (!predicate()) {
|
|
182
|
-
if (Date.now() >= deadline)
|
|
183
|
+
if (Date.now() >= deadline) throw new Error("timed out waiting for compressed logger progress")
|
|
183
184
|
await new Promise((resolve) => setTimeout(resolve, 5))
|
|
184
185
|
}
|
|
185
186
|
}
|