This commit is contained in:
Andras Schmelczer 2026-09-27 15:59:35 +01:00
commit f87ad0d786
4 changed files with 50 additions and 14 deletions

View file

@ -20,7 +20,7 @@
"fs-extra": "^11.3.2",
"mini-css-extract-plugin": "^2.9.4",
"obsidian": "1.11.0",
"reconcile-text": "^0.11.0",
"reconcile-text": "^0.12.1",
"resolve-url-loader": "^5.0.0",
"sass": "^1.96.0",
"sass-loader": "^16.0.6",

View file

@ -5954,9 +5954,9 @@
}
},
"node_modules/reconcile-text": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/reconcile-text/-/reconcile-text-0.8.0.tgz",
"integrity": "sha512-evskVha3YgpP2ZelsFxP9t7CuKnwE7TrsH3FdrH2mfKbzjUWiNF7scHXsFbFS921lmFlAOB94DHNAWPvL34Mqg==",
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/reconcile-text/-/reconcile-text-0.12.1.tgz",
"integrity": "sha512-UtDykRp8k/hDhMMqcexkeCmGCFy/ScrKFYkIj2lpT7OYEN5eXL6BeUORgf9jPZ+sMf6/z5tZWSOC1Q87jNzhUQ==",
"dev": true,
"license": "MIT"
},
@ -7686,7 +7686,7 @@
"fs-extra": "^11.3.2",
"mini-css-extract-plugin": "^2.9.4",
"obsidian": "1.11.0",
"reconcile-text": "^0.11.0",
"reconcile-text": "^0.12.1",
"resolve-url-loader": "^5.0.0",
"sass": "^1.96.0",
"sass-loader": "^16.0.6",
@ -7740,13 +7740,6 @@
"@codemirror/view": "6.38.6"
}
},
"obsidian-plugin/node_modules/reconcile-text": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/reconcile-text/-/reconcile-text-0.11.0.tgz",
"integrity": "sha512-a3sy3obazoc1BMEHx6IQn8ESZKnakVWZuRLi7OSEB56E8evRtrXBMj7yuo10fMoG4JkcZC6tokOfzpwZAKX+PQ==",
"dev": true,
"license": "MIT"
},
"obsidian-plugin/node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
@ -7775,7 +7768,7 @@
"@types/node": "^24.8.1",
"byte-base64": "^1.1.0",
"minimatch": "^10.0.1",
"reconcile-text": "^0.8.0",
"reconcile-text": "^0.12.1",
"ts-loader": "^9.5.2",
"tslib": "2.8.1",
"tsx": "^4.20.6",

View file

@ -17,7 +17,7 @@
"devDependencies": {
"byte-base64": "^1.1.0",
"minimatch": "^10.0.1",
"reconcile-text": "^0.8.0",
"reconcile-text": "^0.12.1",
"uuid": "^13.0.0",
"@types/node": "^24.8.1",
"ts-loader": "^9.5.2",

View file

@ -89,6 +89,49 @@ test("content edited after planning is merged with the incoming snapshot", async
);
});
test("repeated large text replacements reuse WebAssembly memory", async () => {
// The replacement from fuzz-193329 exhausted reconcile-text 0.8.0's
// allocator over repeated merges, eventually stalling sync and trapping.
const original = (
" مرحباéשלום\t日本語\ncaféalphaalphaनमस्ते🦊🌍café\n" +
"🌍مرحبامرحباनमस्तेשלום\talphaalphacaféمرحبا🌍\r\n日本語مرحبا"
).repeat(128);
const replacement = await hashSnapshot({
content: bytes(
(
"alpha🌍éनमस्ते日本語שלום\uFEFFשלום\t\r\n" +
"\uFEFF🦊 🦊🌍नमस्ते\uFEFFéمرحبا\r\ncafé🌍é🌍"
).repeat(128)
)
});
const f = await fixture({ "a.md": original });
const expected = (await f.files.readSnapshot("a.md"))!;
const apply = async () => {
await f.disk.userWrite("a.md", bytes(original));
await f.files.applyChanges(structuredClone(f.database.state), {
a: { expected, replacement }
});
assert.deepEqual(f.disk.userFiles().get("a.md"), replacement.content);
assert.equal(
f.database.state.documents.a.observedHash,
replacement.hash
);
};
await apply(); // Warm up the module and its allocator before measuring.
const warmedMemory = process.memoryUsage.rss();
for (let repeat = 0; repeat < 4; repeat++) {
await apply();
// Measure resident memory because V8's external counter can omit WASM
// memory after GC. The old allocator grew ~150 MiB on every call.
const growth = process.memoryUsage.rss() - warmedMemory;
assert(
growth < 64 * 1024 * 1024,
`Resident memory grew by ${growth} bytes`
);
}
});
test("deleting the source after planning does not resurrect it", async () => {
const f = await fixture();
const next = structuredClone(f.database.state);