Minimal reproduction for https://github.com/expo/expo — expo-sqlite@57.0.2.
Two reads issued concurrently over one prepared statement (prepareAsync)
corrupt each other's result sets: one reader's getAllAsync() steps rows bound
by another reader's run. step/getAll/reset don't take the per-statement
lock that run does, and getAllAsync() is a second native call after run's
await, so the per-call lock cannot cover a cursor's run -> getAll pair.
npm install
npx expo run:ios --configuration Release
Release embeds the JS bundle, so no Metro is needed. The repro is in App.tsx.
SERIALIZED A(a/1)=["A1"] B(b/1)=["B1"]
CONCURRENT {"a/1":["A1"],"a/2":["A2","B3"],"a/3":["A3","B3"],"b/1":["B1","B3"],"b/2":["B2","B3"],"b/3":["B3","B3"]}
RESULT: CORRUPTED — 5/6 reads wrong
Each read that should return a single row gets another reader's row ("B3")
leaked in. The serialized control is correct.
android/src/main/java/expo/modules/sqlite/SQLiteModule.kt:runwraps reset+bind+step insynchronized(statement)("stateful … critical section for thread safety");step/getAll/resettake no lock.ios/SQLiteModule.swift: identical —runtakesstatement.lock;step/getAll/resettake none.