diff --git a/apps/server/src/orchestration-v2/RunFinalizationQueue.test.ts b/apps/server/src/orchestration-v2/RunFinalizationQueue.test.ts index c2b9e0aabcd5b..3f21b20917850 100644 --- a/apps/server/src/orchestration-v2/RunFinalizationQueue.test.ts +++ b/apps/server/src/orchestration-v2/RunFinalizationQueue.test.ts @@ -8,7 +8,6 @@ import * as SqlitePersistence from "../persistence/Sqlite.ts"; import * as PullRequestService from "../pullRequest/PullRequestService.ts"; import * as ServerSettings from "../serverSettings.ts"; import * as VcsStatusBroadcaster from "../vcs/VcsStatusBroadcaster.ts"; -import * as WorkspaceEntries from "../workspace/WorkspaceEntries.ts"; import * as CheckpointCapture from "./CheckpointCaptureService.ts"; import * as CheckpointRollback from "./CheckpointRollbackService.ts"; import * as EffectOutbox from "./EffectOutbox.ts"; @@ -48,7 +47,6 @@ it.effect.each([ Layer.provide( Layer.mergeAll( projections, - Layer.mock(WorkspaceEntries.WorkspaceEntries)({ refresh: () => Effect.void }), Layer.mock(PullRequestService.PullRequestService)({ refreshAfterTurn: () => Effect.void }), Layer.mock(VcsStatusBroadcaster.VcsStatusBroadcaster)({ refreshLocalStatus: () => diff --git a/apps/server/src/orchestration-v2/RunFinalizationService.test.ts b/apps/server/src/orchestration-v2/RunFinalizationService.test.ts index 38a18192531a5..4ac0fcabd7b90 100644 --- a/apps/server/src/orchestration-v2/RunFinalizationService.test.ts +++ b/apps/server/src/orchestration-v2/RunFinalizationService.test.ts @@ -10,7 +10,6 @@ import * as Layer from "effect/Layer"; import * as PullRequestService from "../pullRequest/PullRequestService.ts"; import * as VcsStatusBroadcaster from "../vcs/VcsStatusBroadcaster.ts"; -import * as WorkspaceEntries from "../workspace/WorkspaceEntries.ts"; import * as CheckpointCapture from "./CheckpointCaptureService.ts"; import * as ProjectionStore from "./ProjectionStore.ts"; import * as RunFinalization from "./RunFinalizationService.ts"; @@ -98,7 +97,6 @@ it.effect.each( const layer = RunFinalization.layerObserver.pipe( Layer.provide( Layer.mergeAll( - Layer.mock(WorkspaceEntries.WorkspaceEntries)({ refresh: () => Effect.void }), Layer.mock(PullRequestService.PullRequestService)({ refreshAfterTurn: () => Effect.void, }), diff --git a/apps/server/src/orchestration-v2/RunFinalizationService.ts b/apps/server/src/orchestration-v2/RunFinalizationService.ts index 40882033ba527..b6f0e1852eb13 100644 --- a/apps/server/src/orchestration-v2/RunFinalizationService.ts +++ b/apps/server/src/orchestration-v2/RunFinalizationService.ts @@ -6,7 +6,6 @@ import * as Schema from "effect/Schema"; import * as PullRequestService from "../pullRequest/PullRequestService.ts"; import * as VcsStatusBroadcaster from "../vcs/VcsStatusBroadcaster.ts"; -import * as WorkspaceEntries from "../workspace/WorkspaceEntries.ts"; import * as CheckpointCapture from "./CheckpointCaptureService.ts"; import * as ProjectionStore from "./ProjectionStore.ts"; @@ -92,7 +91,6 @@ export const layer = Layer.effect(RunFinalizationService, make); export const layerObserver = Layer.effect( RunFinalizationObserver, Effect.gen(function* () { - const workspaceEntries = yield* WorkspaceEntries.WorkspaceEntries; const vcsStatus = yield* VcsStatusBroadcaster.VcsStatusBroadcaster; const projections = yield* ProjectionStore.ProjectionStoreV2; const pullRequests = yield* PullRequestService.PullRequestService; @@ -100,10 +98,9 @@ export const layerObserver = Layer.effect( refreshAfterTurn: pullRequests.refreshAfterTurn, refresh: Effect.fn("RunFinalizationService.refresh")( function* ({ cwd, threadId, runId }) { - const [, local] = yield* Effect.all( - [workspaceEntries.refresh(cwd), vcsStatus.refreshLocalStatus(cwd)], - { concurrency: "unbounded" }, - ); + // The search indexes already watch file changes. A forced `scanFiles()` here waits on + // fff's picker lock on the event loop, freezing the server while the watcher holds it. + const local = yield* vcsStatus.refreshLocalStatus(cwd); if (local.refName === null || local.isDefaultRef) return; const thread = yield* projections.getThreadShell(threadId); if (!thread || thread.branch !== local.refName) return; diff --git a/apps/server/src/workspace/WorkspaceSearchIndex.test.ts b/apps/server/src/workspace/WorkspaceSearchIndex.test.ts index 2c41e4ed0b3d0..460562022241e 100644 --- a/apps/server/src/workspace/WorkspaceSearchIndex.test.ts +++ b/apps/server/src/workspace/WorkspaceSearchIndex.test.ts @@ -7,6 +7,7 @@ import { } from "@ff-labs/fff-node"; import { afterEach, expect, it } from "@effect/vitest"; import * as Effect from "effect/Effect"; +import * as TestClock from "effect/testing/TestClock"; import { vi } from "vite-plus/test"; import * as WorkspaceSearchIndex from "./WorkspaceSearchIndex.ts"; @@ -28,6 +29,44 @@ function fileItem(relativePath: string): FileItem { }; } +it.effect("reads never rescan after the former recovery interval", () => + Effect.scoped( + Effect.gen(function* () { + const scanFiles = vi.fn(() => ({ ok: true as const, value: undefined })); + const finder = { + destroy: vi.fn(), + waitForIndexReady: vi.fn(async () => ({ ok: true as const, value: true })), + scanFiles, + mixedSearch: () => ({ ok: true as const, value: { items: [], totalMatched: 0 } }), + fileSearch: () => ({ + ok: true as const, + value: { items: [fileItem("watched.ts")], scores: [], totalMatched: 1, totalFiles: 1 }, + }), + grep: () => ({ ok: true as const, value: { items: [], nextCursor: null } }), + } as unknown as FileFinder; + vi.spyOn(FileFinder, "create").mockReturnValueOnce({ ok: true, value: finder }); + const index = yield* WorkspaceSearchIndex.make("/workspace/project"); + yield* TestClock.adjust("10 minutes"); + expect(yield* index.list()).toEqual({ entries: [], truncated: false }); + expect((yield* index.search("", 10, "file")).entries).toEqual([ + { path: "watched.ts", kind: "file" }, + ]); + expect( + yield* index.searchContents({ + query: "needle", + limit: 1, + caseSensitive: true, + wholeWord: false, + useRegex: false, + }), + ).toEqual({ matches: [], truncated: false }); + expect(scanFiles).not.toHaveBeenCalled(); + yield* index.refresh(); + expect(scanFiles).toHaveBeenCalledTimes(1); + }), + ), +); + it.effect("filters image searches before applying the result limit", () => Effect.scoped( Effect.gen(function* () {