Skip to content

Race condition in PsSqlDriver? #135

@ecstatic-morse

Description

@ecstatic-morse

I'm seeing intermittent crashes involving PsSqlDriver, for example

inside fireTableUpdates
     Fatal Exception: java.lang.ArrayIndexOutOfBoundsException: length=7; index=7
       at java.util.LinkedHashMap.keysToArray(LinkedHashMap.java:703)
       at java.util.LinkedHashMap.keysToArray(LinkedHashMap.java:691)
       at java.util.HashSet.toArray(HashSet.java:376)
       at java.util.ArrayList.<init>(ArrayList.java:188)
       at kotlin.collections.CollectionsKt___CollectionsKt.toMutableList(_Collections.kt:1343)
       at kotlin.collections.CollectionsKt___CollectionsKt.toList(_Collections.kt:1324)
       at com.powersync.PsSqlDriver.fireTableUpdates(PsSqlDriver.kt:37)
       at com.powersync.DatabaseDriverFactory.onTransactionCommit(DatabaseDriverFactory.android.kt:29)
       at io.requery.android.database.sqlite.SQLiteConnection.nativeExecuteForChangedRowCount(SQLiteConnection.java)
       at io.requery.android.database.sqlite.SQLiteConnection.executeForChangedRowCount(SQLiteConnection.java:778)
       at io.requery.android.database.sqlite.SQLiteSession.executeForChangedRowCount(SQLiteSession.java:764)
       at io.requery.android.database.sqlite.SQLiteStatement.executeUpdateDelete(SQLiteStatement.java:71)
       at app.cash.sqldelight.driver.android.AndroidPreparedStatement.execute(AndroidSqliteDriver.kt:261)
       at app.cash.sqldelight.driver.android.AndroidSqliteDriver$execute$2.invoke(AndroidSqliteDriver.kt:184)
       at app.cash.sqldelight.driver.android.AndroidSqliteDriver$execute$2.invoke(AndroidSqliteDriver.kt:184)
       at app.cash.sqldelight.driver.android.AndroidSqliteDriver.execute-zeHU3Mk(AndroidSqliteDriver.kt:169)
       at app.cash.sqldelight.driver.android.AndroidSqliteDriver.execute(AndroidSqliteDriver.kt:184)
       at com.powersync.PsSqlDriver.execute(:7)
       at com.powersync.db.internal.InternalDatabaseImpl.execute(InternalDatabaseImpl.kt:85)
       at com.powersync.db.PowerSyncDatabaseImpl.execute(PowerSyncDatabaseImpl.kt:235)
       at com.ravenscarlabs.yingyu.powersync.SyncedDatabase$execute$2.invokeSuspend(SyncedDb.kt:140)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
       at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
       at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:113)
       at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
       at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:820)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)

and

inside toFriendlyTableName (whose argument comes from PsSqlDriver)
          Fatal Exception: java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.text.Regex.containsMatchIn, parameter input
       at kotlin.text.Regex.containsMatchIn(:2)
       at com.powersync.db.internal.InternalDatabaseImpl.toFriendlyTableName(InternalDatabaseImpl.kt:203)
       at com.powersync.db.internal.InternalDatabaseImpl.access$toFriendlyTableName(InternalDatabaseImpl.kt:24)
       at com.powersync.db.internal.InternalDatabaseImpl$1$2.emit(InternalDatabaseImpl.kt:71)
       at com.powersync.db.internal.InternalDatabaseImpl$1$2.emit(InternalDatabaseImpl.kt:70)
       at kotlinx.coroutines.flow.FlowKt__DelayKt$debounceInternal$1$3$1.invokeSuspend(Delay.kt:226)
       at kotlinx.coroutines.flow.FlowKt__DelayKt$debounceInternal$1$3$1.invoke(:8)
       at kotlinx.coroutines.flow.FlowKt__DelayKt$debounceInternal$1$3$1.invoke(:2)
       at kotlinx.coroutines.selects.SelectImplementation$ClauseData.invokeBlock(Select.kt:843)
       at kotlinx.coroutines.selects.SelectImplementation.complete(Select.kt:715)
       at kotlinx.coroutines.selects.SelectImplementation.doSelectSuspend(Select.kt:456)
       at kotlinx.coroutines.selects.SelectImplementation.access$doSelectSuspend(Select.kt:251)
       at kotlinx.coroutines.selects.SelectImplementation$doSelectSuspend$1.invokeSuspend(:13)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
       at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
       at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:113)
       at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
       at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:820)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
        

Operations on PsSqlDriver.pendingUpdates aren't thread-safe (it's a MutableSet), and I can't see any synchronization by callers that would make this correct:

val mappedDriver = PsSqlDriver(scope = scope, driver = driver)
driver.connection.database.addUpdateListener { _, _, table, _ ->
mappedDriver.updateTable(table)
}

withContext(dbContext) {
val r = executeSync(sql, parameters)
driver.fireTableUpdates()
r
}

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