mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:40:44 +00:00
fix: stabilize qa lab capture store cleanup
This commit is contained in:
@@ -113,6 +113,10 @@ const captureMock = vi.hoisted(() => {
|
||||
});
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/proxy-capture", () => ({
|
||||
acquireDebugProxyCaptureStore: () => ({
|
||||
store: captureMock.store,
|
||||
release: captureMock.store.close,
|
||||
}),
|
||||
getDebugProxyCaptureStore: () => captureMock.store,
|
||||
resolveDebugProxySettings: () => ({
|
||||
dbPath: process.env.OPENCLAW_DEBUG_PROXY_DB_PATH ?? "",
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createServer, type IncomingMessage } from "node:http";
|
||||
import path from "node:path";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import {
|
||||
getDebugProxyCaptureStore,
|
||||
acquireDebugProxyCaptureStore,
|
||||
resolveDebugProxySettings,
|
||||
} from "openclaw/plugin-sdk/proxy-capture";
|
||||
import { closeQaHttpServer, handleQaBusRequest, writeError, writeJson } from "./bus-server.js";
|
||||
@@ -168,7 +168,11 @@ export async function startQaLabServer(
|
||||
): Promise<QaLabServerHandle> {
|
||||
const repoRoot = path.resolve(params?.repoRoot ?? process.cwd());
|
||||
const captureSettings = resolveDebugProxySettings();
|
||||
const captureStore = getDebugProxyCaptureStore(captureSettings.dbPath, captureSettings.blobDir);
|
||||
const captureStoreLease = acquireDebugProxyCaptureStore(
|
||||
captureSettings.dbPath,
|
||||
captureSettings.blobDir,
|
||||
);
|
||||
const captureStore = captureStoreLease.store;
|
||||
const state = createQaBusState();
|
||||
let latestReport: QaLabLatestReport | null = null;
|
||||
let latestScenarioRun: QaLabScenarioRun | null = null;
|
||||
@@ -639,7 +643,7 @@ export async function startQaLabServer(
|
||||
await runnerModelCatalogPromise?.catch(() => undefined);
|
||||
await gateway?.stop();
|
||||
await closeQaHttpServer(server);
|
||||
captureStore.close();
|
||||
captureStoreLease.release();
|
||||
},
|
||||
};
|
||||
labHandle = lab;
|
||||
|
||||
@@ -4,7 +4,9 @@ export {
|
||||
resolveEffectiveDebugProxyUrl,
|
||||
} from "../proxy-capture/env.js";
|
||||
export {
|
||||
acquireDebugProxyCaptureStore,
|
||||
DebugProxyCaptureStore,
|
||||
closeDebugProxyCaptureStore,
|
||||
getDebugProxyCaptureStore,
|
||||
} from "../proxy-capture/store.sqlite.js";
|
||||
export {
|
||||
|
||||
@@ -2,11 +2,18 @@ import { mkdtempSync, rmSync } from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { DebugProxyCaptureStore, persistEventPayload } from "./store.sqlite.js";
|
||||
import {
|
||||
acquireDebugProxyCaptureStore,
|
||||
closeDebugProxyCaptureStore,
|
||||
DebugProxyCaptureStore,
|
||||
getDebugProxyCaptureStore,
|
||||
persistEventPayload,
|
||||
} from "./store.sqlite.js";
|
||||
|
||||
const cleanupDirs: string[] = [];
|
||||
|
||||
afterEach(() => {
|
||||
closeDebugProxyCaptureStore();
|
||||
while (cleanupDirs.length > 0) {
|
||||
const dir = cleanupDirs.pop();
|
||||
if (dir) {
|
||||
@@ -22,6 +29,35 @@ function makeStore() {
|
||||
}
|
||||
|
||||
describe("DebugProxyCaptureStore", () => {
|
||||
it("keeps the cached store open until the last lease releases", () => {
|
||||
const root = mkdtempSync(path.join(os.tmpdir(), "openclaw-proxy-capture-lease-"));
|
||||
cleanupDirs.push(root);
|
||||
const dbPath = path.join(root, "capture.sqlite");
|
||||
const blobDir = path.join(root, "blobs");
|
||||
|
||||
const first = acquireDebugProxyCaptureStore(dbPath, blobDir);
|
||||
const second = acquireDebugProxyCaptureStore(dbPath, blobDir);
|
||||
|
||||
expect(second.store).toBe(first.store);
|
||||
first.release();
|
||||
expect(first.store.isClosed).toBe(false);
|
||||
|
||||
second.release();
|
||||
expect(first.store.isClosed).toBe(true);
|
||||
|
||||
const reopened = getDebugProxyCaptureStore(dbPath, blobDir);
|
||||
expect(Object.is(reopened, first.store)).toBe(false);
|
||||
expect(reopened.isClosed).toBe(false);
|
||||
});
|
||||
|
||||
it("ignores duplicate close calls", () => {
|
||||
const store = makeStore();
|
||||
|
||||
store.close();
|
||||
expect(() => store.close()).not.toThrow();
|
||||
expect(store.isClosed).toBe(true);
|
||||
});
|
||||
|
||||
it("stores sessions, blobs, and duplicate-send query results", () => {
|
||||
const store = makeStore();
|
||||
store.upsertSession({
|
||||
|
||||
@@ -93,6 +93,7 @@ function sortObservedCounts(counts: Map<string, number>): CaptureObservedDimensi
|
||||
|
||||
export class DebugProxyCaptureStore {
|
||||
readonly db: DatabaseSync;
|
||||
private closed = false;
|
||||
|
||||
constructor(
|
||||
readonly dbPath: string,
|
||||
@@ -102,7 +103,15 @@ export class DebugProxyCaptureStore {
|
||||
}
|
||||
|
||||
close(): void {
|
||||
if (this.closed) {
|
||||
return;
|
||||
}
|
||||
this.db.close();
|
||||
this.closed = true;
|
||||
}
|
||||
|
||||
get isClosed(): boolean {
|
||||
return this.closed;
|
||||
}
|
||||
|
||||
upsertSession(session: CaptureSessionRecord): void {
|
||||
@@ -448,12 +457,14 @@ export class DebugProxyCaptureStore {
|
||||
|
||||
let cachedStore: DebugProxyCaptureStore | null = null;
|
||||
let cachedKey = "";
|
||||
let cachedStoreLeases = 0;
|
||||
|
||||
export function getDebugProxyCaptureStore(dbPath: string, blobDir: string): DebugProxyCaptureStore {
|
||||
const key = `${dbPath}:${blobDir}`;
|
||||
if (!cachedStore || cachedKey !== key) {
|
||||
if (!cachedStore || cachedStore.isClosed || cachedKey !== key) {
|
||||
cachedStore = new DebugProxyCaptureStore(dbPath, blobDir);
|
||||
cachedKey = key;
|
||||
cachedStoreLeases = 0;
|
||||
}
|
||||
return cachedStore;
|
||||
}
|
||||
@@ -465,6 +476,30 @@ export function closeDebugProxyCaptureStore(): void {
|
||||
cachedStore.close();
|
||||
cachedStore = null;
|
||||
cachedKey = "";
|
||||
cachedStoreLeases = 0;
|
||||
}
|
||||
|
||||
export function acquireDebugProxyCaptureStore(
|
||||
dbPath: string,
|
||||
blobDir: string,
|
||||
): { store: DebugProxyCaptureStore; release: () => void } {
|
||||
const store = getDebugProxyCaptureStore(dbPath, blobDir);
|
||||
const key = cachedKey;
|
||||
cachedStoreLeases += 1;
|
||||
let released = false;
|
||||
return {
|
||||
store,
|
||||
release: () => {
|
||||
if (released) {
|
||||
return;
|
||||
}
|
||||
released = true;
|
||||
cachedStoreLeases = Math.max(0, cachedStoreLeases - 1);
|
||||
if (cachedStoreLeases === 0 && cachedStore === store && cachedKey === key) {
|
||||
closeDebugProxyCaptureStore();
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function persistEventPayload(
|
||||
|
||||
Reference in New Issue
Block a user