Skip to content
Closed
Show file tree
Hide file tree
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
57 changes: 57 additions & 0 deletions apps/server/src/pullRequest/PullRequestService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,63 @@ it.effect("does not wait for an in-flight detail read to display a preview", ()
}),
);

it.effect("starts a fresh detail lookup while an interrupted read is cleaning up", () =>
Effect.gen(function* () {
const detailStarted = yield* Deferred.make<void>();
const cleanupStarted = yield* Deferred.make<void>();
const releaseCleanup = yield* Deferred.make<void>();
let calls = 0;
const service = yield* makeService({
projects: [project({ id: "p1", title: "web", workspaceRoot: "/w", repository: "acme/web" })],
providers: [
fakeProvider("github", {
getChangeRequest: () => {
const call = ++calls;
return Effect.gen(function* () {
if (call === 1) {
yield* Deferred.succeed(detailStarted, undefined);
return yield* Effect.never;
}
return hostedChangeRequest("fresh detail");
}).pipe(
Effect.ensuring(
call === 1
? Deferred.succeed(cleanupStarted, undefined).pipe(
Effect.andThen(Deferred.await(releaseCleanup)),
)
: Effect.void,
),
);
},
}),
],
});
const reference = { projectId: "p1" as ProjectId, repository: "acme/web", number: 1 };

const first = yield* service
.detail(reference)
.pipe(Effect.forkChild({ startImmediately: true }));
yield* Deferred.await(detailStarted);
const firstInterrupt = yield* Fiber.interrupt(first).pipe(
Effect.forkChild({ startImmediately: true }),
);
yield* Deferred.await(cleanupStarted);

const second = yield* service
.detail(reference)
.pipe(Effect.forkChild({ startImmediately: true }));
yield* Deferred.succeed(releaseCleanup, undefined);
yield* Fiber.join(firstInterrupt);

assert.strictEqual((yield* Fiber.join(second)).body, "fresh detail");
assert.strictEqual(
(yield* service.detail({ ...reference, allowStale: false })).body,
"fresh detail",
);
assert.strictEqual(calls, 2);
}),
);

it.effect("keeps previews warm when another project finishes a turn", () =>
Effect.gen(function* () {
const reads: string[] = [];
Expand Down
26 changes: 26 additions & 0 deletions patches/effect@4.0.0-rc.115.patch
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
diff --git a/dist/Cache.js b/dist/Cache.js
index 315795c3f9f23cc407b270f7def4fef792257a0c..541ae1896a58c88b147d4f364529f1f36e2d98cf 100644
--- a/dist/Cache.js
+++ b/dist/Cache.js
@@ -300,6 +300,8 @@ class EntryImpl {
return effect.onExit(effect.fiberJoin(this.fiber), () => {
this.awaiters--;
if (this.awaiters > 0 || this.fiber.pollUnsafe()) return effect.void;
+ // A new reader must not join this lookup while interruption finalizers run.
+ this.expiresAt = 0;
return effect.fiberInterrupt(this.fiber);
});
}
diff --git a/dist/unstable/ai/McpServer.js b/dist/unstable/ai/McpServer.js
index e8b9be4..9cffc5c 100644
--- a/dist/unstable/ai/McpServer.js
Expand Down Expand Up @@ -350,3 +363,16 @@ index cbf047f..c7d5cc2 100644
}

get remoteAddress(): Option.Option<string> {
diff --git a/src/Cache.ts b/src/Cache.ts
index de91e02322e21f59c0b49cfebed68a4a536ff9e4..d24cd5cc79ff0c805665ace97d462b2138a4cf48 100644
--- a/src/Cache.ts
+++ b/src/Cache.ts
@@ -669,6 +669,8 @@ class EntryImpl<A, E> implements Entry<A, E> {
return effect.onExit(effect.fiberJoin(this.fiber), () => {
this.awaiters--
if (this.awaiters > 0 || this.fiber.pollUnsafe()) return effect.void
+ // A new reader must not join this lookup while interruption finalizers run.
+ this.expiresAt = 0
return effect.fiberInterrupt(this.fiber)
})
}
Loading
Loading