Skip to content

Commit 0b1a52e

Browse files
committed
Prevent db reset while statement is in flight; warm up Room3 db upon app start
1 parent d4ffab7 commit 0b1a52e

3 files changed

Lines changed: 58 additions & 10 deletions

File tree

sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/sqlite/Room3Dao.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ interface SongDao3 {
2626
@Query("SELECT * FROM song") suspend fun getAll(): List<SongEntity3>
2727

2828
@Query("SELECT count(*) FROM song") suspend fun count(): Int
29+
30+
/**
31+
* No-op write (matches no rows) used at warm-up to open Room's writer connection up front. A read
32+
* like [count] only opens a reader, so without this the first INSERT would (noisily) open and
33+
* bootstrap the writer connection inside a demo transaction.
34+
*/
35+
@Query("DELETE FROM song WHERE id < 0") suspend fun primeWriter()
2936
}
3037

3138
@Database(entities = [SongEntity3::class], version = 1, exportSchema = false)

sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/sqlite/SQLiteActivity.kt

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,12 @@ class SQLiteActivity : ComponentActivity() {
204204
/** Incremented on each tap that runs SQL. Used to retrigger the detail box's outline shimmer. */
205205
private var runTick by mutableStateOf(0)
206206

207+
/** True while a demo or reset is running SQL on a background thread. */
208+
private var dbOperationInFlight by mutableStateOf(false)
209+
210+
/** True for the duration of a reset; disables the reset button immediately (no debounce). */
211+
private var resetInProgress by mutableStateOf(false)
212+
207213
/**
208214
* The shared trace used when [shareScreenTrace] is enabled: one trace per visit to this screen.
209215
* onResume() generates a fresh one each time the screen is (re)entered.
@@ -316,7 +322,10 @@ class SQLiteActivity : ComponentActivity() {
316322
)
317323
}
318324

319-
ResetButton()
325+
ResetButton(
326+
dbOperationInFlight = dbOperationInFlight,
327+
resetInProgress = resetInProgress,
328+
)
320329

321330
// Same [CONTROL_SECTION_GAP] above as the other sections, separating the controls from
322331
// the detail output.
@@ -348,12 +357,17 @@ class SQLiteActivity : ComponentActivity() {
348357
runTick++ // shimmer the detail box outline in the integration color
349358

350359
lifecycleScope.launch {
351-
latestResult =
352-
withContext(Dispatchers.IO) {
353-
runInTransaction(variant.transactionName, variant.op) {
354-
SqlStatements.execute(applicationContext, variant.demo, heavyWork)
360+
dbOperationInFlight = true
361+
try {
362+
latestResult =
363+
withContext(Dispatchers.IO) {
364+
runInTransaction(variant.transactionName, variant.op) {
365+
SqlStatements.execute(applicationContext, variant.demo, heavyWork)
366+
}
355367
}
356-
}
368+
} finally {
369+
dbOperationInFlight = false
370+
}
357371
}
358372
}
359373

@@ -488,15 +502,38 @@ class SQLiteActivity : ComponentActivity() {
488502
}
489503

490504
@androidx.compose.runtime.Composable
491-
private fun ResetButton() {
505+
private fun ResetButton(dbOperationInFlight: Boolean, resetInProgress: Boolean) {
506+
// Debounce demo-driven disablement so fast taps don't flicker the button; reset disables
507+
// immediately via [resetInProgress]. [dbOperationInFlight] still guards [onClick] either way.
508+
var enabled by remember { mutableStateOf(true) }
509+
LaunchedEffect(dbOperationInFlight, resetInProgress) {
510+
when {
511+
resetInProgress -> enabled = false
512+
dbOperationInFlight -> {
513+
delay(RESET_DISABLE_DEBOUNCE_MS)
514+
enabled = false
515+
}
516+
else -> enabled = true
517+
}
518+
}
519+
492520
Button(
493521
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
522+
enabled = enabled,
494523
colors = ButtonDefaults.buttonColors(containerColor = Color.Gray, contentColor = Color.White),
495524
onClick = {
525+
if (dbOperationInFlight) return@Button
496526
lifecycleScope.launch {
497-
val message = withContext(Dispatchers.IO) { resetDatabases() }
498-
latestResult = message
499-
sqlDetail = "DROP: deletes every demo database file, resetting all row counts to 0."
527+
this@SQLiteActivity.resetInProgress = true
528+
this@SQLiteActivity.dbOperationInFlight = true
529+
try {
530+
val message = withContext(Dispatchers.IO) { resetDatabases() }
531+
latestResult = message
532+
sqlDetail = "DROP: deletes every demo database file, resetting all row counts to 0."
533+
} finally {
534+
this@SQLiteActivity.dbOperationInFlight = false
535+
this@SQLiteActivity.resetInProgress = false
536+
}
500537
}
501538
},
502539
) {
@@ -568,6 +605,9 @@ class SQLiteActivity : ComponentActivity() {
568605

569606
private companion object {
570607

608+
/** Demo SQL shorter than this won't visibly disable the reset button. */
609+
private const val RESET_DISABLE_DEBOUNCE_MS = 300L
610+
571611
/**
572612
* Builds a fresh sentry-trace header ("<traceId>-<spanId>-<sampled>") representing this screen
573613
* visit's trace. The trailing "-1" marks it sampled so the whole session is kept.

sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/sqlite/SampleDatabases.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ object SampleDatabases {
156156
// demo INSERT/SELECT reuses them instead of bootstrapping a connection inside its
157157
// transaction.
158158
runCatching { driverRoom2Db(appContext).songDao().also { it.primeWriter() }.count() }
159+
runCatching { driverRoom3Db(appContext).songDao().also { it.primeWriter() }.count() }
159160
runCatching { directHelper(appContext).writableDatabase }
160161
runCatching { openHelperRoomDb(appContext).songDao().also { it.primeWriter() }.count() }
161162
runCatching {

0 commit comments

Comments
 (0)