Skip to content

Commit 7436803

Browse files
committed
fix: serialize hw reconnect triggers into one loop
1 parent 5088aad commit 7436803

2 files changed

Lines changed: 34 additions & 3 deletions

File tree

app/src/main/java/to/bitkit/repositories/TrezorRepo.kt

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import kotlinx.collections.immutable.persistentListOf
3131
import kotlinx.collections.immutable.toImmutableList
3232
import kotlinx.coroutines.CoroutineDispatcher
3333
import kotlinx.coroutines.CoroutineScope
34+
import kotlinx.coroutines.Job
3435
import kotlinx.coroutines.SupervisorJob
3536
import kotlinx.coroutines.delay
3637
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -96,6 +97,9 @@ class TrezorRepo @Inject constructor(
9697

9798
private val scope = CoroutineScope(SupervisorJob() + ioDispatcher)
9899

100+
@Volatile
101+
private var transportReconnectJob: Job? = null
102+
99103
init {
100104
observeExternalDisconnects()
101105
observeTransportRestored()
@@ -675,7 +679,7 @@ class TrezorRepo @Inject constructor(
675679
*/
676680
private fun observeTransportRestored() {
677681
trezorTransport.transportRestored.onEach {
678-
retryAutoReconnect()
682+
launchTransportReconnect()
679683
}.launchIn(scope)
680684
}
681685

@@ -684,8 +688,17 @@ class TrezorRepo @Inject constructor(
684688
* e.g. the USB attach intent the OS app picker routes to the activity (attach is
685689
* not broadcast to receivers, unlike detach).
686690
*/
687-
fun onTransportRestored() {
688-
scope.launch { retryAutoReconnect() }
691+
fun onTransportRestored() = launchTransportReconnect()
692+
693+
/**
694+
* Serializes reconnect triggers into one in-flight retry loop. A Trezor
695+
* re-enumerates USB during its unlock flow, so a single replug delivers several
696+
* attach intents; letting each spawn its own loop staggers connect attempts for
697+
* many seconds, and every attempt restarts the device's PIN entry.
698+
*/
699+
private fun launchTransportReconnect() {
700+
if (transportReconnectJob?.isActive == true) return
701+
transportReconnectJob = scope.launch { retryAutoReconnect() }
689702
}
690703

691704
/**

app/src/test/java/to/bitkit/repositories/TrezorRepoTest.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,24 @@ class TrezorRepoTest : BaseUnitTest() {
250250
verify(trezorService, times(2)).scan()
251251
}
252252

253+
@Test
254+
fun `repeated transport restored triggers run a single reconnect`() = test {
255+
val features = mockFeatures()
256+
val device = mockDeviceInfo()
257+
whenever(hwWalletStore.loadKnownDevices()).thenReturn(listOf(mockKnownDevice()))
258+
whenever(trezorService.isConnected()).thenReturn(false)
259+
whenever(trezorService.scan()).thenReturn(listOf(device))
260+
whenever(trezorService.connect(eq(DEVICE_ID), any())).thenReturn(features)
261+
sut = createSut()
262+
263+
repeat(3) { sut.onTransportRestored() }
264+
advanceUntilIdle()
265+
266+
assertNotNull(sut.state.value.connected)
267+
verify(trezorService, times(1)).scan()
268+
verify(trezorService, times(1)).connect(eq(DEVICE_ID), any())
269+
}
270+
253271
@Test
254272
fun `autoReconnect bails while device awaits pin entry`() = test {
255273
whenever(trezorUiHandler.needsPinEntry).thenReturn(MutableStateFlow(true))

0 commit comments

Comments
 (0)