fix: add retry-once logic to isSqliteBackend for startup race condition
This commit is contained in:
parent
49ed32308b
commit
ed84b431fc
@ -15,15 +15,27 @@ const SQLITE_VERSION = "1.1.53"
|
|||||||
// Other files (e.g., opencode-message-dir.test.ts) mock ./opencode-storage-detection globally,
|
// Other files (e.g., opencode-message-dir.test.ts) mock ./opencode-storage-detection globally,
|
||||||
// making dynamic import unreliable. By inlining, we test the actual logic with controlled deps.
|
// making dynamic import unreliable. By inlining, we test the actual logic with controlled deps.
|
||||||
const NOT_CACHED = Symbol("NOT_CACHED")
|
const NOT_CACHED = Symbol("NOT_CACHED")
|
||||||
let cachedResult: boolean | typeof NOT_CACHED = NOT_CACHED
|
const FALSE_PENDING_RETRY = Symbol("FALSE_PENDING_RETRY")
|
||||||
|
let cachedResult: true | false | typeof NOT_CACHED | typeof FALSE_PENDING_RETRY = NOT_CACHED
|
||||||
|
|
||||||
function isSqliteBackend(): boolean {
|
function isSqliteBackend(): boolean {
|
||||||
if (cachedResult !== NOT_CACHED) return cachedResult as boolean
|
if (cachedResult === true) return true
|
||||||
|
if (cachedResult === false) return false
|
||||||
|
if (cachedResult === FALSE_PENDING_RETRY) {
|
||||||
const versionOk = (() => { versionCheckCalls.push(SQLITE_VERSION); return versionReturnValue })()
|
const versionOk = (() => { versionCheckCalls.push(SQLITE_VERSION); return versionReturnValue })()
|
||||||
const dbPath = join(TEST_DATA_DIR, "opencode", "opencode.db")
|
const dbPath = join(TEST_DATA_DIR, "opencode", "opencode.db")
|
||||||
const dbExists = existsSync(dbPath)
|
const dbExists = existsSync(dbPath)
|
||||||
cachedResult = versionOk && dbExists
|
const result = versionOk && dbExists
|
||||||
return cachedResult
|
cachedResult = result
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
const versionOk = (() => { versionCheckCalls.push(SQLITE_VERSION); return versionReturnValue })()
|
||||||
|
const dbPath = join(TEST_DATA_DIR, "opencode", "opencode.db")
|
||||||
|
const dbExists = existsSync(dbPath)
|
||||||
|
const result = versionOk && dbExists
|
||||||
|
if (result) { cachedResult = true }
|
||||||
|
else { cachedResult = FALSE_PENDING_RETRY }
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetSqliteBackendCache(): void {
|
function resetSqliteBackendCache(): void {
|
||||||
@ -77,7 +89,7 @@ describe("isSqliteBackend", () => {
|
|||||||
expect(versionCheckCalls).toContain("1.1.53")
|
expect(versionCheckCalls).toContain("1.1.53")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("caches the result and does not re-check on subsequent calls", () => {
|
it("caches true permanently and does not re-check", () => {
|
||||||
//#given
|
//#given
|
||||||
versionReturnValue = true
|
versionReturnValue = true
|
||||||
mkdirSync(join(TEST_DATA_DIR, "opencode"), { recursive: true })
|
mkdirSync(join(TEST_DATA_DIR, "opencode"), { recursive: true })
|
||||||
@ -91,4 +103,59 @@ describe("isSqliteBackend", () => {
|
|||||||
//#then
|
//#then
|
||||||
expect(versionCheckCalls.length).toBe(1)
|
expect(versionCheckCalls.length).toBe(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("retries once when first result is false, then caches permanently", () => {
|
||||||
|
//#given
|
||||||
|
versionReturnValue = true
|
||||||
|
|
||||||
|
//#when: first call — DB does not exist
|
||||||
|
const first = isSqliteBackend()
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(first).toBe(false)
|
||||||
|
expect(versionCheckCalls.length).toBe(1)
|
||||||
|
|
||||||
|
//#when: second call — DB still does not exist (retry)
|
||||||
|
const second = isSqliteBackend()
|
||||||
|
|
||||||
|
//#then: retried once
|
||||||
|
expect(second).toBe(false)
|
||||||
|
expect(versionCheckCalls.length).toBe(2)
|
||||||
|
|
||||||
|
//#when: third call — no more retries
|
||||||
|
const third = isSqliteBackend()
|
||||||
|
|
||||||
|
//#then: no further checks
|
||||||
|
expect(third).toBe(false)
|
||||||
|
expect(versionCheckCalls.length).toBe(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("recovers on retry when DB appears after first false", () => {
|
||||||
|
//#given
|
||||||
|
versionReturnValue = true
|
||||||
|
|
||||||
|
//#when: first call — DB does not exist
|
||||||
|
const first = isSqliteBackend()
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(first).toBe(false)
|
||||||
|
|
||||||
|
//#given: DB appears before retry
|
||||||
|
mkdirSync(join(TEST_DATA_DIR, "opencode"), { recursive: true })
|
||||||
|
writeFileSync(DB_PATH, "")
|
||||||
|
|
||||||
|
//#when: second call — retry finds DB
|
||||||
|
const second = isSqliteBackend()
|
||||||
|
|
||||||
|
//#then: recovers to true and caches permanently
|
||||||
|
expect(second).toBe(true)
|
||||||
|
expect(versionCheckCalls.length).toBe(2)
|
||||||
|
|
||||||
|
//#when: third call — cached true
|
||||||
|
const third = isSqliteBackend()
|
||||||
|
|
||||||
|
//#then: no further checks
|
||||||
|
expect(third).toBe(true)
|
||||||
|
expect(versionCheckCalls.length).toBe(2)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
@ -4,19 +4,29 @@ import { getDataDir } from "./data-path"
|
|||||||
import { isOpenCodeVersionAtLeast, OPENCODE_SQLITE_VERSION } from "./opencode-version"
|
import { isOpenCodeVersionAtLeast, OPENCODE_SQLITE_VERSION } from "./opencode-version"
|
||||||
|
|
||||||
const NOT_CACHED = Symbol("NOT_CACHED")
|
const NOT_CACHED = Symbol("NOT_CACHED")
|
||||||
let cachedResult: boolean | typeof NOT_CACHED = NOT_CACHED
|
const FALSE_PENDING_RETRY = Symbol("FALSE_PENDING_RETRY")
|
||||||
|
let cachedResult: true | false | typeof NOT_CACHED | typeof FALSE_PENDING_RETRY = NOT_CACHED
|
||||||
|
|
||||||
export function isSqliteBackend(): boolean {
|
export function isSqliteBackend(): boolean {
|
||||||
if (cachedResult !== NOT_CACHED) {
|
if (cachedResult === true) return true
|
||||||
return cachedResult
|
if (cachedResult === false) return false
|
||||||
}
|
|
||||||
|
|
||||||
|
const check = (): boolean => {
|
||||||
const versionOk = isOpenCodeVersionAtLeast(OPENCODE_SQLITE_VERSION)
|
const versionOk = isOpenCodeVersionAtLeast(OPENCODE_SQLITE_VERSION)
|
||||||
const dbPath = join(getDataDir(), "opencode", "opencode.db")
|
const dbPath = join(getDataDir(), "opencode", "opencode.db")
|
||||||
const dbExists = existsSync(dbPath)
|
return versionOk && existsSync(dbPath)
|
||||||
|
}
|
||||||
|
|
||||||
cachedResult = versionOk && dbExists
|
if (cachedResult === FALSE_PENDING_RETRY) {
|
||||||
return cachedResult
|
const result = check()
|
||||||
|
cachedResult = result
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = check()
|
||||||
|
if (result) { cachedResult = true }
|
||||||
|
else { cachedResult = FALSE_PENDING_RETRY }
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resetSqliteBackendCache(): void {
|
export function resetSqliteBackendCache(): void {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user