Skip to content

Commit

Permalink
implement Local multiplexed transport
Browse files Browse the repository at this point in the history
  • Loading branch information
whyoleg committed Mar 13, 2024
1 parent bc2c384 commit f917dc0
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 69 deletions.
5 changes: 4 additions & 1 deletion rsocket-transports/local/api/rsocket-transport-local.api
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,11 @@ public final class io/rsocket/kotlin/transport/local/LocalServerTransport$Factor
}

public abstract interface class io/rsocket/kotlin/transport/local/LocalServerTransportBuilder : io/rsocket/kotlin/transport/RSocketTransportBuilder {
public abstract fun connectionBufferCapacity (I)V
public abstract fun dispatcher (Lkotlin/coroutines/CoroutineContext;)V
public fun inheritDispatcher ()V
public abstract fun multiplexed (II)V
public static synthetic fun multiplexed$default (Lio/rsocket/kotlin/transport/local/LocalServerTransportBuilder;IIILjava/lang/Object;)V
public abstract fun sequential (I)V
public static synthetic fun sequential$default (Lio/rsocket/kotlin/transport/local/LocalServerTransportBuilder;IILjava/lang/Object;)V
}

Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,5 @@ private class LocalClientTargetImpl(
) : LocalClientTarget {

@RSocketTransportApi
override suspend fun createSession(): RSocketTransportSession {
ensureActive()

return server.connect(coroutineContext)
}
override suspend fun createSession(): RSocketTransportSession = server.connect(this)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/*
* Copyright 2015-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.rsocket.kotlin.transport.local

import io.ktor.utils.io.core.*
import io.rsocket.kotlin.internal.io.*
import io.rsocket.kotlin.transport.*
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
import kotlin.coroutines.*

internal sealed class LocalServerConnector {
@RSocketTransportApi
abstract fun connect(
acceptor: RSocketServerAcceptor,
clientScope: CoroutineScope,
serverScope: CoroutineScope,
): RSocketTransportSession

class Sequential(
private val connectionBufferCapacity: Int,
) : LocalServerConnector() {
@RSocketTransportApi
override fun connect(
acceptor: RSocketServerAcceptor,
clientScope: CoroutineScope,
serverScope: CoroutineScope,
): RSocketTransportSession {
clientScope.ensureActive()
serverScope.ensureActive()

val clientToServer = channelForCloseable<ByteReadPacket>(connectionBufferCapacity)
val serverToClient = channelForCloseable<ByteReadPacket>(connectionBufferCapacity)

serverScope.launch {
acceptor.acceptSession(
Session(
coroutineContext = coroutineContext.childContext(),
outbound = serverToClient,
inbound = clientToServer
)
)
}

return Session(
coroutineContext = clientScope.coroutineContext.childContext(),
outbound = clientToServer,
inbound = serverToClient
)
}


@RSocketTransportApi
private class Session(
override val coroutineContext: CoroutineContext,
private val outbound: SendChannel<ByteReadPacket>,
private val inbound: ReceiveChannel<ByteReadPacket>,
) : RSocketTransportSession.Sequential {

init {
coroutineContext.job.invokeOnCompletion {
outbound.close(it)
inbound.cancel(CancellationException("Local connection closed", it))
}
}

override suspend fun sendFrame(frame: ByteReadPacket) {
outbound.send(frame)
}

override suspend fun receiveFrame(): ByteReadPacket {
return inbound.receive()
}
}

}

class Multiplexed(
private val connectionStreamsQueueCapacity: Int,
private val streamBufferCapacity: Int,
) : LocalServerConnector() {
@RSocketTransportApi
override fun connect(
acceptor: RSocketServerAcceptor,
clientScope: CoroutineScope,
serverScope: CoroutineScope,
): RSocketTransportSession {
clientScope.ensureActive()
serverScope.ensureActive()

val clientToServer = channelForCloseable<Stream>(connectionStreamsQueueCapacity)
val serverToClient = channelForCloseable<Stream>(connectionStreamsQueueCapacity)

serverScope.launch {
acceptor.acceptSession(
Session(
coroutineContext = coroutineContext.childContext(),
outbound = serverToClient,
inbound = clientToServer,
streamBufferCapacity = streamBufferCapacity
)
)
}

return Session(
coroutineContext = clientScope.coroutineContext.childContext(),
outbound = clientToServer,
inbound = serverToClient,
streamBufferCapacity = streamBufferCapacity
)
}

@RSocketTransportApi
private class Session(
override val coroutineContext: CoroutineContext,
private val outbound: SendChannel<Stream>,
private val inbound: ReceiveChannel<Stream>,
private val streamBufferCapacity: Int,
) : RSocketTransportSession.Multiplexed {
private val streamsContext = coroutineContext.supervisorContext()

init {
coroutineContext.job.invokeOnCompletion {
outbound.close(it)
inbound.cancel(CancellationException("Local connection closed", it))
}
}

override suspend fun createStream(): RSocketTransportSession.Multiplexed.Stream {
val clientToServer = channelForCloseable<ByteReadPacket>(streamBufferCapacity)
val serverToClient = channelForCloseable<ByteReadPacket>(streamBufferCapacity)

outbound.send(
Stream(
coroutineContext = streamsContext.childContext(),
outbound = serverToClient,
inbound = clientToServer
)
)

return Stream(
coroutineContext = streamsContext.childContext(),
outbound = clientToServer,
inbound = serverToClient
)
}

override suspend fun awaitStream(): RSocketTransportSession.Multiplexed.Stream {
return inbound.receive()
}
}

@RSocketTransportApi
private class Stream(
override val coroutineContext: CoroutineContext,
private val outbound: SendChannel<ByteReadPacket>,
private val inbound: ReceiveChannel<ByteReadPacket>,
) : RSocketTransportSession.Multiplexed.Stream, Closeable {

init {
coroutineContext.job.invokeOnCompletion {
outbound.close(it)
inbound.cancel(CancellationException("Local stream closed", it))
}
}

override fun updatePriority(value: Int) {} // no effect

override suspend fun sendFrame(frame: ByteReadPacket) {
outbound.send(frame)
}

override suspend fun receiveFrame(): ByteReadPacket {
return inbound.receive()
}

override fun close() {
cancel("Stream is closed") // just for undelivered element case
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,17 @@

package io.rsocket.kotlin.transport.local

import io.ktor.utils.io.core.*
import io.rsocket.kotlin.internal.io.*
import io.rsocket.kotlin.transport.*
import kotlinx.atomicfu.locks.*
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
import kotlin.coroutines.*
import kotlin.random.*

internal class LocalServerInstanceImpl @RSocketTransportApi constructor(
override val serverName: String,
override val coroutineContext: CoroutineContext,
private val connectionBufferCapacity: Int,
private val acceptor: RSocketServerAcceptor,
private val connector: LocalServerConnector,
) : LocalServerInstance {

init {
Expand All @@ -38,31 +35,10 @@ internal class LocalServerInstanceImpl @RSocketTransportApi constructor(
}

@RSocketTransportApi
override suspend fun createSession(): RSocketTransportSession = connect(coroutineContext)
override suspend fun createSession(): RSocketTransportSession = connect(this)

@RSocketTransportApi
fun connect(clientContext: CoroutineContext): RSocketTransportSession {
ensureActive()

val clientToServer = channelForCloseable<ByteReadPacket>(connectionBufferCapacity)
val serverToClient = channelForCloseable<ByteReadPacket>(connectionBufferCapacity)

launch {
acceptor.acceptSession(
LocalSequentialSession(
coroutineContext = coroutineContext.childContext(),
outbound = serverToClient,
inbound = clientToServer
)
)
}

return LocalSequentialSession(
coroutineContext = clientContext.childContext(),
outbound = clientToServer,
inbound = serverToClient
)
}
fun connect(clientScope: CoroutineScope): RSocketTransportSession = connector.connect(acceptor, clientScope, this)

companion object {
private val lock = SynchronizedObject()
Expand All @@ -85,26 +61,3 @@ internal class LocalServerInstanceImpl @RSocketTransportApi constructor(
fun randomName(): String = Random.nextBytes(16).toHexString(HexFormat.UpperCase)
}
}

@RSocketTransportApi
private class LocalSequentialSession(
override val coroutineContext: CoroutineContext,
private val outbound: SendChannel<ByteReadPacket>,
private val inbound: ReceiveChannel<ByteReadPacket>,
) : RSocketTransportSession.Sequential {

init {
coroutineContext.job.invokeOnCompletion {
outbound.close(it)
inbound.cancel(CancellationException("Local connection closed", it))
}
}

override suspend fun sendFrame(frame: ByteReadPacket) {
outbound.send(frame)
}

override suspend fun receiveFrame(): ByteReadPacket {
return inbound.receive()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package io.rsocket.kotlin.transport.local
import io.rsocket.kotlin.internal.io.*
import io.rsocket.kotlin.transport.*
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
import kotlin.coroutines.*

// TODO: rename to inprocess and more to another module/package later
Expand All @@ -46,44 +45,50 @@ public sealed interface LocalServerTransportBuilder : RSocketTransportBuilder<

public fun dispatcher(context: CoroutineContext)
public fun inheritDispatcher(): Unit = dispatcher(EmptyCoroutineContext)
public fun connectionBufferCapacity(capacity: Int)

public fun sequential(connectionBufferCapacity: Int = 64)
public fun multiplexed(connectionStreamsQueueCapacity: Int = 64, streamBufferCapacity: Int = 64)
}

private class LocalServerTransportBuilderImpl : LocalServerTransportBuilder {
private var dispatcher: CoroutineContext = Dispatchers.Default
private var connectionBufferCapacity: Int = Channel.BUFFERED
private var connector: LocalServerConnector? = null

override fun dispatcher(context: CoroutineContext) {
check(context[Job] == null) { "Dispatcher shouldn't contain job" }
this.dispatcher = context
}

override fun connectionBufferCapacity(capacity: Int) {
this.connectionBufferCapacity = capacity
override fun sequential(connectionBufferCapacity: Int) {
connector = LocalServerConnector.Sequential(connectionBufferCapacity)
}

override fun multiplexed(connectionStreamsQueueCapacity: Int, streamBufferCapacity: Int) {
connector = LocalServerConnector.Multiplexed(connectionStreamsQueueCapacity, streamBufferCapacity)
}

@RSocketTransportApi
override fun buildTransport(context: CoroutineContext): LocalServerTransport = LocalServerTransportImpl(
coroutineContext = context.supervisorContext() + dispatcher,
connectionBufferCapacity = connectionBufferCapacity
connector = connector ?: LocalServerConnector.Sequential(64) // TODO: what is default?
)
}

private class LocalServerTransportImpl(
override val coroutineContext: CoroutineContext,
private val connectionBufferCapacity: Int,
private val connector: LocalServerConnector,
) : LocalServerTransport {
override fun target(address: String): LocalServerTarget = LocalServerTargetImpl(
serverName = address,
coroutineContext = coroutineContext.supervisorContext(),
connectionBufferCapacity = connectionBufferCapacity
connector = connector
)
}

private class LocalServerTargetImpl(
override val serverName: String,
override val coroutineContext: CoroutineContext,
private val connectionBufferCapacity: Int,
private val connector: LocalServerConnector,
) : LocalServerTarget {
@RSocketTransportApi
override suspend fun startServer(acceptor: RSocketServerAcceptor): LocalServerInstance {
Expand All @@ -92,8 +97,8 @@ private class LocalServerTargetImpl(
return LocalServerInstanceImpl(
serverName = serverName,
coroutineContext = coroutineContext.supervisorContext(),
connectionBufferCapacity = connectionBufferCapacity,
acceptor = acceptor
acceptor = acceptor,
connector = connector
)
}
}
Loading

0 comments on commit f917dc0

Please sign in to comment.