This document provides comprehensive context for reproducing and debugging a SIGTRAP crash that occurs when using LiveStore with Bun.
When creating and destroying ~100-180 LiveStore instances sequentially (each backed by SQLite WASM via wa-sqlite), Bun crashes with a SIGTRAP signal (Trace or breakpoint trap).
- Occurrence: Happens around iteration 100-180 when creating/destroying stores in a loop
- Concurrency: Crashes even with
CONCURRENCY=1(purely sequential processing) - Partial Mitigation: Adding
Bun.gc(true)after each store shutdown helps but doesn't fully prevent the crash - Intermittent: The exact iteration where it crashes varies between runs
- Likely Cause: WASM memory/resource exhaustion or improper cleanup
Application Code
↓
@livestore/livestore (createStorePromise / Store)
↓
@livestore/adapter-node (makeAdapter)
↓
@livestore/sqlite-wasm (sqliteDbFactory, loadSqlite3Wasm)
↓
wa-sqlite (SQLite compiled to WASM)
↓
Bun's WASM runtime
Store Creation:
- Load SQLite WASM module (
loadSqlite3Wasm()) - Create SQLite database factory (
sqliteDbFactory()) - Create in-memory SQLite database
- Run schema migrations (create tables)
- Initialize client session and sync processor
- Boot the store (start background fibers)
Store Shutdown:
- Close lifetime scope
- Finalize prepared statements
- Close SQLite connection
- Cleanup Effect fibers/resources
- Store creation:
packages/@livestore/livestore/src/store/create-store.ts - Adapter initialization:
packages/@livestore/adapter-node/src/client-session/adapter.ts - SQLite WASM loading:
packages/@livestore/sqlite-wasm/
- Create a test directory in the livestore repo:
mkdir -p tests/sigtrap-repro
cd tests/sigtrap-repro- Create
package.json:
{
"name": "sigtrap-repro",
"private": true,
"type": "module",
"scripts": {
"repro": "bun run repro.ts",
"repro:node": "npx tsx repro.ts",
"repro:smol": "bun --smol run repro.ts"
},
"dependencies": {
"@livestore/livestore": "file:../../packages/@livestore/livestore",
"@livestore/adapter-node": "file:../../packages/@livestore/adapter-node",
"@livestore/common": "file:../../packages/@livestore/common",
"@livestore/utils": "file:../../packages/@livestore/utils"
}
}- Create
schema.ts:
import { Events, makeSchema, State } from '@livestore/livestore'
import { Schema } from '@livestore/utils/effect'
// Simple table for testing
const items = State.SQLite.table({
name: 'items',
columns: {
id: State.SQLite.text({ primaryKey: true }),
data: State.SQLite.text(),
createdAt: State.SQLite.integer({ schema: Schema.DateFromNumber }),
},
})
// Simple events
export const events = {
itemAdded: Events.synced({
name: 'itemAdded',
schema: Schema.Struct({
id: Schema.String,
data: Schema.String,
}),
}),
}
// Materializers
const materializers = State.SQLite.materializers(events, {
itemAdded: ({ id, data }) => items.insert({ id, data, createdAt: Date.now() }),
})
export const tables = { items }
const state = State.SQLite.makeState({ tables, materializers })
export const schema = makeSchema({ state, events })- Create
repro.ts:
import * as fs from 'node:fs'
import * as os from 'node:os'
import { createStorePromise } from '@livestore/livestore'
import { makeAdapter } from '@livestore/adapter-node'
import { schema, events } from './schema.ts'
const STORES_DIR = `${os.tmpdir()}/livestore-sigtrap-repro-${Date.now()}`
const TOTAL_ITERATIONS = 300
const ITEMS_PER_STORE = 10
// Track memory usage
function logMemory(label: string) {
if (typeof Bun !== 'undefined') {
const usage = process.memoryUsage()
console.log(`[${label}] Memory - RSS: ${Math.round(usage.rss / 1024 / 1024)}MB, Heap: ${Math.round(usage.heapUsed / 1024 / 1024)}MB`)
}
}
async function createAndDestroyStore(iteration: number): Promise<void> {
const storeId = `test-store-${iteration}`
// Create store
const store = await createStorePromise({
storeId,
schema,
adapter: makeAdapter({
storage: { type: 'fs', baseDirectory: STORES_DIR },
}),
// Reduce log noise
logLevel: 'Warning',
})
// Insert some data to simulate real usage
for (let i = 0; i < ITEMS_PER_STORE; i++) {
store.commit(
events.itemAdded({
id: `item-${iteration}-${i}`,
data: `Test data for iteration ${iteration}, item ${i}. `.repeat(10),
})
)
}
// Small delay to let async operations settle
await new Promise((resolve) => setTimeout(resolve, 10))
// Shutdown store
await store.shutdownPromise()
// Attempt garbage collection (helps but doesn't fully prevent crash)
if (typeof Bun !== 'undefined' && typeof Bun.gc === 'function') {
Bun.gc(true)
}
}
async function main() {
console.log('='.repeat(60))
console.log('LiveStore SIGTRAP Reproduction Test')
console.log('='.repeat(60))
console.log(`Runtime: ${typeof Bun !== 'undefined' ? 'Bun ' + Bun.version : 'Node.js ' + process.version}`)
console.log(`Platform: ${process.platform} ${process.arch}`)
console.log(`Stores directory: ${STORES_DIR}`)
console.log(`Total iterations: ${TOTAL_ITERATIONS}`)
console.log(`Items per store: ${ITEMS_PER_STORE}`)
console.log('='.repeat(60))
console.log('')
// Clean up previous runs
if (fs.existsSync(STORES_DIR)) {
fs.rmSync(STORES_DIR, { recursive: true })
}
fs.mkdirSync(STORES_DIR, { recursive: true })
logMemory('Start')
const startTime = Date.now()
for (let i = 0; i < TOTAL_ITERATIONS; i++) {
const iterStart = Date.now()
try {
await createAndDestroyStore(i)
const iterDuration = Date.now() - iterStart
console.log(`Iteration ${i + 1}/${TOTAL_ITERATIONS} completed in ${iterDuration}ms`)
// Log memory every 25 iterations
if ((i + 1) % 25 === 0) {
logMemory(`Iteration ${i + 1}`)
}
} catch (error) {
console.error(`Error at iteration ${i + 1}:`, error)
throw error
}
}
const totalDuration = Date.now() - startTime
console.log('')
console.log('='.repeat(60))
console.log(`SUCCESS: Completed all ${TOTAL_ITERATIONS} iterations without crash!`)
console.log(`Total time: ${Math.round(totalDuration / 1000)}s`)
logMemory('End')
console.log('='.repeat(60))
// Cleanup
fs.rmSync(STORES_DIR, { recursive: true })
}
main().catch((error) => {
console.error('Fatal error:', error)
process.exit(1)
})- Install dependencies:
bun installBasic run (expected to crash):
bun run repro.tsWith Bun's small heap mode:
bun --smol run repro.tsCompare with Node.js (should NOT crash):
npx tsx repro.ts- Bun: Crashes with SIGTRAP around iteration 100-180, OR stores are immediately marked as shutdown (see "Additional Issue" section below)
- Node.js: Completes all 300 iterations successfully
Note: The repro test files have been set up in tests/sigtrap-repro/ in this repository. You can run them directly:
cd tests/sigtrap-repro
bun install
bun run repro.tsTypical crash output looks like:
Iteration 127/300 completed in 45ms
Iteration 128/300 completed in 43ms
[1] 12345 trace trap bun run repro.ts
Or with more detail:
fish: Job 1, 'bun run repro.ts' terminated by signal SIGTRAP (Trace or breakpoint trap)
Modify the GC call in createAndDestroyStore:
// Option A: Aggressive GC (default)
if (typeof Bun !== 'undefined' && typeof Bun.gc === 'function') {
Bun.gc(true) // synchronous, full GC
}
// Option B: No GC
// (comment out the above)
// Option C: Async GC
if (typeof Bun !== 'undefined' && typeof Bun.gc === 'function') {
Bun.gc(false) // asynchronous GC
}Modify ITEMS_PER_STORE:
const ITEMS_PER_STORE = 1 // Minimal
const ITEMS_PER_STORE = 10 // Default
const ITEMS_PER_STORE = 100 // HeavyAdd delay after shutdown:
await store.shutdownPromise()
// Add delay
await new Promise((resolve) => setTimeout(resolve, 100))
if (typeof Bun !== 'undefined' && typeof Bun.gc === 'function') {
Bun.gc(true)
}Change storage type:
adapter: makeAdapter({
storage: { type: 'memory' }, // Instead of 'fs'
}),bun --smol run repro.tsThis uses a smaller default heap which might trigger the issue sooner or change behavior.
Determine if the issue is:
- LiveStore-specific (event processing, sync, reactive system)
- wa-sqlite specific (SQLite WASM bindings)
- Bun WASM runtime issue (general WASM memory management)
- Bun SQLite issue (native SQLite bindings interference?)
Isolation tests to create:
// Test A: Pure wa-sqlite without LiveStore
import { loadSqlite3Wasm } from '@livestore/sqlite-wasm/load-wasm'
import { sqliteDbFactory } from '@livestore/sqlite-wasm/node'
async function testWaSqliteOnly() {
for (let i = 0; i < 300; i++) {
const sqlite3 = await loadSqlite3Wasm()
const factory = await sqliteDbFactory({ sqlite3 })
const db = await factory({ _tag: 'in-memory' })
// Do some operations
db.exec('CREATE TABLE test (id TEXT PRIMARY KEY)')
db.exec("INSERT INTO test VALUES ('foo')")
// Close
db.close()
console.log(`wa-sqlite iteration ${i + 1}`)
}
}// Test B: WASM module loading only
async function testWasmLoading() {
for (let i = 0; i < 300; i++) {
const sqlite3 = await loadSqlite3Wasm()
// Don't create any databases, just load the module
console.log(`WASM load iteration ${i + 1}`)
}
}Possible causes to investigate:
-
WASM Memory Fragmentation
- WASM linear memory not being properly freed
- Memory allocator fragmentation over time
-
File Descriptor Exhaustion
- SQLite file handles not being closed
- Check with
lsof -p <pid>during execution
-
Native Resource Leaks
- Bun-specific native bindings not cleaning up
- Worker threads or async handles not released
-
WASM Instance Accumulation
- Multiple WASM instances being kept alive
- Module caching preventing cleanup
Based on findings:
- Bun WASM runtime: https://github.com/oven-sh/bun/issues
- LiveStore: https://github.com/livestorejs/livestore/issues
- wa-sqlite: https://github.com/nickvans/wa-sqlite/issues
During testing, we discovered a related Bun-specific issue where stores are immediately marked as shutdown after creation. This manifests as:
Error: Store has been shut down (while performing "commit").
This happens even when calling store.commit() immediately after await createStorePromise() returns. This suggests a Bun-specific issue with the Effect runtime lifecycle management.
Symptoms:
- Store is returned from
createStorePromise()butisShutdownflag is alreadytrue - All subsequent operations fail with "Store has been shut down" error
- Does NOT occur with Node.js runtime
This may be:
- A separate bug from the SIGTRAP issue
- A symptom of the same underlying WASM/resource management problem
- Related to how Bun handles Effect fibers or scopes
| Approach | Result |
|---|---|
CONCURRENCY=1 (sequential) |
Still crashes |
Bun.gc(true) after each shutdown |
Delays crash, doesn't prevent |
bun --smol flag |
Crashes sooner (smaller heap) |
| Node.js runtime | No crash (completes all iterations) |
| Isolated WASM SQLite tests | Needs more investigation |
| Adding delays between operations | Store still prematurely shutdown |
Collect this information when reporting:
# Bun version
bun --version
# System info
uname -a
# macOS specific
sw_vers
# Check for other SQLite processes
ps aux | grep sqlite
# During run, check file descriptors
lsof -p $(pgrep -f "bun run repro") | wc -l- Bun Version: Check with
bun --version - Platform: macOS (Apple Silicon / M1/M2/M3)
- LiveStore Version: 0.4.0-dev.22 (from workspace)
- wa-sqlite: Via
@livestore/sqlite-wasm
const store = await createStorePromise({
// ...
logLevel: 'Debug', // or 'Trace' for maximum verbosity
})// If accessible, log WASM memory
function logWasmMemory() {
// This depends on how wa-sqlite exposes its memory
// May need to modify @livestore/sqlite-wasm
}bun --inspect run repro.tsThen connect Chrome DevTools to chrome://inspect.
On macOS:
# Enable core dumps
ulimit -c unlimited
# Run and let it crash
bun run repro.ts
# Analyze with lldb
lldb -c /cores/core.<pid> $(which bun)- (Add links to any related GitHub issues)
- Bun WASM issues: https://github.com/oven-sh/bun/labels/wasm
- wa-sqlite memory issues: (search their issue tracker)
- Run the repro script and confirm crash behavior
- Test isolated wa-sqlite without LiveStore
- Test pure WASM module loading
- Monitor file descriptors during execution
- Capture and analyze core dump
- Investigate the premature shutdown issue (may be Effect + Bun incompatibility)
- File bug report with minimal reproduction
The reproduction test files are located at:
- Directory:
tests/sigtrap-repro/ - Schema:
tests/sigtrap-repro/schema.ts - Repro Script:
tests/sigtrap-repro/repro.ts - Package:
tests/sigtrap-repro/package.json
To run:
cd tests/sigtrap-repro
bun install
bun run repro.ts