Skip to content

Commit 0d9dfdb

Browse files
committed
fix(server): 限制隔离状态抓取的整体耗时
为隔离抓取的配置读取、下载、对象解析及引用发布共享五秒超时预算,超时后完成私有目录清理并沿用失败退避。 保留单个 pack 与索引发布的短暂不可中断区,避免超时造成半发布状态。 补充七种本地 Git 阻塞及跨阶段累计预算回归,48 个定向测试和服务端类型检查通过。
1 parent 44bb6e4 commit 0d9dfdb

2 files changed

Lines changed: 125 additions & 1 deletion

File tree

‎apps/server/src/vcs/GitVcsDriverCore.test.ts‎

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2875,6 +2875,116 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => {
28752875
}),
28762876
);
28772877

2878+
it.effect.each([
2879+
"object format",
2880+
"configuration",
2881+
"initialization",
2882+
"read target",
2883+
"resolve",
2884+
"promote ref",
2885+
"read promoted target",
2886+
"shared budget",
2887+
] as const)("bounds the complete status fetch when %s stalls", (stage) =>
2888+
Effect.gen(function* () {
2889+
const cwd = yield* makeTmpDir();
2890+
const remote = yield* makeTmpDir("git-vcs-driver-remote-");
2891+
const updater = yield* makeTmpDir("git-vcs-driver-updater-");
2892+
const { initialBranch } = yield* initRepoWithCommit(cwd);
2893+
const originalCommit = yield* git(cwd, ["rev-parse", "HEAD"]);
2894+
yield* git(remote, ["init", "--bare"]);
2895+
yield* git(cwd, ["remote", "add", "origin", remote]);
2896+
yield* git(cwd, ["push", "-u", "origin", initialBranch]);
2897+
yield* git(updater, ["clone", "--branch", initialBranch, remote, "."]);
2898+
yield* git(updater, ["config", "user.email", "[email protected]"]);
2899+
yield* git(updater, ["config", "user.name", "Test"]);
2900+
yield* writeTextFile(updater, "remote.txt", "remote update\n");
2901+
yield* git(updater, ["add", "remote.txt"]);
2902+
yield* git(updater, ["commit", "-m", "remote update"]);
2903+
yield* git(updater, ["push"]);
2904+
2905+
const delegate = yield* ChildProcessSpawner.ChildProcessSpawner;
2906+
const blocked = yield* Deferred.make<void>();
2907+
const release = yield* Deferred.make<void>();
2908+
const interrupted = yield* Deferred.make<void>();
2909+
const preparationBlocked = yield* Deferred.make<void>();
2910+
const releasePreparation = yield* Deferred.make<void>();
2911+
let targetReads = 0;
2912+
let attempts = 0;
2913+
const spawner = ChildProcessSpawner.make((command) =>
2914+
Effect.gen(function* () {
2915+
if (ChildProcess.isStandardCommand(command)) {
2916+
const args = command.args;
2917+
if (stage === "shared budget" && args.includes("extensions.objectFormat")) {
2918+
yield* Deferred.succeed(preparationBlocked, undefined);
2919+
yield* Deferred.await(releasePreparation);
2920+
}
2921+
const readsTarget =
2922+
args.includes("rev-parse") &&
2923+
args.at(-1) === `refs/remotes/origin/${initialBranch}`;
2924+
if (readsTarget) targetReads += 1;
2925+
if (stage === "read promoted target" && args.includes("update-ref")) {
2926+
return ChildProcessSpawner.makeHandle({
2927+
...makeSuccessfulHandle(""),
2928+
exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(1)),
2929+
});
2930+
}
2931+
const shouldBlock =
2932+
(stage === "object format" && args.includes("extensions.objectFormat")) ||
2933+
(stage === "configuration" && args.includes("--show-scope")) ||
2934+
(stage === "initialization" && args[0] === "init" && args.includes("--bare")) ||
2935+
(stage === "read target" && readsTarget && targetReads === 1) ||
2936+
((stage === "resolve" || stage === "shared budget") &&
2937+
args.includes("rev-parse") &&
2938+
command.options.env?.GIT_OBJECT_DIRECTORY !== undefined) ||
2939+
(stage === "promote ref" && args.includes("update-ref")) ||
2940+
(stage === "read promoted target" && readsTarget && targetReads === 2);
2941+
if (shouldBlock) {
2942+
attempts += 1;
2943+
yield* Deferred.succeed(blocked, undefined);
2944+
yield* Deferred.await(release).pipe(
2945+
Effect.onInterrupt(() => Deferred.succeed(interrupted, undefined)),
2946+
);
2947+
}
2948+
}
2949+
return yield* delegate.spawn(command);
2950+
}),
2951+
);
2952+
const driver = yield* makeGitVcsDriverCore().pipe(
2953+
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
2954+
Effect.provide(layerServerConfig),
2955+
);
2956+
const refresh = yield* driver
2957+
.statusDetailsRemote(cwd)
2958+
.pipe(Effect.forkChild({ startImmediately: true }));
2959+
if (stage === "shared budget") {
2960+
yield* Deferred.await(preparationBlocked);
2961+
yield* TestClock.adjust("3 seconds");
2962+
yield* Deferred.succeed(releasePreparation, undefined);
2963+
}
2964+
yield* Deferred.await(blocked);
2965+
yield* TestClock.adjust(stage === "shared budget" ? "2 seconds" : "5 seconds");
2966+
const interruptedWithinDeadline = yield* Deferred.isDone(interrupted);
2967+
// Always release the injected stall so the pre-fix regression fails without leaking fibers.
2968+
yield* Deferred.succeed(release, undefined);
2969+
const status = yield* Fiber.join(refresh);
2970+
assert.isTrue(interruptedWithinDeadline);
2971+
assert.equal(status.behindCount, 0);
2972+
assert.equal(
2973+
yield* git(cwd, ["rev-parse", `refs/remotes/origin/${initialBranch}`]),
2974+
originalCommit,
2975+
);
2976+
assert.equal((yield* driver.statusDetailsRemote(cwd)).behindCount, 0);
2977+
assert.equal(attempts, 1);
2978+
const fs = yield* FileSystem.FileSystem;
2979+
const path = yield* Path.Path;
2980+
const isolatedRoot = path.join(cwd, ".git", "objects", "t3-status-fetch");
2981+
if (yield* fs.exists(isolatedRoot)) {
2982+
assert.deepStrictEqual(yield* fs.readDirectory(isolatedRoot), []);
2983+
}
2984+
yield* git(cwd, ["fsck", "--full", "--no-dangling"]);
2985+
}),
2986+
);
2987+
28782988
it.effect("refreshes status without a second upload-pack process", () =>
28792989
Effect.gen(function* () {
28802990
const cwd = yield* makeTmpDir();

‎apps/server/src/vcs/GitVcsDriverCore.ts‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1714,7 +1714,21 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
17141714
{ concurrency: 1, discard: true },
17151715
);
17161716
}).pipe(Effect.ensuring(cleanupIsolatedFetch));
1717-
});
1717+
}).pipe(
1718+
Effect.timeoutOption(STATUS_UPSTREAM_REFRESH_TIMEOUT),
1719+
Effect.flatMap((result) =>
1720+
Effect.fromOption(
1721+
result,
1722+
() =>
1723+
new GitCommandError({
1724+
operation: "GitVcsDriver.fetchRemoteForStatus",
1725+
command: "git",
1726+
cwd: gitCommonDir,
1727+
detail: "Git status refresh timed out.",
1728+
}),
1729+
),
1730+
),
1731+
);
17181732

17191733
const resolveRepositoryPathsUncached = Effect.fn("resolveRepositoryPathsUncached")(function* (
17201734
cwd: string,

0 commit comments

Comments
 (0)