Skip to content

EXC_BAD_ACCESS crash during hot restart on iOS Simulator (NativeFinalizer race with background DartWorkers) #417

@JCKodel

Description

@JCKodel

Environment

  • powersync version: 2.1.0
  • sqlite3 / sqlite3_connection_pool version: 0.2.4
  • Flutter version: 3.41.9
  • Platform: iOS Simulator (ARM-64, macOS 26.3.1)
  • Dart mode: Debug (hot restart)

What happened

The app crashes intermittently during hot restart on the iOS Simulator.
The crash does not reproduce on a full app restart — only on hot restart.

Exception

Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000007
Faulting Thread: DartWorker

Root cause analysis

The crash is a use-after-free race condition between two concurrent events triggered by hot restart:

  1. Thread ADart_ShutdownIsolate fires NativeFinalizer callbacks,
    which call sqlite3_connection_pool_closesqlite3Close, tearing down
    native SQLite state.

  2. Thread B (crashed) — A DartWorker is still processing a message
    from a background isolate. The message handler dereferences a Dart object
    whose underlying native resource (the SQLite connection) has already been
    freed by Thread A.

The crash address 0x0000000000000007 is a strong indicator of a
use-after-free: the code reads a field at offset 7 from a pointer that now
points to deallocated memory.

Key frames from the crash report:

Thread that closes the connection (NativeFinalizer path):
sqlite3MemFree
exprListDeleteNN
sqlite3SchemaClear
sqlite3BtreeClose
sqlite3LeaveMutexAndCloseZombie
sqlite3Close
pkg_sqlite3_connection_pool_close ← native asset: sqlite3_connection_pool
dart::NativeFinalizer::RunCallback
dart::Isolate::RunAndCleanupFinalizersOnShutdown
dart::Isolate::Shutdown
Dart_ShutdownIsolate
dart::ThreadPool::WorkerLoop

Crashed thread (still executing Dart code):
[Dart JIT frames — message handler executing against a torn-down connection]
dart::DartLibraryCalls::HandleMessage
dart::IsolateMessageHandler::HandleMessage
dart::MessageHandler::TaskCallback
dart::ThreadPool::WorkerLoop

Additional symptom — Thread 23 is blocked waiting on
dart::SafepointRwLock::EnterRead inside Library::LookupLibrary while
trying to deserialize a message, suggesting the VM library table is in a
partially-shutdown state during the race.

Expected behavior

PowerSync should drain or cancel all pending background isolate messages and
explicitly close SQLite connections before the Dart isolate reaches the
RunAndCleanupFinalizersOnShutdown phase during hot restart. This would
ensure no DartWorker is left processing messages that reference resources
whose NativeFinalizer has already fired.

Actual behavior

The connection pool's NativeFinalizer fires concurrently with in-flight
Dart message handlers, causing a use-after-free segfault.

Crash report

crash.txt.zip

Workaround

None found. The crash is non-deterministic and depends on thread scheduling
timing. It occurs more frequently under load or when more background workers
are active.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions