Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions apps/desktop/src/main/remote-agent/__tests__/groupRelay.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { setTimeout as delay } from 'node:timers/promises';

import { REMOTE_AGENT_MAX_RUNS_PER_CONTROLLER } from '@cindy/device-link';
import type { AgentEvent, AgentSessionHandle } from '@cindy/maker-core';
Expand Down Expand Up @@ -205,8 +206,20 @@ beforeEach(() => {
root = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'cindy-group-relay-')));
});

afterEach(() => {
fs.rmSync(root, { recursive: true, force: true });
afterEach(async () => {
// The hosts persist run directories and guest-session state in fire-and-forget promises
// (mkdir + tmp + rename), so a write can still be landing when a test ends: a one-shot rmdir
// sees ENOTEMPTY, and a removal that wins the race is undone when the write recreates `root`.
// Remove asynchronously so those writes can finish, and only stop once `root` has stayed gone
// across two consecutive quiet windows; a removal that still hits ENOTEMPTY is retried by the
// loop rather than failing the hook. If something keeps recreating it, fail instead of leaking.
let absentChecks = 0;
for (let attempt = 0; attempt < 40 && absentChecks < 2; attempt += 1) {
await fs.promises.rm(root, { recursive: true, force: true, maxRetries: 3, retryDelay: 20 }).catch(() => undefined);
await delay(50);
absentChecks = fs.existsSync(root) ? 0 : absentChecks + 1;
Comment on lines +217 to +220

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 在判定清理完成前排空后台写入

在高负载 CI 让 guest-index I/O 超过 100 ms 时,这里仍能在两个 50 ms 采样后返回:runHost.ts 的 startRelay() 会先发布 started、再等待 recordGuestSession(),而 open 路径又以 void ...then(startRelay) 启动它,因此测试和 client.close() 都可能先完成,后台链随后仍会重建 root。相较已有评论检查的版本,最终 diff 的新证据是退出条件虽然改成了 absentChecks < 2,却仍完全不观察 host/promise 生命周期;hook 成功后泄漏的目录会在下一次 beforeEach 覆盖 root 后失去清理机会。请先 await host 的 flush/异步 dispose,再执行最终删除。

AGENTS.md reference: AGENTS.md:L40-L41

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同意:只要清理不观察 host 的 promise 生命周期,它就仍是启发式的;你举的 startRelay() 先发布 started、再等 recordGuestSession() 的路径也属实。

这和 PRRT_kwDOTgdRUs6rJizN 是同一个取舍,我不再继续往测试侧叠等待逻辑:

  • 要真正排空,需要在 runHost.ts 里给后台链路(startRelay/recordGuestSession、影子工作区与附件目录的写入、disposeRun 等)建立统一跟踪,并暴露 whenIdle() 或异步 dispose();
  • 这是供应商组 host 的生命周期设计,这个模块今天还在被维护者频繁改动;
  • 我会等维护者决定:是在本 PR 里加这个接口,还是本 PR 只保留测试侧的缓解、另开 issue 跟进。

在此之前这条线程保持 open。

另:本 head(edf31311a)的 Linux/Windows (2/2) 失败是 remoteAgentRelocationWiring.test.ts「改道的行带上所属电脑」。main 自 7b07864 起同样失败(main client-ci 38107453839),与本 PR 无关;修复在 #5781。

}
if (absentChecks < 2) throw new Error(`test directory ${root} kept being recreated after cleanup`);
});

let runSeq = 0;
Expand Down
Loading