io.vertx.kotlin.coroutines.ReceiveChannelHandler.kt Maven / Gradle / Ivy
/*
* Copyright 2019 Red Hat, Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.vertx.kotlin.coroutines
import io.vertx.core.Context
import io.vertx.core.Handler
import io.vertx.core.Vertx
import io.vertx.core.streams.ReadStream
import io.vertx.core.streams.WriteStream
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.selects.SelectClause1
import kotlin.coroutines.CoroutineContext
/**
* @author Stream Liu
* @author Julien Viet
* @author [Julien Ponge](https://julien.ponge.org/)
* @author [Guido Pio Mariotti](https://github.com/gmariotti)
*/
/**
* An adapter that converts a stream of events from the [Handler] into a [ReceiveChannel] which allows the events
* to be received synchronously.
*/
class ReceiveChannelHandler(context: Context) : ReceiveChannel, Handler, CoroutineScope {
constructor(vertx: Vertx) : this(vertx.getOrCreateContext())
override val coroutineContext: CoroutineContext = context.dispatcher()
private val channel: Channel = Channel(DEFAULT_CAPACITY)
@ExperimentalCoroutinesApi
override val isClosedForReceive: Boolean
get() = channel.isClosedForReceive
@ExperimentalCoroutinesApi
override val isEmpty: Boolean
get() = channel.isEmpty
override fun iterator(): ChannelIterator {
return channel.iterator()
}
override suspend fun receive(): T {
return channel.receive()
}
override val onReceive: SelectClause1
get() = channel.onReceive
override val onReceiveCatching: SelectClause1>
get() = channel.onReceiveCatching
override fun handle(event: T) {
launch { channel.send(event) }
}
override suspend fun receiveCatching(): ChannelResult {
return channel.receiveCatching()
}
override fun tryReceive(): ChannelResult {
return channel.tryReceive()
}
@ObsoleteCoroutinesApi
@Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x")
override fun cancel(cause: Throwable?): Boolean {
channel.cancel(CancellationException(message = null, cause = cause))
return true
}
override fun cancel(cause: CancellationException?) {
channel.cancel(cause)
}
@Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x")
override fun cancel() {
return channel.cancel()
}
}
/**
* Create a [ReceiveChannelHandler] of some type `T`.
*/
fun Vertx.receiveChannelHandler(): ReceiveChannelHandler = ReceiveChannelHandler(this)
/**
* Adapts the current read stream to Kotlin [ReceiveChannel].
*
* The adapter will fetch at most max channel capacity from the stream and pause it when the channel is full.
*
* @param vertx the related vertx instance
*/
fun ReadStream.toReceiveChannel(vertx: Vertx): ReceiveChannel {
return toReceiveChannel(vertx.orCreateContext)
}
/**
* Adapts the current read stream to Kotlin [ReceiveChannel].
*
* @param context the vertx context
*/
fun ReadStream.toReceiveChannel(context: Context): ReceiveChannel {
this.pause()
val ret = ChannelReadStream(
stream = this,
channel = Channel(0),
context = context
)
ret.subscribe()
this.fetch(1)
return ret
}
private class ChannelReadStream(val stream: ReadStream,
val channel: Channel,
context: Context) : Channel by channel, CoroutineScope {
override val coroutineContext: CoroutineContext = context.dispatcher()
fun subscribe() {
stream.endHandler {
close()
}
stream.exceptionHandler { err ->
close(err)
}
stream.handler { event ->
launch {
send(event)
stream.fetch(1)
}
}
}
}
/**
* Adapts the current write stream to Kotlin [SendChannel].
*
* The channel can be used to write items, the coroutine is suspended when the stream is full
* and resumed when the stream is drained.
*
* @param vertx the related vertx instance
* @param capacity the channel buffering capacity
*/
@ExperimentalCoroutinesApi
fun WriteStream.toSendChannel(vertx: Vertx, capacity: Int = DEFAULT_CAPACITY): SendChannel {
return toSendChannel(vertx.orCreateContext, capacity)
}
/**
* Adapts the current write stream to Kotlin [SendChannel].
*
* The channel can be used to write items, the coroutine is suspended when the stream is full
* and resumed when the stream is drained.
*
* @param context the vertx context
* @param capacity the channel buffering capacity
*/
@ExperimentalCoroutinesApi
fun WriteStream.toSendChannel(context: Context, capacity: Int = DEFAULT_CAPACITY): SendChannel {
val ret = ChannelWriteStream(
stream = this,
channel = Channel(capacity),
context = context
)
ret.subscribe()
return ret
}
private class ChannelWriteStream(val stream: WriteStream,
val channel: Channel,
context: Context) : Channel by channel, CoroutineScope {
override val coroutineContext: CoroutineContext = context.dispatcher()
@ExperimentalCoroutinesApi
fun subscribe() {
stream.exceptionHandler {
channel.close(it)
}
launch {
while (true) {
val res = channel.receiveCatching()
if (res.isSuccess) {
val elt = res.getOrNull()
if (stream.writeQueueFull()) {
stream.drainHandler {
if (dispatch(elt)) {
subscribe()
}
}
break
} else {
if (!dispatch(elt)) {
break
}
}
} else if (res.isClosed) {
stream.end()
break
} else {
// Can it happen?
}
}
}
}
fun dispatch(elt: T?): Boolean {
return if (elt != null) {
stream.write(elt)
true
} else {
close()
false
}
}
override fun close(cause: Throwable?): Boolean {
return channel.close(cause)
}
}
private const val DEFAULT_CAPACITY = 16
© 2015 - 2024 Weber Informatics LLC | Privacy Policy