Skip to content

Commit 17ce10d

Browse files
authored
Update coroutines version and usage (#126)
* coroutines 1.4.2-native-mt * Use Flow.stateIn operator in reconnectable implementation * use CompletableJob in Cancellable interface for better connection state handling * use CompletableJob.completeExceptionally instead of Job.cancel in places, where error should happen * use new channel API with onUndeliveredElement * introduce SendChannel.safeOffer for better releasing of frames/payloads if channel is closed * better channel handing in TCP and local transports
1 parent 3353ab4 commit 17ce10d

File tree

24 files changed

+141
-100
lines changed

24 files changed

+141
-100
lines changed

examples/nodejs-tcp-transport/src/jsMain/kotlin/Server.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ fun NodeJsTcpServerTransport(port: Int, onStart: () -> Unit = {}): ServerTranspo
5757
// nodejs TCP transport connection - may not work in all cases, not tested properly
5858
@OptIn(ExperimentalCoroutinesApi::class, TransportApi::class)
5959
class NodeJsTcpConnection(private val socket: Socket) : Connection {
60-
override val job: Job = Job()
60+
override val job: CompletableJob = Job()
6161

6262
private val sendChannel = Channel<ByteReadPacket>(8)
6363
private val receiveChannel = Channel<ByteReadPacket>(8)

gradle.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ group=io.rsocket.kotlin
1919
version=0.12.0
2020

2121
#Versions
22-
kotlinVersion=1.4.20
22+
kotlinVersion=1.4.21
2323
ktorVersion=1.4.3
24-
kotlinxCoroutinesVersion=1.3.9-native-mt-2
24+
kotlinxCoroutinesVersion=1.4.2-native-mt
2525
kotlinxAtomicfuVersion=0.14.4
2626
kotlinxSerializationVersion=1.0.1
2727
kotlinxBenchmarkVersion=0.2.0-dev-20

playground/src/commonMain/kotlin/streams.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,11 @@ import io.rsocket.kotlin.*
1818
import kotlinx.coroutines.*
1919
import kotlinx.coroutines.channels.*
2020
import kotlinx.coroutines.flow.*
21-
import kotlin.coroutines.*
2221

2322
@ExperimentalStreamsApi
2423
private suspend fun s() {
2524
val flow = flow {
26-
val strategy = coroutineContext[RequestStrategy]!!.provide()
25+
val strategy = currentCoroutineContext()[RequestStrategy]!!.provide()
2726
var i = strategy.firstRequest()
2827
println("INIT: $i")
2928
var r = 0

rsocket-core/src/commonMain/kotlin/io/rsocket/kotlin/Cancelable.kt renamed to rsocket-core/src/commonMain/kotlin/io/rsocket/kotlin/Cancellable.kt

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,10 @@ package io.rsocket.kotlin
1818

1919
import kotlinx.coroutines.*
2020

21-
interface Cancelable {
22-
val job: Job
21+
interface Cancellable {
22+
val job: CompletableJob
2323
}
2424

25-
val Cancelable.isActive: Boolean get() = job.isActive
26-
fun Cancelable.cancel(cause: CancellationException? = null): Unit = job.cancel(cause)
27-
fun Cancelable.cancel(message: String, cause: Throwable? = null): Unit = job.cancel(message, cause)
28-
suspend fun Cancelable.join(): Unit = job.join()
29-
suspend fun Cancelable.cancelAndJoin(): Unit = job.cancelAndJoin()
25+
val Cancellable.isActive: Boolean get() = job.isActive
26+
suspend fun Cancellable.join(): Unit = job.join()
27+
suspend fun Cancellable.cancelAndJoin(): Unit = job.cancelAndJoin()

rsocket-core/src/commonMain/kotlin/io/rsocket/kotlin/Connection.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import io.rsocket.kotlin.frame.*
2525
* That interface isn't stable for inheritance.
2626
*/
2727
@TransportApi
28-
interface Connection : Cancelable {
28+
interface Connection : Cancellable {
2929

3030
@DangerousInternalIoApi
3131
val pool: ObjectPool<ChunkBuffer>

rsocket-core/src/commonMain/kotlin/io/rsocket/kotlin/RSocket.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import io.ktor.utils.io.core.*
2020
import io.rsocket.kotlin.payload.*
2121
import kotlinx.coroutines.flow.*
2222

23-
interface RSocket : Cancelable {
23+
interface RSocket : Cancellable {
2424

2525
suspend fun metadataPush(metadata: ByteReadPacket) {
2626
metadata.release()

rsocket-core/src/commonMain/kotlin/io/rsocket/kotlin/RSocketRequestHandler.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class RSocketRequestHandlerBuilder internal constructor() {
5353
requestChannel = block
5454
}
5555

56-
internal fun build(job: Job): RSocket =
56+
internal fun build(job: CompletableJob): RSocket =
5757
RSocketRequestHandler(job, metadataPush, fireAndForget, requestResponse, requestStream, requestChannel)
5858
}
5959

@@ -65,7 +65,7 @@ fun RSocketRequestHandler(parentJob: Job? = null, configure: RSocketRequestHandl
6565
}
6666

6767
private class RSocketRequestHandler(
68-
override val job: Job,
68+
override val job: CompletableJob,
6969
private val metadataPush: (suspend RSocket.(metadata: ByteReadPacket) -> Unit)? = null,
7070
private val fireAndForget: (suspend RSocket.(payload: Payload) -> Unit)? = null,
7171
private val requestResponse: (suspend RSocket.(payload: Payload) -> Payload)? = null,

rsocket-core/src/commonMain/kotlin/io/rsocket/kotlin/core/RSocketConnectorBuilder.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,10 @@ public class RSocketConnectorBuilder internal constructor() {
100100
)
101101

102102
private companion object {
103-
private val defaultAcceptor: ConnectionAcceptor = ConnectionAcceptor { EmptyRSocket }
103+
private val defaultAcceptor: ConnectionAcceptor = ConnectionAcceptor { EmptyRSocket() }
104104

105-
private object EmptyRSocket : RSocket {
106-
override val job: Job = NonCancellable
105+
private class EmptyRSocket : RSocket {
106+
override val job: CompletableJob = Job()
107107
}
108108
}
109109
}

rsocket-core/src/commonMain/kotlin/io/rsocket/kotlin/core/RSocketServer.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ class RSocketServer internal constructor(
6868

6969
private suspend fun Connection.failSetup(error: RSocketError.Setup): Nothing {
7070
sendFrame(ErrorFrame(0, error))
71-
cancel("Setup failed", error)
71+
job.completeExceptionally(error)
7272
throw error
7373
}
7474
}

rsocket-core/src/commonMain/kotlin/io/rsocket/kotlin/core/ReconnectableRSocket.kt

Lines changed: 17 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -27,40 +27,40 @@ import kotlinx.coroutines.flow.*
2727
internal typealias ReconnectPredicate = suspend (cause: Throwable, attempt: Long) -> Boolean
2828

2929
@Suppress("FunctionName")
30-
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
30+
@OptIn(FlowPreview::class)
3131
internal suspend fun ReconnectableRSocket(
3232
logger: Logger,
3333
connect: suspend () -> RSocket,
3434
predicate: ReconnectPredicate,
3535
): RSocket {
36-
val state = MutableStateFlow<ReconnectState>(ReconnectState.Connecting)
37-
38-
val job =
36+
val job = Job()
37+
val state =
3938
connect.asFlow()
4039
.map<RSocket, ReconnectState> { ReconnectState.Connected(it) } //if connection established - state = connected
4140
.onStart { emit(ReconnectState.Connecting) } //init - state = connecting
42-
.retryWhen { cause, attempt ->
41+
.retryWhen { cause, attempt -> //reconnection logic
4342
logger.debug(cause) { "Connection establishment failed, attempt: $attempt. Trying to reconnect..." }
4443
predicate(cause, attempt)
45-
} //reconnection logic
46-
.catch {
44+
}
45+
.catch { //reconnection failed - state = failed
4746
logger.debug(it) { "Reconnection failed" }
4847
emit(ReconnectState.Failed(it))
49-
} //reconnection failed - state = failed
50-
.mapNotNull {
51-
state.value = it //set state //TODO replace with Flow.stateIn when coroutines 1.4.0-native-mt will be released
48+
}
49+
.transform { value ->
50+
emit(value) //emit before any action, to pass value directly to state
5251

53-
when (it) {
52+
when (value) {
5453
is ReconnectState.Connected -> {
5554
logger.debug { "Connection established" }
56-
it.rSocket.join() //await for connection completion
55+
value.rSocket.join() //await for connection completion
5756
logger.debug { "Connection closed. Reconnecting..." }
5857
}
59-
is ReconnectState.Failed -> throw it.error //reconnect failed, cancel job
60-
ReconnectState.Connecting -> null //skip, still waiting for new connection
58+
is ReconnectState.Failed -> job.completeExceptionally(value.error) //reconnect failed, fail job
59+
ReconnectState.Connecting -> Unit //skip, still waiting for new connection
6160
}
6261
}
63-
.launchRestarting() //reconnect if old connection completed/failed
62+
.restarting() //reconnect if old connection completed
63+
.stateIn(CoroutineScope(Dispatchers.Unconfined + job))
6464

6565
//await first connection to fail fast if something
6666
state.mapNotNull {
@@ -74,27 +74,16 @@ internal suspend fun ReconnectableRSocket(
7474
return ReconnectableRSocket(job, state)
7575
}
7676

77-
private fun Flow<*>.launchRestarting(): Job = GlobalScope.launch(Dispatchers.Unconfined) {
78-
while (isActive) {
79-
try {
80-
collect()
81-
} catch (e: Throwable) {
82-
// KLUDGE: K/N
83-
cancel("Reconnection failed", e)
84-
break
85-
}
86-
}
87-
}
77+
private fun Flow<ReconnectState>.restarting(): Flow<ReconnectState> = flow { while (true) emitAll(this@restarting) }
8878

8979
private sealed class ReconnectState {
9080
object Connecting : ReconnectState()
9181
data class Failed(val error: Throwable) : ReconnectState()
9282
data class Connected(val rSocket: RSocket) : ReconnectState()
9383
}
9484

95-
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
9685
private class ReconnectableRSocket(
97-
override val job: Job,
86+
override val job: CompletableJob,
9887
private val state: StateFlow<ReconnectState>,
9988
) : RSocket {
10089

0 commit comments

Comments
 (0)