[Bug] `runBlocking` can block `AccConfigEditorActivity.onCreate()` while reading ACC config
Nobody has claimed this yet.
- Dominant language
- Kotlin
- Stars
- 1.4k
- Forks
- 98
- PR merge metrics
- No merged PRs in 30d
Description
Hi AccA Team,
I’m a PhD student researching Android thread-related issues. My research group recently ran a static analysis scan for thread-affinity and main-thread blocking bugs in real-world Android apps, and our prototype flagged a potential issue in AccA.
## Checked target
- App: AccA (`mattecarra.accapp`)
- Repository: `https://github.com/MatteCarra/AccA`
- Source-level caller: `mattecarra.accapp.activities.AccConfigEditorActivity.onCreate(...)`
- Additional helper: `mattecarra.accapp.acc.Acc.isInstalledAccOutdated()`
- Detected API / pattern: `kotlinx.coroutines.runBlocking(...)`
- Observed context in our scan: Android main/UI thread
- Expected behavior: avoid blocking Activity startup/UI setup paths
## What I found
The latest `develop` source still calls `runBlocking` directly from `AccConfigEditorActivity.onCreate(...)` while preparing the editor state:
```kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
val config = when {
savedInstanceState?.containsKey(Constants.ACC_CONFIG_KEY) == true ->
savedInstanceState.getSerializable(Constants.ACC_CONFIG_KEY) as AccConfig
intent.hasExtra(Constants.ACC_CONFIG_KEY) ->
intent.getSerializableExtra(Constants.ACC_CONFIG_KEY) as AccConfig
else -> try {
runBlocking {
Acc.instance.readConfig()
}
} catch (ex: Exception) {
ex.printStackTrace()
showConfigReadError()
runBlocking {
Acc.instance.readDefaultConfig()
}
}
}
...
}
```
The ACC helper also has a synchronous bridge:
```kotlin
fun isInstalledAccOutdated(): Boolean = runBlocking {
instance.getAccVersion()?.let { it < bundledVersion } ?: true
}
```
The important point is that `runBlocking` blocks the current thread until the coroutine completes. Therefore, when it is invoked from `Activity.onCreate(...)`, the main/UI thread waits synchronously for the ACC configuration or version operation to complete.
## Verified bug trace
```text
User opens ACC config editor
-> AccConfigEditorActivity.onCreate(...)
-> load profile/config from Intent or saved state
-> fallback path calls runBlocking { Acc.instance.readConfig() }
-> error path calls runBlocking { Acc.instance.readDefaultConfig() }
-> UI thread is blocked until the ACC config read completes
-> possible slow Activity startup, jank, or ANR risk
```
Additional helper path:
```text
UI/service path checking bundled ACC version
-> Acc.isInstalledAccOutdated()
-> runBlocking { instance.getAccVersion() }
-> caller thread waits synchronously
```
I did not dynamically reproduce an ANR. This should be treated as a source-confirmed main-thread blocking risk rather than a guaranteed crash. The risk depends on how fast the ACC backend/shell/config operations complete on a given rooted device.
I also searched the current GitHub issues for terms such as `runBlocking`, `AccConfigEditorActivity`, `readConfig onCreate`, and `main thread readConfig`, and did not find an existing matching issue.
## Why this matters
`onCreate(...)` is part of Activity startup and normally runs on the main thread. Blocking it while reading configuration or querying the ACC backend can delay UI rendering and make the app appear frozen, especially on slow devices, after root/shell delays, or if the ACC backend is unavailable.
The coroutine body may call suspending APIs, but `runBlocking` still blocks the caller until the coroutine completes.
## Possible fix
Avoid synchronously reading ACC state in `onCreate(...)`. Instead, initialize the UI with a loading state and load the config from a lifecycle-aware coroutine:
```kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
lifecycleScope.launch {
val config = withContext(Dispatchers.IO) {
try {
Acc.instance.readConfig()
} catch (ex: Exception) {
ex.printStackTrace()
null
}
}
if (config == null) {
showConfigReadError()
val defaultConfig = withContext(Dispatchers.IO) {
Acc.instance.readDefaultConfig()
}
initializeEditor(defaultConfig)
} else {
initializeEditor(config)
}
}
}
```
Similarly, `isInstalledAccOutdated()` could be made `suspend`, or expose an async API:
```kotlin
suspend fun isInstalledAccOutdated(): Boolean =
withContext(Dispatchers.IO) {
instance.getAccVersion()?.let { it < bundledVersion } ?: true
}
```
## References
- Kotlin `runBlocking` documentation: `runBlocking` blocks the current thread until completion.
- Android ANR guidance: blocking the main/UI thread can lead to unresponsiveness or ANRs.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with AccConfigEditorActivity.onCreate(...) and Acc.isInstalledAccOutdated(), the two entry points named in the report. Trace their callers and existing configuration-loading lifecycle, then verify the editor still handles saved, intent, normal, and default configurations without synchronously blocking startup. Done means the reported main-thread runBlocking paths are removed or made asynchronous while the existing error behavior remains intact.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, kotlin
- Domain
- mobile, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100