Use blocking access to solve ZMQ performance issues.
This commit is contained in:
parent
6e01e28015
commit
d0f22eec93
@ -1,21 +1,21 @@
|
|||||||
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.*
|
||||||
import kotlinx.serialization.json.JsonObjectBuilder
|
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
|
||||||
import kotlinx.serialization.json.put
|
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import ru.mipt.npm.magix.api.MagixEndpoint
|
import ru.mipt.npm.magix.api.MagixEndpoint
|
||||||
import ru.mipt.npm.magix.api.MagixMessage
|
import ru.mipt.npm.magix.api.MagixMessage
|
||||||
import ru.mipt.npm.magix.server.startMagixServer
|
import ru.mipt.npm.magix.server.startMagixServer
|
||||||
import ru.mipt.npm.magix.zmq.ZmqMagixEndpoint
|
import ru.mipt.npm.magix.zmq.ZmqMagixEndpoint
|
||||||
|
import java.awt.Desktop
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
|
||||||
suspend fun MagixEndpoint<JsonElement>.sendJson(
|
suspend fun MagixEndpoint<JsonObject>.sendJson(
|
||||||
origin: String,
|
origin: String,
|
||||||
format: String = "json",
|
format: String = "json",
|
||||||
target: String? = null,
|
target: String? = null,
|
||||||
@ -25,33 +25,44 @@ suspend fun MagixEndpoint<JsonElement>.sendJson(
|
|||||||
builder: JsonObjectBuilder.() -> Unit
|
builder: JsonObjectBuilder.() -> Unit
|
||||||
): Unit = broadcast(MagixMessage(format, origin, buildJsonObject(builder), target, id, parentId, user))
|
): Unit = broadcast(MagixMessage(format, origin, buildJsonObject(builder), target, id, parentId, user))
|
||||||
|
|
||||||
|
internal const val numberOfMessages = 100
|
||||||
|
|
||||||
suspend fun main(): Unit = coroutineScope {
|
suspend fun main(): Unit = coroutineScope {
|
||||||
val logger = LoggerFactory.getLogger("magix-demo")
|
val logger = LoggerFactory.getLogger("magix-demo")
|
||||||
logger.info("Starting magix server")
|
logger.info("Starting magix server")
|
||||||
val server = startMagixServer(enableRawRSocket = false)
|
val server = startMagixServer(
|
||||||
logger.info("Waiting for server to start")
|
buffer = 10,
|
||||||
delay(2000)
|
enableRawRSocket = false //Disable rsocket to avoid kotlin 1.5 compatibility issue
|
||||||
|
)
|
||||||
|
|
||||||
|
server.apply {
|
||||||
|
val host = "localhost"//environment.connectors.first().host
|
||||||
|
val port = environment.connectors.first().port
|
||||||
|
val uri = URI("http", null, host, port, "/state", null, null)
|
||||||
|
Desktop.getDesktop().browse(uri)
|
||||||
|
}
|
||||||
|
|
||||||
logger.info("Starting client")
|
logger.info("Starting client")
|
||||||
ZmqMagixEndpoint("tcp://localhost", JsonElement.serializer()).use { client ->
|
//Create zmq magix endpoint and wait for to finish
|
||||||
|
ZmqMagixEndpoint("tcp://localhost", JsonObject.serializer()).use { client ->
|
||||||
logger.info("Starting subscription")
|
logger.info("Starting subscription")
|
||||||
try {
|
|
||||||
client.subscribe().onEach {
|
client.subscribe().onEach {
|
||||||
println(it.payload)
|
println(it.payload)
|
||||||
}.catch { it.printStackTrace() }.launchIn(this)
|
if (it.payload["index"]?.jsonPrimitive?.int == numberOfMessages) {
|
||||||
} catch (t: Throwable) {
|
logger.info("Index $numberOfMessages reached. Terminating")
|
||||||
t.printStackTrace()
|
cancel()
|
||||||
throw t
|
|
||||||
}
|
}
|
||||||
|
}.catch { it.printStackTrace() }.launchIn(this)
|
||||||
|
|
||||||
|
|
||||||
var counter = 0
|
var counter = 0
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
delay(500)
|
delay(500)
|
||||||
logger.info("Sending message number ${counter + 1}")
|
val index = (counter++).toString()
|
||||||
client.sendJson("magix-demo") {
|
logger.info("Sending message number $index")
|
||||||
|
client.sendJson("magix-demo", id = index) {
|
||||||
put("message", "Hello world!")
|
put("message", "Hello world!")
|
||||||
put("index", counter++)
|
put("index", index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,6 +84,9 @@ private fun ApplicationCall.buildFilter(): MagixMessageFilter {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attache magix http/sse and websocket-based rsocket event loop + statistics page to existing [MutableSharedFlow]
|
||||||
|
*/
|
||||||
public fun Application.magixModule(magixFlow: MutableSharedFlow<GenericMagixMessage>, route: String = "/") {
|
public fun Application.magixModule(magixFlow: MutableSharedFlow<GenericMagixMessage>, route: String = "/") {
|
||||||
if (featureOrNull(WebSockets) == null) {
|
if (featureOrNull(WebSockets) == null) {
|
||||||
install(WebSockets)
|
install(WebSockets)
|
||||||
@ -107,11 +110,7 @@ public fun Application.magixModule(magixFlow: MutableSharedFlow<GenericMagixMess
|
|||||||
|
|
||||||
routing {
|
routing {
|
||||||
route(route) {
|
route(route) {
|
||||||
post {
|
get("state") {
|
||||||
val message = call.receive<GenericMagixMessage>()
|
|
||||||
magixFlow.emit(message)
|
|
||||||
}
|
|
||||||
get("loop-state") {
|
|
||||||
call.respondHtml {
|
call.respondHtml {
|
||||||
body {
|
body {
|
||||||
h1 { +"Magix loop statistics" }
|
h1 { +"Magix loop statistics" }
|
||||||
@ -140,12 +139,19 @@ public fun Application.magixModule(magixFlow: MutableSharedFlow<GenericMagixMess
|
|||||||
}
|
}
|
||||||
call.respondSse(sseFlow)
|
call.respondSse(sseFlow)
|
||||||
}
|
}
|
||||||
|
post("broadcast") {
|
||||||
|
val message = call.receive<GenericMagixMessage>()
|
||||||
|
magixFlow.emit(message)
|
||||||
|
}
|
||||||
//rSocket server. Filter from Payload
|
//rSocket server. Filter from Payload
|
||||||
rSocket("rsocket", acceptor = magixAcceptor(magixFlow))
|
rSocket("rsocket", acceptor = magixAcceptor(magixFlow))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new loop [MutableSharedFlow] with given [buffer] and setup magix module based on it
|
||||||
|
*/
|
||||||
public fun Application.magixModule(route: String = "/", buffer: Int = 100) {
|
public fun Application.magixModule(route: String = "/", buffer: Int = 100) {
|
||||||
val magixFlow = MutableSharedFlow<GenericMagixMessage>(buffer)
|
val magixFlow = MutableSharedFlow<GenericMagixMessage>(buffer)
|
||||||
magixModule(magixFlow, route)
|
magixModule(magixFlow, route)
|
||||||
|
@ -57,10 +57,16 @@ public fun CoroutineScope.startMagixServer(
|
|||||||
val zmqPubSocketPort: Int = MagixEndpoint.DEFAULT_MAGIX_ZMQ_PUB_PORT
|
val zmqPubSocketPort: Int = MagixEndpoint.DEFAULT_MAGIX_ZMQ_PUB_PORT
|
||||||
val zmqPullSocketPort: Int = MagixEndpoint.DEFAULT_MAGIX_ZMQ_PULL_PORT
|
val zmqPullSocketPort: Int = MagixEndpoint.DEFAULT_MAGIX_ZMQ_PULL_PORT
|
||||||
logger.info("Starting magix zmq server on pub port $zmqPubSocketPort and pull port $zmqPullSocketPort")
|
logger.info("Starting magix zmq server on pub port $zmqPubSocketPort and pull port $zmqPullSocketPort")
|
||||||
launchMagixServerZmqSocket(magixFlow, zmqPubSocketPort = zmqPubSocketPort, zmqPullSocketPort = zmqPullSocketPort)
|
launchMagixServerZmqSocket(
|
||||||
|
magixFlow,
|
||||||
|
zmqPubSocketPort = zmqPubSocketPort,
|
||||||
|
zmqPullSocketPort = zmqPullSocketPort
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return embeddedServer(CIO, port = port) {
|
return embeddedServer(CIO, host = "localhost", port = port) {
|
||||||
magixModule(magixFlow)
|
magixModule(magixFlow)
|
||||||
|
}.apply {
|
||||||
|
start()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,12 +1,9 @@
|
|||||||
package ru.mipt.npm.magix.server
|
package ru.mipt.npm.magix.server
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.isActive
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.zeromq.SocketType
|
import org.zeromq.SocketType
|
||||||
import org.zeromq.ZContext
|
import org.zeromq.ZContext
|
||||||
@ -17,7 +14,7 @@ public fun CoroutineScope.launchMagixServerZmqSocket(
|
|||||||
localHost: String = "tcp://*",
|
localHost: String = "tcp://*",
|
||||||
zmqPubSocketPort: Int = MagixEndpoint.DEFAULT_MAGIX_ZMQ_PUB_PORT,
|
zmqPubSocketPort: Int = MagixEndpoint.DEFAULT_MAGIX_ZMQ_PUB_PORT,
|
||||||
zmqPullSocketPort: Int = MagixEndpoint.DEFAULT_MAGIX_ZMQ_PULL_PORT,
|
zmqPullSocketPort: Int = MagixEndpoint.DEFAULT_MAGIX_ZMQ_PULL_PORT,
|
||||||
): Job = launch {
|
): Job = launch(Dispatchers.IO) {
|
||||||
val logger = LoggerFactory.getLogger("magix-server-zmq")
|
val logger = LoggerFactory.getLogger("magix-server-zmq")
|
||||||
|
|
||||||
ZContext().use { context ->
|
ZContext().use { context ->
|
||||||
@ -33,11 +30,10 @@ public fun CoroutineScope.launchMagixServerZmqSocket(
|
|||||||
//launch pulling job
|
//launch pulling job
|
||||||
val pullSocket = context.createSocket(SocketType.PULL)
|
val pullSocket = context.createSocket(SocketType.PULL)
|
||||||
pullSocket.bind("$localHost:$zmqPullSocketPort")
|
pullSocket.bind("$localHost:$zmqPullSocketPort")
|
||||||
|
pullSocket.receiveTimeOut = 500
|
||||||
//suspending loop while pulling is active
|
//suspending loop while pulling is active
|
||||||
|
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
//This is a blocking call.
|
val string: String? = pullSocket.recvStr()
|
||||||
val string: String? = pullSocket.recvStr(zmq.ZMQ.ZMQ_DONTWAIT)
|
|
||||||
if (string != null) {
|
if (string != null) {
|
||||||
logger.debug("Received: $string")
|
logger.debug("Received: $string")
|
||||||
val message = MagixEndpoint.magixJson.decodeFromString(genericMessageSerializer, string)
|
val message = MagixEndpoint.magixJson.decodeFromString(genericMessageSerializer, string)
|
||||||
@ -46,3 +42,4 @@ public fun CoroutineScope.launchMagixServerZmqSocket(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ public class ZmqMagixEndpoint<T>(
|
|||||||
while (activeFlag) {
|
while (activeFlag) {
|
||||||
try {
|
try {
|
||||||
//This is a blocking call.
|
//This is a blocking call.
|
||||||
val string: String? = socket.recvStr(zmq.ZMQ.ZMQ_DONTWAIT)
|
val string: String? = socket.recvStr()
|
||||||
if (string != null) {
|
if (string != null) {
|
||||||
val message = MagixEndpoint.magixJson.decodeFromString(serializer, string)
|
val message = MagixEndpoint.magixJson.decodeFromString(serializer, string)
|
||||||
send(message)
|
send(message)
|
||||||
|
Loading…
Reference in New Issue
Block a user