Skip to content

Commit 00510b8

Browse files
committed
fix(wiki): createWikiChild 自动重试服务端锁冲突 (131009)
实测 voice-room v1.9 sync:167 endpoint + 自动分组共 174 个节点要建, 其中 1 个挂在 [131009] lock contention, please retry later。这是飞书 服务端在快速连续给同父建子节点时的瞬时锁竞争。 修:createWikiChild 内置重试,识别 stdout/stderr 含 131009 或 'lock contention' 时退避重试,最多 4 次,间隔 250/500/1000/2000/4000ms。 也覆盖了 stdout 显示 ok=true 但 JSON body 含 ok=false + lock 错误的 case(lark-cli 把 server-level 错误塞在 stdout JSON 里、进程仍 0 退)。
1 parent edf6b71 commit 00510b8

1 file changed

Lines changed: 65 additions & 23 deletions

File tree

src/lark/wiki.ts

Lines changed: 65 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,28 @@ export class WikiError extends Error {
2525
}
2626
}
2727

28+
function looksLikeLockContention(body: string): boolean {
29+
return (
30+
body.includes('131009') ||
31+
body.toLowerCase().includes('lock contention')
32+
);
33+
}
34+
35+
function sleepSync(ms: number): void {
36+
// Synchronous sleep for retry backoff. Avoids requiring caller to be async.
37+
const until = Date.now() + ms;
38+
// eslint-disable-next-line no-empty
39+
const spawnSync = require('node:child_process').spawnSync;
40+
// Use shell sleep for accurate ms-level wait without busy-loop
41+
spawnSync('sleep', [(ms / 1000).toFixed(3)], { stdio: 'ignore', timeout: ms + 5000 });
42+
// Guard in case sleep is unavailable (Windows): fall back to small busy-wait spike
43+
if (Date.now() < until) {
44+
while (Date.now() < until) {
45+
/* busy wait short tail */
46+
}
47+
}
48+
}
49+
2850
function runLark(
2951
bin: string,
3052
args: string[],
@@ -143,38 +165,58 @@ export function createWikiChild(
143165
larkBin = 'lark-cli',
144166
env?: NodeJS.ProcessEnv,
145167
): WikiChild {
146-
const r = runLark(
147-
larkBin,
148-
[
149-
'wiki',
150-
'+node-create',
151-
'--space-id',
152-
spaceId,
153-
'--parent-node-token',
154-
parentNodeToken,
155-
'--title',
156-
title,
157-
'--obj-type',
158-
'docx',
159-
'--node-type',
160-
'origin',
161-
],
162-
env,
163-
60_000,
164-
);
165-
if (!r.ok) {
166-
throw new WikiError(`wiki +node-create "${title}" failed: ${r.stderr || r.stdout}`);
168+
// Lark wiki has server-side lock contention when creating many children under
169+
// the same parent in quick succession (error 131009). Auto-retry a few times
170+
// with exponential backoff.
171+
const args = [
172+
'wiki',
173+
'+node-create',
174+
'--space-id',
175+
spaceId,
176+
'--parent-node-token',
177+
parentNodeToken,
178+
'--title',
179+
title,
180+
'--obj-type',
181+
'docx',
182+
'--node-type',
183+
'origin',
184+
];
185+
const MAX_RETRIES = 4;
186+
let r;
187+
let lastBody = '';
188+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
189+
r = runLark(larkBin, args, env, 60_000);
190+
lastBody = r.stdout || r.stderr;
191+
if (r.ok && !looksLikeLockContention(lastBody)) break;
192+
// Inspect JSON body to detect server-side errors that succeeded at CLI level
193+
if (r.ok) {
194+
try {
195+
const j = JSON.parse(r.stdout);
196+
if (j && j.ok !== false) break; // genuine success
197+
} catch {
198+
break;
199+
}
200+
}
201+
if (!looksLikeLockContention(lastBody) || attempt === MAX_RETRIES) break;
202+
const delayMs = 250 * Math.pow(2, attempt); // 250 / 500 / 1000 / 2000 / 4000
203+
sleepSync(delayMs);
204+
}
205+
if (!r!.ok || looksLikeLockContention(lastBody)) {
206+
throw new WikiError(
207+
`wiki +node-create "${title}" failed after retries: ${lastBody.slice(0, 300)}`,
208+
);
167209
}
168210
let parsed: any;
169211
try {
170-
parsed = JSON.parse(r.stdout);
212+
parsed = JSON.parse(r!.stdout);
171213
} catch (err) {
172214
throw new WikiError(`wiki +node-create returned non-JSON: ${(err as Error).message}`);
173215
}
174216
// Real shape (lark-cli 1.0.32): { data: { node_token, obj_token, ... } } — flat under data
175217
const node = parsed?.data?.node ?? parsed?.data ?? parsed?.node ?? parsed;
176218
if (!node?.node_token || !node?.obj_token) {
177-
throw new WikiError(`wiki +node-create response missing tokens: ${r.stdout.slice(0, 200)}`);
219+
throw new WikiError(`wiki +node-create response missing tokens: ${r!.stdout.slice(0, 200)}`);
178220
}
179221
return {
180222
nodeToken: node.node_token,

0 commit comments

Comments
 (0)