Skip to content

Commit 7c2caf4

Browse files
edyedy
authored andcommitted
chore: merge rename-event-to-bus into main
2 parents f0000b3 + 1257bc9 commit 7c2caf4

70 files changed

Lines changed: 1367 additions & 1154 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

MERGE.md

Lines changed: 20 additions & 5 deletions
Large diffs are not rendered by default.

packages/core/src/bus.ts

Lines changed: 657 additions & 0 deletions
Large diffs are not rendered by default.

packages/core/src/database/database.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export * as Database from "./database"
22

33
import { EffectDrizzleSqlite } from "@opencode-ai/effect-drizzle-sqlite"
44
import { layer as sqliteLayer } from "#sqlite"
5-
import { Context, Effect, Layer } from "effect"
5+
import { Context, Effect, Layer, Schedule } from "effect"
66
import { Global } from "../global"
77
import { Flag } from "../flag/flag"
88
import { isAbsolute, join } from "path"
@@ -24,9 +24,22 @@ const layer = Layer.effect(
2424
Effect.gen(function* () {
2525
const db = yield* makeDatabase
2626

27-
yield* db.run("PRAGMA journal_mode = WAL")
28-
yield* db.run("PRAGMA synchronous = NORMAL")
27+
// busy_timeout must precede journal_mode: the WAL switch takes a write lock
28+
// and would fail with SQLITE_BUSY under concurrent startup at zero timeout.
2929
yield* db.run("PRAGMA busy_timeout = 5000")
30+
// The WAL mode switch needs an exclusive lock; busy_timeout does not always
31+
// cover it, so retry briefly when two processes cold-start the same file.
32+
yield* db
33+
.run("PRAGMA journal_mode = WAL")
34+
.pipe(
35+
Effect.retry(
36+
Schedule.exponential(25).pipe(
37+
Schedule.jittered,
38+
Schedule.while((meta) => meta.elapsed < 5_000),
39+
),
40+
),
41+
)
42+
yield* db.run("PRAGMA synchronous = NORMAL")
3043
yield* db.run("PRAGMA cache_size = -64000")
3144
yield* db.run("PRAGMA foreign_keys = ON")
3245
yield* db.run("PRAGMA wal_checkpoint(PASSIVE)")

packages/core/src/database/migration.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,26 @@ export function apply(db: Database) {
2323
)
2424
if (tables.some((table) => table.name === "session")) return yield* applyOnly(db, migrations)
2525
if (tables.length > 0) return yield* Effect.die("Database is not empty and has no session table")
26-
yield* db.transaction((tx) =>
26+
// Bootstrap DDL cannot replay, so the emptiness decision must be re-verified
27+
// inside this immediate write transaction: another process may have
28+
// bootstrapped the same file after the read above.
29+
const bootstrapped = yield* db.transaction((tx) =>
2730
Effect.gen(function* () {
28-
yield* schema.up(tx)
2931
yield* tx.run(
30-
sql`CREATE TABLE ${sql.identifier("migration")} (id TEXT PRIMARY KEY, time_completed INTEGER NOT NULL)`,
32+
sql`CREATE TABLE IF NOT EXISTS ${sql.identifier("migration")} (id TEXT PRIMARY KEY, time_completed INTEGER NOT NULL)`,
3133
)
34+
if (yield* tx.get(sql`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ${"session"}`))
35+
return false
36+
yield* schema.up(tx)
3237
yield* Effect.forEach(migrations, (migration) =>
3338
tx.run(
3439
sql`INSERT OR IGNORE INTO ${sql.identifier("migration")} (id, time_completed) VALUES (${migration.id}, ${Date.now()})`,
3540
),
3641
)
42+
return true
3743
}),
3844
)
45+
if (!bootstrapped) return yield* applyOnly(db, migrations)
3946
}),
4047
)
4148
}
@@ -68,8 +75,12 @@ export function applyOnly(db: Database, input: Migration[]) {
6875

6976
for (const migration of input) {
7077
if (completed.has(migration.id)) continue
78+
// Migration DDL cannot replay, so completion must be re-verified inside
79+
// this immediate write transaction: another process may have claimed the
80+
// migration after the journal read above.
7181
yield* db.transaction((tx) =>
7282
Effect.gen(function* () {
83+
if (yield* tx.get(sql`SELECT id FROM ${sql.identifier("migration")} WHERE id = ${migration.id}`)) return
7384
yield* migration.up(tx)
7485
yield* tx.run(
7586
sql`INSERT OR IGNORE INTO ${sql.identifier("migration")} (id, time_completed) VALUES (${migration.id}, ${Date.now()})`,

0 commit comments

Comments
 (0)