mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-27 23:40:20 +00:00
fix: preserve plugin state limits
This commit is contained in:
@@ -159,6 +159,19 @@ describe("DiffArtifactStore", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps standalone artifact dirs when cleanup overlaps metadata registration", async () => {
|
||||
const register = blobStore.register.bind(blobStore);
|
||||
vi.spyOn(blobStore, "register").mockImplementationOnce(async (key, metadata, blob, opts) => {
|
||||
await store.cleanupExpired();
|
||||
await register(key, metadata, blob, opts);
|
||||
});
|
||||
|
||||
const standalone = await store.createStandaloneFileArtifact();
|
||||
|
||||
const directory = await fs.stat(path.dirname(standalone.filePath));
|
||||
expect(directory.isDirectory()).toBe(true);
|
||||
});
|
||||
|
||||
it("expires standalone file artifacts using ttl metadata", async () => {
|
||||
vi.useFakeTimers();
|
||||
const now = new Date("2026-02-27T16:00:00Z");
|
||||
|
||||
@@ -168,8 +168,8 @@ export class DiffArtifactStore {
|
||||
...(params.context ? { context: params.context } : {}),
|
||||
};
|
||||
|
||||
await (await this.artifactRoot()).mkdir(id);
|
||||
await this.writeStandaloneMeta(meta);
|
||||
await (await this.artifactRoot()).mkdir(id);
|
||||
this.scheduleCleanup();
|
||||
return {
|
||||
id,
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
} from "./plugin-state-store.types.js";
|
||||
|
||||
export const MAX_PLUGIN_STATE_VALUE_BYTES = 65_536;
|
||||
export const MAX_PLUGIN_STATE_ENTRIES_PER_PLUGIN = 1_000;
|
||||
|
||||
type PluginStateEntriesTable = OpenClawStateKyselyDatabase["plugin_state_entries"];
|
||||
type PluginStateStoreDatabase = Pick<OpenClawStateKyselyDatabase, "plugin_state_entries">;
|
||||
@@ -247,6 +248,21 @@ function countLivePluginStateNamespaceEntries(
|
||||
return countRow(row);
|
||||
}
|
||||
|
||||
function countLivePluginStateEntries(
|
||||
db: DatabaseSync,
|
||||
params: { pluginId: string; now: number },
|
||||
): number {
|
||||
const row = executeSqliteQueryTakeFirstSync(
|
||||
db,
|
||||
getPluginStateKysely(db)
|
||||
.selectFrom("plugin_state_entries")
|
||||
.select((eb) => eb.fn.countAll<number | bigint>().as("count"))
|
||||
.where("plugin_id", "=", params.pluginId)
|
||||
.where((eb) => eb.or([eb("expires_at", "is", null), eb("expires_at", ">", params.now)])),
|
||||
);
|
||||
return countRow(row);
|
||||
}
|
||||
|
||||
function deleteOldestPluginStateNamespaceEntries(
|
||||
db: DatabaseSync,
|
||||
params: { pluginId: string; namespace: string; protectedKey: string; now: number; limit: number },
|
||||
@@ -358,6 +374,19 @@ function enforcePostRegisterLimits(params: {
|
||||
limit: namespaceCount - params.maxEntries,
|
||||
});
|
||||
}
|
||||
|
||||
const pluginCount = countLivePluginStateEntries(params.store.db, {
|
||||
pluginId: params.pluginId,
|
||||
now: params.now,
|
||||
});
|
||||
if (pluginCount > MAX_PLUGIN_STATE_ENTRIES_PER_PLUGIN) {
|
||||
throw createPluginStateError({
|
||||
code: "PLUGIN_STATE_LIMIT_EXCEEDED",
|
||||
operation: "register",
|
||||
message: `Plugin state for ${params.pluginId} exceeds the ${MAX_PLUGIN_STATE_ENTRIES_PER_PLUGIN} live row limit.`,
|
||||
path: params.store.path,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function pluginStateRegister(params: {
|
||||
|
||||
@@ -228,7 +228,7 @@ describe("plugin state keyed store", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("registerIfAbsent preserves namespace eviction without capping sibling namespaces", async () => {
|
||||
it("registerIfAbsent preserves sibling namespaces when plugin-wide limit rejects overflow", async () => {
|
||||
await withPluginStateTestState(async () => {
|
||||
vi.useFakeTimers();
|
||||
const evicting = createPluginStateKeyedStore<number>("discord", {
|
||||
@@ -265,8 +265,11 @@ describe("plugin state keyed store", () => {
|
||||
namespace: "sibling",
|
||||
maxEntries: 10,
|
||||
});
|
||||
await expect(limited.registerIfAbsent("overflow", { overflow: true })).resolves.toBe(true);
|
||||
await expect(limited.lookup("overflow")).resolves.toEqual({ overflow: true });
|
||||
await expectPluginStateStoreError(limited.registerIfAbsent("overflow", { overflow: true }), {
|
||||
code: "PLUGIN_STATE_LIMIT_EXCEEDED",
|
||||
operation: "register",
|
||||
});
|
||||
await expect(limited.lookup("overflow")).resolves.toBeUndefined();
|
||||
await expect(sibling.lookup("k-0")).resolves.toEqual({ sibling: true });
|
||||
});
|
||||
});
|
||||
@@ -372,7 +375,7 @@ describe("plugin state keyed store", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("applies entry limits per namespace without evicting siblings", async () => {
|
||||
it("rejects plugin-wide overflow without evicting sibling namespaces", async () => {
|
||||
await withPluginStateTestState(async () => {
|
||||
seedPluginStateEntriesForTests([
|
||||
...Array.from({ length: 5_989 }, (_, entryIndex) => ({
|
||||
@@ -398,12 +401,15 @@ describe("plugin state keyed store", () => {
|
||||
maxEntries: 100,
|
||||
});
|
||||
|
||||
await expect(limitStore.register("overflow", { overflow: true })).resolves.toBeUndefined();
|
||||
await expectPluginStateStoreError(limitStore.register("overflow", { overflow: true }), {
|
||||
code: "PLUGIN_STATE_LIMIT_EXCEEDED",
|
||||
operation: "register",
|
||||
});
|
||||
await expect(siblingStore.lookup("k-0")).resolves.toEqual({
|
||||
namespaceIndex: 1,
|
||||
entryIndex: 0,
|
||||
});
|
||||
await expect(limitStore.lookup("overflow")).resolves.toEqual({ overflow: true });
|
||||
await expect(limitStore.lookup("overflow")).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user