alchemy dev runs each Worker in a workerd of its own, and each runtime's R2 simulator (R2BucketObject) opens the same {storage}/r2/cloudflare-runtime-R2BucketObject/<id>.sqlite for a bucket the Workers share. When two Workers write that bucket at the same moment, one write fails:
Error: database is locked: SQLITE_BUSY
at putRow (bindings/r2-bucket/R2Bucket.worker.mjs)
→ put: Unspecified error (0)
The simulator itself says it assumes "a single runtime instance operating on a blob store". We hit it with three Workers sharing one bucket, one writing small objects while another wrote others, in about one run in four of a test that does both.
Our patch, applied to the built worker (dist/core/workers/bindings/r2-bucket/R2Bucket.worker.mjs), retries a write that finds the database locked, after yielding, since time doesn't advance within a synchronous turn:
async function unlocked(write) {
for (let attempt = 1; ; attempt++)
try {
return write();
} catch (error) {
if (attempt === 50 || !String(error).includes("SQLITE_BUSY")) throw error;
await scheduler.wait(attempt * 2);
}
}
// #put: oldBlobIds = await unlocked(() => this.#stmts.put(row, opts.onlyIf));
// #delete: const oldBlobIds = await unlocked(() => this.#stmts.deleteByKeys(keys));
It doesn't address the blob store's in-process locking, which is also per runtime.
Ask: host a shared simulator in one runtime, with the other Workers reaching it through their binding, or give the simulator's database a busy timeout.
Versions: @alchemy.run/cloudflare-runtime 2.0.0-beta.79, workerd 1.20260921.1, macOS arm64.
alchemy devruns each Worker in a workerd of its own, and each runtime's R2 simulator (R2BucketObject) opens the same{storage}/r2/cloudflare-runtime-R2BucketObject/<id>.sqlitefor a bucket the Workers share. When two Workers write that bucket at the same moment, one write fails:The simulator itself says it assumes "a single runtime instance operating on a blob store". We hit it with three Workers sharing one bucket, one writing small objects while another wrote others, in about one run in four of a test that does both.
Our patch, applied to the built worker (
dist/core/workers/bindings/r2-bucket/R2Bucket.worker.mjs), retries a write that finds the database locked, after yielding, since time doesn't advance within a synchronous turn:It doesn't address the blob store's in-process locking, which is also per runtime.
Ask: host a shared simulator in one runtime, with the other Workers reaching it through their binding, or give the simulator's database a busy timeout.
Versions:
@alchemy.run/cloudflare-runtime2.0.0-beta.79, workerd 1.20260921.1, macOS arm64.