From 5bf18042113eb31413af41895d8e5a2e7ab153ef Mon Sep 17 00:00:00 2001 From: Saatvik Arya Date: Wed, 24 Jun 2026 18:45:59 +0530 Subject: [PATCH 1/2] fix(sdk): refresh cache writes in LRU order --- packages/core/sdk/src/executor-cache.test.ts | 92 ++++++++++++++++++++ packages/core/sdk/src/executor.ts | 1 + 2 files changed, 93 insertions(+) create mode 100644 packages/core/sdk/src/executor-cache.test.ts diff --git a/packages/core/sdk/src/executor-cache.test.ts b/packages/core/sdk/src/executor-cache.test.ts new file mode 100644 index 000000000..1acaf8318 --- /dev/null +++ b/packages/core/sdk/src/executor-cache.test.ts @@ -0,0 +1,92 @@ +import { describe, expect, it } from "@effect/vitest"; +import { Effect } from "effect"; + +import { createExecutor, type Executor } from "./executor"; +import { Tenant } from "./ids"; + +const MEMORY_CACHE_CAPACITY = 2_048; +const MEMORY_CACHE_TTL_MS = 10 * 60 * 1000; + +const makeExecutor = Effect.acquireRelease( + createExecutor({ + tenant: Tenant.make("test-tenant"), + onElicitation: "accept-all", + }), + (executor) => executor.close().pipe(Effect.ignore), +); + +const withFakeNow = ( + initialNow: number, + run: (clock: { readonly advance: (ms: number) => void }) => Effect.Effect, +): Effect.Effect => + Effect.acquireUseRelease( + Effect.sync(() => { + const originalNow = Date.now; + let now = initialNow; + Date.now = () => now; + return { + advance: (ms: number) => { + now += ms; + }, + restore: () => { + Date.now = originalNow; + }, + }; + }), + (clock) => run({ advance: clock.advance }), + (clock) => Effect.sync(clock.restore), + ); + +describe("executor cache", () => { + it.effect("uses an in-memory fallback when no cache is configured", () => + Effect.scoped( + Effect.gen(function* () { + const executor = yield* makeExecutor; + + yield* executor.cache.set("a", "value"); + expect(yield* executor.cache.get("a")).toBe("value"); + + yield* executor.cache.remove("a"); + expect(yield* executor.cache.get("a")).toBeUndefined(); + }), + ), + ); + + it.effect("expires fallback entries by TTL on get and size", () => + withFakeNow(1_000, ({ advance }) => + Effect.scoped( + Effect.gen(function* () { + const executor = yield* makeExecutor; + + yield* executor.cache.set("a", "value"); + expect(yield* executor.cache.size).toBe(1); + + advance(MEMORY_CACHE_TTL_MS); + + expect(yield* executor.cache.get("a")).toBeUndefined(); + expect(yield* executor.cache.size).toBe(0); + }), + ), + ), + ); + + it.effect("refreshes fallback LRU position when an existing key is written", () => + Effect.scoped( + Effect.gen(function* () { + const executor: Executor = yield* makeExecutor; + + yield* executor.cache.set("a", "old"); + for (let index = 0; index < MEMORY_CACHE_CAPACITY - 1; index += 1) { + yield* executor.cache.set(`key-${index}`, String(index)); + } + + yield* executor.cache.set("a", "new"); + yield* executor.cache.set("overflow", "value"); + + expect(yield* executor.cache.get("a")).toBe("new"); + expect(yield* executor.cache.get("key-0")).toBeUndefined(); + expect(yield* executor.cache.get("key-1")).toBe("1"); + }), + ), + ); +}); diff --git a/packages/core/sdk/src/executor.ts b/packages/core/sdk/src/executor.ts index 3ab3757dc..d60cb70e6 100644 --- a/packages/core/sdk/src/executor.ts +++ b/packages/core/sdk/src/executor.ts @@ -511,6 +511,7 @@ const makeMemoryCacheStore = (): KeyValueStore.KeyValueStore => { Effect.sync(() => { const now = Date.now(); evictExpired(now); + rows.delete(key); rows.set(key, { value, expiresAt: now + MEMORY_CACHE_TTL_MS }); evictCapacity(); }), From 5c0474743d49ee6dd9c219f3032ab8ef1441723d Mon Sep 17 00:00:00 2001 From: Saatvik Arya Date: Fri, 26 Jun 2026 18:01:46 +0530 Subject: [PATCH 2/2] test(sdk): document cache defaults in tests (greptile) --- packages/core/sdk/src/executor-cache.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/sdk/src/executor-cache.test.ts b/packages/core/sdk/src/executor-cache.test.ts index 1acaf8318..839e9c5cf 100644 --- a/packages/core/sdk/src/executor-cache.test.ts +++ b/packages/core/sdk/src/executor-cache.test.ts @@ -4,6 +4,7 @@ import { Effect } from "effect"; import { createExecutor, type Executor } from "./executor"; import { Tenant } from "./ids"; +// Keep in sync with the unexported fallback cache defaults in executor.ts. const MEMORY_CACHE_CAPACITY = 2_048; const MEMORY_CACHE_TTL_MS = 10 * 60 * 1000;