authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-06-16 10:54:00-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-06-16 10:54:00-07:00
loga5efb7cb6b9e80b9543bc3e0aabfea37d3b459c5
treedea81b01ef04943b45317f5dc3600a4dbb452db9
parent8f9911f79dcfa6890c6f14f9c712c236e8453cea
signature Signed by PGP key B5690EEEBB952194

Parallelize VCS status refresh and status reads (#3112)


3 files changed, 140 insertions(+), 30 deletions(-)

apps/server/src/git/GitManager.ts+3-1
...@@ -1361,7 +1361,9 @@ export const makeGitManager = Effect.fn("makeGitManager")(function* () {...@@ -1361,7 +1361,9 @@ export const makeGitManager = Effect.fn("makeGitManager")(function* () {
1361 },1361 },
1362 );1362 );
1363 const status: GitManagerShape["status"] = Effect.fn("status")(function* (input) {1363 const status: GitManagerShape["status"] = Effect.fn("status")(function* (input) {
1364 const [local, remote] = yield* Effect.all([localStatus(input), remoteStatus(input)]);1364 const [local, remote] = yield* Effect.all([localStatus(input), remoteStatus(input)], {
1365 concurrency: "unbounded",
1366 });
1365 return mergeGitStatusParts(local, remote);1367 return mergeGitStatusParts(local, remote);
1366 });1368 });
1367 const invalidateLocalStatus: GitManagerShape["invalidateLocalStatus"] = Effect.fn(1369 const invalidateLocalStatus: GitManagerShape["invalidateLocalStatus"] = Effect.fn(
apps/server/src/vcs/VcsStatusBroadcaster.test.ts+66
...@@ -17,6 +17,7 @@ import type {...@@ -17,6 +17,7 @@ import type {
17 VcsStatusResult,17 VcsStatusResult,
18 VcsStatusStreamEvent,18 VcsStatusStreamEvent,
19} from "@t3tools/contracts";19} from "@t3tools/contracts";
20import { GitManagerError } from "@t3tools/contracts";
2021
21import * as VcsStatusBroadcaster from "./VcsStatusBroadcaster.ts";22import * as VcsStatusBroadcaster from "./VcsStatusBroadcaster.ts";
22import * as GitWorkflowService from "../git/GitWorkflowService.ts";23import * as GitWorkflowService from "../git/GitWorkflowService.ts";
...@@ -149,6 +150,71 @@ describe("VcsStatusBroadcaster", () => {...@@ -149,6 +150,71 @@ describe("VcsStatusBroadcaster", () => {
149 }).pipe(Effect.provide(makeTestLayer(state)));150 }).pipe(Effect.provide(makeTestLayer(state)));
150 });151 });
151152
153 it.effect("keeps the cached snapshot unchanged when a refresh branch fails", () => {
154 const state = {
155 currentLocalStatus: baseLocalStatus,
156 currentRemoteStatus: baseRemoteStatus,
157 localStatusCalls: 0,
158 remoteStatusCalls: 0,
159 localInvalidationCalls: 0,
160 remoteInvalidationCalls: 0,
161 failRemoteStatus: false,
162 };
163 const testLayer = VcsStatusBroadcaster.layer.pipe(
164 Layer.provideMerge(NodeServices.layer),
165 Layer.provide(
166 Layer.mock(GitWorkflowService.GitWorkflowService)({
167 localStatus: () =>
168 Effect.sync(() => {
169 state.localStatusCalls += 1;
170 return state.currentLocalStatus;
171 }),
172 remoteStatus: () =>
173 Effect.suspend(() => {
174 state.remoteStatusCalls += 1;
175 return state.failRemoteStatus
176 ? Effect.fail(
177 new GitManagerError({
178 operation: "VcsStatusBroadcaster.test",
179 detail: "remote status failed",
180 }),
181 )
182 : Effect.succeed(state.currentRemoteStatus);
183 }),
184 invalidateLocalStatus: () =>
185 Effect.sync(() => {
186 state.localInvalidationCalls += 1;
187 }),
188 invalidateRemoteStatus: () =>
189 Effect.sync(() => {
190 state.remoteInvalidationCalls += 1;
191 }),
192 }),
193 ),
194 );
195
196 return Effect.gen(function* () {
197 const broadcaster = yield* VcsStatusBroadcaster.VcsStatusBroadcaster;
198 yield* broadcaster.getStatus({ cwd: "/repo" });
199
200 state.currentLocalStatus = {
201 ...baseLocalStatus,
202 refName: "feature/partial-refresh",
203 };
204 state.currentRemoteStatus = {
205 ...baseRemoteStatus,
206 aheadCount: 3,
207 };
208 state.failRemoteStatus = true;
209
210 const refreshExit = yield* broadcaster.refreshStatus("/repo").pipe(Effect.exit);
211 const cached = yield* broadcaster.getStatus({ cwd: "/repo" });
212
213 assert.isTrue(Exit.isFailure(refreshExit));
214 assert.deepStrictEqual(cached, baseStatus);
215 }).pipe(Effect.provide(testLayer));
216 });
217
152 it.effect("refreshes only the cached local snapshot when requested", () => {218 it.effect("refreshes only the cached local snapshot when requested", () => {
153 const state = {219 const state = {
154 currentLocalStatus: baseLocalStatus,220 currentLocalStatus: baseLocalStatus,
apps/server/src/vcs/VcsStatusBroadcaster.ts+71-29
...@@ -179,18 +179,53 @@ export const layer = Layer.effect(...@@ -179,18 +179,53 @@ export const layer = Layer.effect(
179 },179 },
180 );180 );
181181
182 const loadLocalStatus = Effect.fn("VcsStatusBroadcaster.loadLocalStatus")(function* (182 const updateCachedStatus = Effect.fn("VcsStatusBroadcaster.updateCachedStatus")(function* (
183 cwd: string,183 cwd: string,
184 local: VcsStatusLocalResult,
185 remote: VcsStatusRemoteResult | null,
186 options?: { publish?: boolean },
184 ) {187 ) {
185 const local = yield* workflow.localStatus({ cwd });188 const nextLocal = {
186 return yield* updateCachedLocalStatus(cwd, local);189 fingerprint: fingerprintStatusPart(local),
190 value: local,
191 } satisfies CachedValue<VcsStatusLocalResult>;
192 const nextRemote = {
193 fingerprint: fingerprintStatusPart(remote),
194 value: remote,
195 } satisfies CachedValue<VcsStatusRemoteResult | null>;
196 const shouldPublish = yield* Ref.modify(cacheRef, (cache) => {
197 const previous = cache.get(cwd) ?? { local: null, remote: null };
198 const nextCache = new Map(cache);
199 nextCache.set(cwd, {
200 local: nextLocal,
201 remote: nextRemote,
202 });
203 return [
204 previous.local?.fingerprint !== nextLocal.fingerprint ||
205 previous.remote?.fingerprint !== nextRemote.fingerprint,
206 nextCache,
207 ] as const;
208 });
209
210 if (options?.publish && shouldPublish) {
211 yield* PubSub.publish(changesPubSub, {
212 cwd,
213 event: {
214 _tag: "snapshot",
215 local,
216 remote,
217 },
218 });
219 }
220
221 return mergeGitStatusParts(local, remote);
187 });222 });
188223
189 const loadRemoteStatus = Effect.fn("VcsStatusBroadcaster.loadRemoteStatus")(function* (224 const loadLocalStatus = Effect.fn("VcsStatusBroadcaster.loadLocalStatus")(function* (
190 cwd: string,225 cwd: string,
191 ) {226 ) {
192 const remote = yield* workflow.remoteStatus({ cwd });227 const local = yield* workflow.localStatus({ cwd });
193 return yield* updateCachedRemoteStatus(cwd, remote);228 return yield* updateCachedLocalStatus(cwd, local);
194 });229 });
195230
196 const getOrLoadLocalStatus = Effect.fn("VcsStatusBroadcaster.getOrLoadLocalStatus")(function* (231 const getOrLoadLocalStatus = Effect.fn("VcsStatusBroadcaster.getOrLoadLocalStatus")(function* (
...@@ -203,36 +238,39 @@ export const layer = Layer.effect(...@@ -203,36 +238,39 @@ export const layer = Layer.effect(
203 return yield* loadLocalStatus(cwd);238 return yield* loadLocalStatus(cwd);
204 });239 });
205240
206 const getOrLoadRemoteStatus = Effect.fn("VcsStatusBroadcaster.getOrLoadRemoteStatus")(
207 function* (cwd: string) {
208 const cached = yield* getCachedStatus(cwd);
209 if (cached?.remote) {
210 return cached.remote.value;
211 }
212 return yield* loadRemoteStatus(cwd);
213 },
214 );
215
216 const withFileSystem = Effect.provideService(FileSystem.FileSystem, fs);241 const withFileSystem = Effect.provideService(FileSystem.FileSystem, fs);
217242
218 const getStatus: VcsStatusBroadcasterShape["getStatus"] = Effect.fn(243 const getStatus: VcsStatusBroadcasterShape["getStatus"] = Effect.fn(
219 "VcsStatusBroadcaster.getStatus",244 "VcsStatusBroadcaster.getStatus",
220 )(function* (input) {245 )(function* (input) {
221 const cwd = yield* withFileSystem(normalizeCwd(input.cwd));246 const cwd = yield* withFileSystem(normalizeCwd(input.cwd));
222 const [local, remote] = yield* Effect.all([247 const cached = yield* getCachedStatus(cwd);
223 getOrLoadLocalStatus(cwd),248 if (cached?.local && cached.remote) {
224 getOrLoadRemoteStatus(cwd),249 return mergeGitStatusParts(cached.local.value, cached.remote.value);
225 ]);250 }
226 return mergeGitStatusParts(local, remote);251 const [local, remote] = yield* Effect.all(
252 [
253 cached?.local ? Effect.succeed(cached.local.value) : workflow.localStatus({ cwd }),
254 cached?.remote ? Effect.succeed(cached.remote.value) : workflow.remoteStatus({ cwd }),
255 ],
256 { concurrency: "unbounded" },
257 );
258 return yield* updateCachedStatus(cwd, local, remote);
227 });259 });
228260
261 const refreshLocalStatusCore = Effect.fn("VcsStatusBroadcaster.refreshLocalStatusCore")(
262 function* (cwd: string) {
263 yield* workflow.invalidateLocalStatus(cwd);
264 const local = yield* workflow.localStatus({ cwd });
265 return yield* updateCachedLocalStatus(cwd, local, { publish: true });
266 },
267 );
268
229 const refreshLocalStatus: VcsStatusBroadcasterShape["refreshLocalStatus"] = Effect.fn(269 const refreshLocalStatus: VcsStatusBroadcasterShape["refreshLocalStatus"] = Effect.fn(
230 "VcsStatusBroadcaster.refreshLocalStatus",270 "VcsStatusBroadcaster.refreshLocalStatus",
231 )(function* (rawCwd) {271 )(function* (rawCwd) {
232 const cwd = yield* withFileSystem(normalizeCwd(rawCwd));272 const cwd = yield* withFileSystem(normalizeCwd(rawCwd));
233 yield* workflow.invalidateLocalStatus(cwd);273 return yield* refreshLocalStatusCore(cwd);
234 const local = yield* workflow.localStatus({ cwd });
235 return yield* updateCachedLocalStatus(cwd, local, { publish: true });
236 });274 });
237275
238 const refreshRemoteStatus = Effect.fn("VcsStatusBroadcaster.refreshRemoteStatus")(function* (276 const refreshRemoteStatus = Effect.fn("VcsStatusBroadcaster.refreshRemoteStatus")(function* (
...@@ -247,11 +285,15 @@ export const layer = Layer.effect(...@@ -247,11 +285,15 @@ export const layer = Layer.effect(
247 "VcsStatusBroadcaster.refreshStatus",285 "VcsStatusBroadcaster.refreshStatus",
248 )(function* (rawCwd) {286 )(function* (rawCwd) {
249 const cwd = yield* withFileSystem(normalizeCwd(rawCwd));287 const cwd = yield* withFileSystem(normalizeCwd(rawCwd));
250 const [local, remote] = yield* Effect.all([288 yield* Effect.all(
251 refreshLocalStatus(cwd),289 [workflow.invalidateLocalStatus(cwd), workflow.invalidateRemoteStatus(cwd)],
252 refreshRemoteStatus(cwd),290 { concurrency: "unbounded", discard: true },
253 ]);291 );
254 return mergeGitStatusParts(local, remote);292 const [local, remote] = yield* Effect.all(
293 [workflow.localStatus({ cwd }), workflow.remoteStatus({ cwd })],
294 { concurrency: "unbounded" },
295 );
296 return yield* updateCachedStatus(cwd, local, remote, { publish: true });
255 });297 });
256298
257 const makeRemoteRefreshLoop = (299 const makeRemoteRefreshLoop = (