Skip to content

[Bug] runBlocking can block AccConfigEditorActivity.onCreate() while reading ACC config #258

Description

@venkyqz

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:

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:

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

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:

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:

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:

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions