Skip to content

Commit 744e862

Browse files
committed
Workaround offer throwing on closed channel
Will fix rare crashes caused by uncaught exception that could be throw from the onConnectionStateChange method in GattConnectionImpl. Related to Kotlin/kotlinx.coroutines#974
1 parent a142245 commit 744e862

File tree

2 files changed

+23
-25
lines changed

2 files changed

+23
-25
lines changed

core/src/androidMain/kotlin/com/beepiz/bluetooth/gattcoroutines/GattConnectionImpl.kt

+15-25
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.beepiz.bluetooth.gattcoroutines
33
import android.bluetooth.*
44
import android.os.Build.VERSION.SDK_INT
55
import androidx.annotation.RequiresApi
6+
import com.beepiz.bluetooth.gattcoroutines.extensions.offerCatching
67
import kotlinx.coroutines.*
78
import kotlinx.coroutines.channels.*
89
import kotlinx.coroutines.sync.Mutex
@@ -48,8 +49,8 @@ internal class GattConnectionImpl(
4849
private val isConnectedBroadcastChannel = ConflatedBroadcastChannel(false)
4950
private val isConnectedChannel get() = isConnectedBroadcastChannel.openSubscription()
5051
override var isConnected: Boolean
51-
get() = !isClosed && isConnectedBroadcastChannel.value
52-
private set(value) = isConnectedBroadcastChannel.offer(value).let { Unit }
52+
get() = runCatching { !isClosed && isConnectedBroadcastChannel.value }.getOrDefault(false)
53+
private set(value) = isConnectedBroadcastChannel.offerCatching(value).let { Unit }
5354
private var isClosed = false
5455
private var closedException: ConnectionClosedException? = null
5556
private val stateChangeBroadcastChannel =
@@ -102,30 +103,20 @@ internal class GattConnectionImpl(
102103
}
103104

104105
override fun close(notifyStateChangeChannel: Boolean) {
105-
closeInternal(notifyStateChangeChannel,
106-
ConnectionClosedException()
107-
)
106+
closeInternal(notifyStateChangeChannel, ConnectionClosedException())
108107
}
109108

110109
private fun closeInternal(notifyStateChangeChannel: Boolean, cause: ConnectionClosedException) {
111110
closedException = cause
112111
bluetoothGatt?.close()
113112
isClosed = true
114-
try {
115-
isConnected = false
116-
if (notifyStateChangeChannel) {
117-
stateChangeBroadcastChannel.offer(
118-
GattConnection.StateChange(
119-
STATUS_SUCCESS,
120-
BluetoothProfile.STATE_DISCONNECTED
121-
)
122-
)
123-
}
124-
} catch (ignored: ConnectionClosedException) {
125-
// isConnected property is delegated by a channel that throws if already closed,
126-
// but we don't need the exception to be thrown again here, so we ignore it.
127-
// We do the same for stateChangeBroadcastChannel.offer(…) call.
128-
}
113+
isConnected = false
114+
if (notifyStateChangeChannel) stateChangeBroadcastChannel.offerCatching(
115+
element = GattConnection.StateChange(
116+
status = STATUS_SUCCESS,
117+
newState = BluetoothProfile.STATE_DISCONNECTED
118+
)
119+
)
129120
isConnectedBroadcastChannel.close(cause)
130121
rssiChannel.close(cause)
131122
servicesDiscoveryChannel.close(cause)
@@ -202,7 +193,7 @@ internal class GattConnectionImpl(
202193
when (status) {
203194
STATUS_SUCCESS -> isConnected = newState == BluetoothProfile.STATE_CONNECTED
204195
}
205-
stateChangeBroadcastChannel.offer(
196+
stateChangeBroadcastChannel.offerCatching(
206197
GattConnection.StateChange(
207198
status,
208199
newState
@@ -251,7 +242,8 @@ internal class GattConnectionImpl(
251242
GattConnection.Phy(
252243
txPhy,
253244
rxPhy
254-
), status)
245+
), status
246+
)
255247
}
256248
}
257249

@@ -305,9 +297,7 @@ internal class GattConnectionImpl(
305297

306298
@Suppress("NOTHING_TO_INLINE")
307299
private inline fun checkNotClosed() {
308-
if (isClosed) throw ConnectionClosedException(
309-
closedException
310-
)
300+
if (isClosed) throw ConnectionClosedException(closedException)
311301
}
312302

313303
private class GattResponse<out E>(val e: E, val status: Int) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.beepiz.bluetooth.gattcoroutines.extensions
2+
3+
import kotlinx.coroutines.channels.SendChannel
4+
5+
//TODO: Replace that when https://github.com/Kotlin/kotlinx.coroutines/issues/974 is resolved.
6+
internal fun <E> SendChannel<E>.offerCatching(element: E): Boolean {
7+
return runCatching { offer(element) }.getOrDefault(false)
8+
}

0 commit comments

Comments
 (0)