Skip to content

Commit eac2df8

Browse files
hongtaohongtao
authored andcommitted
perf: reuse bounded byte accounting for snapshot admission
1 parent 50f73e1 commit eac2df8

7 files changed

Lines changed: 341 additions & 5 deletions
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
{
2+
"schemaVersion": 1,
3+
"date": "2026-10-28",
4+
"baseSource": "50f73e151c62b74fd772cbfdb575c54610cc8bc2",
5+
"change": "Daily snapshot shape admission consumes counters from the complete descriptor-only encoder instead of serializing the retained history and rescanning all raw text.",
6+
"bounds": {
7+
"encodedBytes": "Exact count from unchanged scalar accounting.",
8+
"rawBytesUpperBound": "Encoded bytes plus ASCII array-index key lengths; conservatively bounds the former recursive raw-key count.",
9+
"maximumBytes": 117440512,
10+
"nodes": 65536,
11+
"depth": 64,
12+
"nearBoundary": "Conservative proof failure retains the existing exact authenticated hydration; legacy path unchanged."
13+
},
14+
"regression": {
15+
"beforeSkeletonSerializations": 1,
16+
"afterSkeletonSerializations": 0,
17+
"beforeExitCode": 1,
18+
"pureRelated": {
19+
"tests": 140,
20+
"pass": 140,
21+
"fail": 0,
22+
"skip": 0
23+
},
24+
"expanded": {
25+
"tests": 168,
26+
"pass": 167,
27+
"fail": 1,
28+
"skip": 0,
29+
"failure": "actual encrypted worker exposes reference save/load, exact pages and legacy load across reopen",
30+
"code": "context_recovery_storage_unavailable",
31+
"scope": "Known Mac native startup failure, retained; Mac platform-conditional returns do not grant Windows qualification."
32+
}
33+
},
34+
"checks": {
35+
"syntaxFiles": 1737,
36+
"targetedLintPassed": true,
37+
"ciGovernancePassed": true,
38+
"unifiedDiscoveryConfirmed": true
39+
},
40+
"benchmark": {
41+
"sourceBase": "50f73e151c62b74fd772cbfdb575c54610cc8bc2",
42+
"diagnosticDirty": true,
43+
"privateHistoryBytes": 8388608,
44+
"nativeStorage": false,
45+
"qualification": false,
46+
"realModelRequests": 0,
47+
"samples": [
48+
{
49+
"mode": "baseline",
50+
"sourceSha256": "a0c5484ac757e95f3023a9103667040472650de3acc64996ad57f0f121364820",
51+
"timesMs": [181, 161, 159, 173, 175],
52+
"skeletonSerializations": 5
53+
},
54+
{
55+
"mode": "modified",
56+
"sourceSha256": "79d56799fafcf79ca0da042001d3becbd74b417b6f2f368e2c981f78f3321f9f",
57+
"timesMs": [162, 171, 164, 190, 189],
58+
"skeletonSerializations": 0
59+
}
60+
]
61+
},
62+
"benchmarkConclusion": "Five 8MiB in-memory snapshot samples do not demonstrate wall-time improvement. Additional full-tree serializations fall from five to zero. No Windows five-second qualification.",
63+
"oldSourceWindowsDiagnostic": {
64+
"sourceSha": "ae664cc0f6998eac24bd84bdbd19ab14a5ae9020",
65+
"taskResult": 0,
66+
"childExitCode": 0,
67+
"controllerElapsedMs": 161196,
68+
"originalDeadlineMs": 180000,
69+
"stageIntervals": {
70+
"buildMs": [10702, 10598, 7181, 9708],
71+
"saveMs": [7407, 7544, 12742, 16027],
72+
"reopenMs": 23062,
73+
"loadMs": [10043, 10196, 10391, 10990]
74+
},
75+
"qualification": false,
76+
"previous180SecondFailureRetained": true,
77+
"diagnosticFailuresRetained": [
78+
"Owned-job stdin handshake blocked behind extra Node test parent, before product invocation.",
79+
"Incorrect expected stage count rejected seven actual stage sites before product invocation."
80+
]
81+
},
82+
"baseCoverageFailure": {
83+
"sourceSha": "50f73e151c62b74fd772cbfdb575c54610cc8bc2",
84+
"runId": 40750514561,
85+
"jobId": 123035650972,
86+
"conclusion": "failure",
87+
"fullLogCharacters": 3741220,
88+
"failedTest": "actual core chat WBIPC recovery has exclusive ownership and live credential revocation",
89+
"failure": [
90+
{
91+
"kind": "chat",
92+
"mode": "long-persistent",
93+
"turn": 0,
94+
"phase": "request_failed",
95+
"elapsedMs": 5003,
96+
"requests": 1,
97+
"handshakes": 1,
98+
"roots": 1
99+
}
100+
],
101+
"commits": [
102+
{
103+
"kind": "chat",
104+
"mode": "long-persistent",
105+
"phase": "owner-stage",
106+
"sequence": 0,
107+
"operation": "startup",
108+
"stage": "sqlite_commit",
109+
"elapsedMs": 1450,
110+
"startedAtUnixMs": 1793214679615,
111+
"finishedAtUnixMs": 1793214681065,
112+
"wallClockRollbackObserved": false,
113+
"threadCpuUserUs": 0,
114+
"threadCpuSystemUs": 0,
115+
"ownerEpoch": 1
116+
},
117+
{
118+
"kind": "chat",
119+
"mode": "long-persistent",
120+
"phase": "owner-stage",
121+
"sequence": 3,
122+
"operation": "save-reference-state",
123+
"stage": "sqlite_commit",
124+
"elapsedMs": 2019,
125+
"startedAtUnixMs": 1793214683474,
126+
"finishedAtUnixMs": 1793214685493,
127+
"wallClockRollbackObserved": false,
128+
"threadCpuUserUs": 0,
129+
"threadCpuSystemUs": 0,
130+
"ownerEpoch": 1
131+
},
132+
{
133+
"kind": "chat",
134+
"mode": "long-persistent",
135+
"phase": "owner-stage",
136+
"sequence": 4,
137+
"operation": "save-reference-state",
138+
"stage": "sqlite_commit",
139+
"elapsedMs": 5448,
140+
"startedAtUnixMs": 1793214687388,
141+
"finishedAtUnixMs": 1793214692836,
142+
"wallClockRollbackObserved": false,
143+
"threadCpuUserUs": 0,
144+
"threadCpuSystemUs": 0,
145+
"ownerEpoch": 1
146+
}
147+
],
148+
"qualification": false
149+
},
150+
"invariants": [
151+
"Every snapshot independently authenticates actual manifest and payload bytes.",
152+
"No authentication proof survives COMMIT.",
153+
"Original FULL/DELETE, expiry, exact ACK, budgets and all deadlines unchanged."
154+
],
155+
"new24hStarted": false,
156+
"realModelRequests": 0,
157+
"remaining": "Persistent five-second tail, current-source full gates, latest local installer and both clients, secure quota reconciliation and real providers, valid real24h/expiry, paused maximum-capacity and controlled self-use remain incomplete."
158+
}

‎docs/context-recovery-handoff.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# 上下文恢复换机交接
22

3+
## 2026-10-28:消除快照容量证明的整树序列化;50f 五秒 COMMIT 失败保持
4+
5+
实际Windows固定ae旧源码四根32MiB阶段诊断已自然退出0,控制器161196ms;构造10702/10598/7181/9708ms,保存7407/7544/12742/16027ms,同目录重开23062ms,加载10043/10196/10391/10990ms,完成会话恢复。仅授旧源码诊断范围,不覆盖原180秒取消或授最新版本/五秒/24h资格。此前两次诊断自身错误(额外测试父进程吞所属Job握手、阶段数预期错误)发生在产品调用前,已分别冻结,不当作产品根因。
6+
7+
沿重开快照路径发现:完成有界准入后仍JSON.stringify整棵历史并递归扫描原文字节。新回归先红:额外整树序列化1次,现0次;同一编码器仍完整描述符克隆并检查原节点/深度/编码字节,测量只返回计数。编码字节加数组ASCII索引键长度,构成旧raw-key计数的保守上界;只用于v9/v10容量证明,临界失败仍转原完整认证hydrate,旧布局保持原路径。没有跨COMMIT认证缓存或原期限/ACK/持久语义变更。
8+
9+
纯相关四文件140通过;扩大168项167通过/1个既有Mac原生开库不可用,零skip,Windows条件体返回不授Windows实际资格。新度量用例覆盖正常/空/转义/代理项/极端数组键/非法/资源上限/Proxy/getter/后续修改,统一unit发现确认;1737语法、定向Lint及CI治理通过。同8MiB内存快照五次基线181/161/159/173/175ms,修改162/171/164/190/189ms:没有墙钟提速证明,只确认额外整树序列化5→0,不授五秒修复。
10+
11+
50f普通40750514561终态failure:七作业成功,coverage123035650972完整3741220字符日志中Chat long-persistent第0轮实际5003ms请求失败,同轮COMMIT2019/5448ms且线程CPU记录0;不能用JS去重解释或掩盖此持久等待。正式与额外I/O尚未终态;b7的4634ms COMMIT/5009ms请求及原生拒绝子进程退出失败分别保留。证据见context-recovery-evidence/snapshot-shape-byte-measurement.json。没有有效24h在跑/没有重启,模型0、旧实例与最大容量暂停保持。下一步需进一步区分同轮SQL提交的持久I/O等待与观测环境成本,保留原门禁,不移除认证或持久步骤制造绿色。
12+
13+
314
## 2026-10-28:消除 checkpoint 转引用的连续重复准入;五秒与完整容量失败保持
415

516
referenceStateFromCheckpoint原先先完整normalize,再交parseRootCheckpoint再次normalize同份私有历史。回归先红:实际2次/预期1次;现专用入口从未知输入完整准入一次,再对本次拥有的root完成同样摘要重算和全部语义校验,外部expected-root解析接口仍独立准入且保持原关联检查。没有暴露可跳校验的内部函数、跨调用缓存或跨COMMIT复用认证。覆盖逐调用重新准入、输出隔离、旧无publications格式、有效摘要但非法budget、摘要/期限篡改、空/非法/getter/Proxy输入;相关四文件174项通过,零fail/skip,但Mac中Windows条件体返回不授Windows实际资格。

‎docs/context-recovery-implementation-status.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# HTTPS Proxy 多端上下文恢复实施状态
22

3+
## 2026-10-28:消除快照容量证明的整树序列化;50f 五秒 COMMIT 失败保持
4+
5+
实际Windows固定ae旧源码四根32MiB阶段诊断已自然退出0,控制器161196ms;构造10702/10598/7181/9708ms,保存7407/7544/12742/16027ms,同目录重开23062ms,加载10043/10196/10391/10990ms,完成会话恢复。仅授旧源码诊断范围,不覆盖原180秒取消或授最新版本/五秒/24h资格。此前两次诊断自身错误(额外测试父进程吞所属Job握手、阶段数预期错误)发生在产品调用前,已分别冻结,不当作产品根因。
6+
7+
沿重开快照路径发现:完成有界准入后仍JSON.stringify整棵历史并递归扫描原文字节。新回归先红:额外整树序列化1次,现0次;同一编码器仍完整描述符克隆并检查原节点/深度/编码字节,测量只返回计数。编码字节加数组ASCII索引键长度,构成旧raw-key计数的保守上界;只用于v9/v10容量证明,临界失败仍转原完整认证hydrate,旧布局保持原路径。没有跨COMMIT认证缓存或原期限/ACK/持久语义变更。
8+
9+
纯相关四文件140通过;扩大168项167通过/1个既有Mac原生开库不可用,零skip,Windows条件体返回不授Windows实际资格。新度量用例覆盖正常/空/转义/代理项/极端数组键/非法/资源上限/Proxy/getter/后续修改,统一unit发现确认;1737语法、定向Lint及CI治理通过。同8MiB内存快照五次基线181/161/159/173/175ms,修改162/171/164/190/189ms:没有墙钟提速证明,只确认额外整树序列化5→0,不授五秒修复。
10+
11+
50f普通40750514561终态failure:七作业成功,coverage123035650972完整3741220字符日志中Chat long-persistent第0轮实际5003ms请求失败,同轮COMMIT2019/5448ms且线程CPU记录0;不能用JS去重解释或掩盖此持久等待。正式与额外I/O尚未终态;b7的4634ms COMMIT/5009ms请求及原生拒绝子进程退出失败分别保留。证据见context-recovery-evidence/snapshot-shape-byte-measurement.json。没有有效24h在跑/没有重启,模型0、旧实例与最大容量暂停保持。下一步需进一步区分同轮SQL提交的持久I/O等待与观测环境成本,保留原门禁,不移除认证或持久步骤制造绿色。
12+
13+
314
## 2026-10-28:消除 checkpoint 转引用的连续重复准入;五秒与完整容量失败保持
415

516
referenceStateFromCheckpoint原先先完整normalize,再交parseRootCheckpoint再次normalize同份私有历史。回归先红:实际2次/预期1次;现专用入口从未知输入完整准入一次,再对本次拥有的root完成同样摘要重算和全部语义校验,外部expected-root解析接口仍独立准入且保持原关联检查。没有暴露可跳校验的内部函数、跨调用缓存或跨COMMIT复用认证。覆盖逐调用重新准入、输出隔离、旧无publications格式、有效摘要但非法budget、摘要/期限篡改、空/非法/getter/Proxy输入;相关四文件174项通过,零fail/skip,但Mac中Windows条件体返回不授Windows实际资格。

‎lib/proxy/context-recovery-state-encoder.js‎

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ const RECOVERY_STATE_BYTES = 112 * 1024 * 1024;
1414
function invalid() {
1515
return new TypeError('invalid_context_recovery_state_data');
1616
}
17-
function cloneRecoveryStateData(input, maximum = RECOVERY_STATE_BYTES, facts) {
17+
function encodeRecoveryStateData(input, maximum = RECOVERY_STATE_BYTES, facts, measure = false) {
1818
if (!Number.isSafeInteger(maximum) || maximum < 1 || maximum > RECOVERY_STATE_BYTES + 4096)
1919
throw invalid();
2020
let remaining = maximum,
2121
nodes = 65536;
22+
let arrayKeyBytes = 0;
2223
const ancestors = new Set();
2324
// Short checkpoint IDs must not consume every memo slot before repeated
2425
// original bodies arrive. Keep both tiers local to this parse, with the
@@ -152,6 +153,7 @@ function cloneRecoveryStateData(input, maximum = RECOVERY_STATE_BYTES, facts) {
152153
if (typeof name !== 'string' || !slot?.enumerable || !Object.hasOwn(slot, 'value'))
153154
throw invalid();
154155
if (index) spend(1);
156+
if (measure && array) arrayKeyBytes += name.length;
155157
const keyBytes = array ? 0 : stringBytes(name, collect) + 1;
156158
if (!array) spend(keyBytes);
157159
const keyRaw = collect ? (array ? Buffer.byteLength(name) : latestRaw) : 0;
@@ -192,7 +194,19 @@ function cloneRecoveryStateData(input, maximum = RECOVERY_STATE_BYTES, facts) {
192194
} else if (facts && depth === 0) Object.freeze(output);
193195
return output;
194196
}
195-
return clone(input, 0);
197+
const value = clone(input, 0);
198+
// JSON covers all scalar/key raw bytes except array-index property names.
199+
// Adding those ASCII names conservatively bounds the legacy raw-key metric.
200+
// Only counters escape measurement; the private clone is not retained.
201+
return measure
202+
? { encodedBytes: maximum - remaining, rawBytesUpperBound: maximum - remaining + arrayKeyBytes }
203+
: value;
204+
}
205+
function cloneRecoveryStateData(input, maximum, facts) {
206+
return encodeRecoveryStateData(input, maximum, facts);
207+
}
208+
function measureRecoveryStateData(input, maximum) {
209+
return encodeRecoveryStateData(input, maximum, undefined, true);
196210
}
197211

198-
module.exports = { RECOVERY_STATE_BYTES, cloneRecoveryStateData };
212+
module.exports = { RECOVERY_STATE_BYTES, cloneRecoveryStateData, measureRecoveryStateData };

‎lib/proxy/context-recovery-storage-blobs.js‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const {
1414
RECOVERY_STATE_BYTES,
1515
} = require('./context-recovery-state-data');
1616
const { recoveryRootCapacity } = require('./context-recovery-capacity');
17+
const { measureRecoveryStateData } = require('./context-recovery-state-encoder');
1718
const { parseReferencePayloads } = require('./context-recovery-reference-write');
1819
const {
1920
parseReferenceState,
@@ -205,7 +206,7 @@ function createRecoveryBlobStorage(database, cipher, getStoreId, enabled) {
205206
const empty = (list) => list.map((source) => ({ ...source, output: '' }));
206207
// The exact hydrated shape, with scalar empty strings instead of originals,
207208
// preserves both normalizers' node/depth and array-prefix requirements.
208-
const skeleton = normalizeRecoveryCheckpointData({
209+
const input = {
209210
version: 1,
210211
kind: 'recovery-write-ahead',
211212
digest: '0'.repeat(64),
@@ -221,7 +222,21 @@ function createRecoveryBlobStorage(database, cipher, getStoreId, enabled) {
221222
},
222223
stagedSources: empty(reference.stagedSources),
223224
publications: reference.publications,
224-
});
225+
};
226+
if (sourceVersion === 9 || sourceVersion === 10) {
227+
const size = measureRecoveryStateData(input);
228+
const originals = referenceSources(reference).reduce(
229+
(bytes, source) => bytes + source.bytes,
230+
0
231+
);
232+
// The conservative raw bound may request the existing exact hydration
233+
// near a limit; it never admits more bytes, nodes or depth than before.
234+
return (
235+
Math.max(size.encodedBytes, size.rawBytesUpperBound) + 6 * originals <=
236+
RECOVERY_STATE_BYTES
237+
);
238+
}
239+
const skeleton = normalizeRecoveryCheckpointData(input);
225240
function rawBytes(value) {
226241
if (typeof value === 'string') return Buffer.byteLength(value);
227242
if (!value || typeof value !== 'object') return 0;
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
'use strict';
2+
3+
const test = require('node:test');
4+
const assert = require('node:assert/strict');
5+
const {
6+
cloneRecoveryStateData,
7+
measureRecoveryStateData,
8+
} = require('../lib/proxy/context-recovery-state-encoder');
9+
10+
function rawBytes(value) {
11+
if (typeof value === 'string') return Buffer.byteLength(value);
12+
if (!value || typeof value !== 'object') return 0;
13+
return Object.keys(value).reduce(
14+
(bytes, key) => bytes + Buffer.byteLength(key) + rawBytes(value[key]),
15+
0
16+
);
17+
}
18+
19+
test('state measurement preserves encoded bytes and conservatively bounds raw array keys', () => {
20+
for (const value of [
21+
null,
22+
true,
23+
false,
24+
-0,
25+
1e100,
26+
'',
27+
'原文🙂',
28+
'\ud800',
29+
'\udc00',
30+
'\0\b\t\n\f\r"\\',
31+
[],
32+
{},
33+
[null, false, -0],
34+
{ 'escaped"key': ['原文🙂', { nested: '\ud800' }] },
35+
Array(1025).fill(''),
36+
{ nested: [Array(10000).fill(''), ['exact']] },
37+
{ first: 'x'.repeat(600000), second: 'x'.repeat(600000) },
38+
Array.from({ length: 65536 }, (_, index) => String.fromCharCode(index)).join(''),
39+
]) {
40+
const encodedBytes = Buffer.byteLength(JSON.stringify(value));
41+
const measured = measureRecoveryStateData(value, encodedBytes);
42+
assert.equal(measured.encodedBytes, encodedBytes);
43+
assert.ok(measured.rawBytesUpperBound >= rawBytes(value));
44+
assert.deepEqual(Object.keys(measured).sort(), ['encodedBytes', 'rawBytesUpperBound']);
45+
assert.equal(
46+
JSON.stringify(cloneRecoveryStateData(value, encodedBytes)),
47+
JSON.stringify(value)
48+
);
49+
if (encodedBytes > 1) assert.throws(() => measureRecoveryStateData(value, encodedBytes - 1));
50+
}
51+
});
52+
53+
test('state measurement keeps complete descriptor, depth and occurrence admission', () => {
54+
let touched = 0;
55+
const hook = () => {
56+
touched++;
57+
throw Error('must not run');
58+
};
59+
const revoked = Proxy.revocable({}, {});
60+
revoked.revoke();
61+
const cycle = {};
62+
cycle.self = cycle;
63+
const sparse = [];
64+
sparse.length = 1;
65+
let deep = 'leaf';
66+
for (let index = 0; index < 65; index++) deep = [deep];
67+
for (const value of [
68+
undefined,
69+
NaN,
70+
Infinity,
71+
1n,
72+
() => {},
73+
cycle,
74+
sparse,
75+
deep,
76+
Array(65536).fill(null),
77+
Object.defineProperty({}, 'value', { enumerable: true, get: hook }),
78+
new Proxy({}, { ownKeys: hook, getPrototypeOf: hook, getOwnPropertyDescriptor: hook }),
79+
revoked.proxy,
80+
new Date(),
81+
{ [Symbol('hidden')]: 'value' },
82+
]) {
83+
assert.throws(() => cloneRecoveryStateData(value));
84+
assert.throws(() => measureRecoveryStateData(value));
85+
}
86+
assert.equal(touched, 0);
87+
for (const maximum of [0, -1, NaN, Infinity, 1.5, 112 * 1024 * 1024 + 4097])
88+
assert.throws(() => measureRecoveryStateData({}, maximum));
89+
});
90+
91+
test('state measurement counts without serialization and never retains prior admission', (t) => {
92+
const value = { entries: Array(32).fill('private synthetic body') };
93+
const expected = Buffer.byteLength(JSON.stringify(value));
94+
t.mock.method(JSON, 'stringify', (input) => {
95+
assert.ok(input === null || typeof input !== 'object', 'must not serialize the retained tree');
96+
return String(input);
97+
});
98+
assert.equal(measureRecoveryStateData(value).encodedBytes, expected);
99+
value.entries.push('changed');
100+
assert.ok(measureRecoveryStateData(value).encodedBytes > expected);
101+
Object.defineProperty(value, 'later', {
102+
enumerable: true,
103+
get() {
104+
return assert.fail('getter invoked');
105+
},
106+
});
107+
assert.throws(() => measureRecoveryStateData(value));
108+
});

0 commit comments

Comments
 (0)