I'm trying to use an object (singleton) to share s...
# ktor
m
I'm trying to use an object (singleton) to share state between a JavaFX application and a ktor netty embedded server. Two instances of the singleton are being created: one when the netty server accesses the "singleton" and one when the main application accesses it. Can you help me understand why that is happening?
e
Hey @melatonina, could you tell me if development mode is enabled?
m
I'll report back in a few minutes with the information.
b
Is your ktor server part of the javaFX application?
m
Ok. I can't understand what is "development mode" by googling. What I can say is that I didn't set "development = true" in the HOCON configuration file, anywhere. On the other end, I'm starting netty with
Copy code
io.ktor.server.netty.EngineMain.main(emptyArray())
which are classified as "development engines" in the ktor documentation.
e
Could you check the log during application start? It should explicitly mention if the development mode is enabled
m
@Big Chungus Yes, it's part of the JavaFX application. I'd like to use ktor to let the JavaFX application and the Android application communicate during development.
@e5l I'll check that. 1 minute.
@e5l No, there are no instances of the string "development mode" in the log.
e
And what is your version of Ktor?
m
Logging level is configured to INFO:
Copy code
<logger name="io.netty" level="INFO"/>
Ktor version is 1.5.1:
Copy code
ktor_version=1.5.1
e
Could you file an issue with reproducer on YT? I will take a look
m
Ok. I'll try and make a MCVE.
Thanks
e
Thank you for the feedback!
m
The following is a preview of the MCVE, in case I'm doing something so wrong that it's evident by looking at the source code.
Copy code
import java.util.*

import kotlin.collections.LinkedHashSet

import io.ktor.application.*
import io.ktor.http.cio.websocket.*
import io.ktor.routing.*
import io.ktor.websocket.*

import io.ktor.application.Application

fun Application.module() {
    install(WebSockets)
    routing {
        webSocket("/ktor_fx") {
            val connection = Connection(this)

            KtorFxService.connections += connection

            try {
                connection.send("You are connected!")
                for(frame in incoming) {
                    if (frame is Frame.Text) {
                        val receivedText = frame.readText()

                        connection.send("You said: \"$receivedText\"")
                        connection.send("${KtorFxService::class.simpleName}.connectionCount = ${KtorFxService.connectionCount}")
                    }
                }
            } finally {
                KtorFxService.connections -= connection
            }
        }
    }
}

class Connection(
    val session: DefaultWebSocketServerSession
) {
    suspend fun send(text: String) {
        session.send(text)
    }
}

object KtorFxService {
    init {
        println("Creating a new ${KtorFxService::class.simpleName}")
    }
    val connections = Collections.synchronizedSet<Connection>(LinkedHashSet())
    val connectionCount get() = connections.size

    suspend fun broadcast(text: String) {
        println("Broadcasting \"$text\" to ${connections.size} clients")
        // We should synchronize(connection) but we can't because send is a suspension point
        // and can't be inside a synchronize block.
        // This is another issue
        connections.forEach { it.send(text) }
    }
}
Copy code
import kotlinx.coroutines.*
import kotlinx.coroutines.javafx.JavaFx

import javafx.application.Application
import javafx.event.EventHandler
import javafx.scene.Scene
import javafx.scene.control.Button
import javafx.scene.layout.VBox
import javafx.stage.Stage
import kotlin.random.Random

class KtorFxApplication : Application() {
    val coroutineScope = CoroutineScope(Dispatchers.JavaFx)

    override fun start(primaryStage: Stage) {
        primaryStage.apply {
            title = "${KtorFxApplication::class.simpleName}"
            scene = Scene(
                VBox().apply {
                    children.setAll(
                        Button().apply {
                            text = "Start service"
                            onAction = EventHandler {
                                coroutineScope.launch(<http://Dispatchers.IO|Dispatchers.IO>) {
                                    io.ktor.server.netty.EngineMain.main(emptyArray())
                                }
                            }
                        },
                        Button().apply {
                            text = "Broadcast something"
                            onAction = EventHandler {
                                coroutineScope.launch(<http://Dispatchers.IO|Dispatchers.IO>) {
                                    KtorFxService.broadcast("${Random.nextInt()}")
                                }
                            }
                        }
                    )
                },
                300.0, 250.0
            )
            show()
        }
    }
    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            launch(KtorFxApplication::class.java, *args)
        }
    }
}
Connecting to the service with https://www.websocket.org/echo.html I get:
Copy code
CONNECTED
RECEIVED: You are connected!
SENT: Hi!
RECEIVED: You said: "Hi!"
RECEIVED: KtorFxService.connectionCount = 1
When I try to broadcast something, the console says:
Copy code
Creating a new KtorFxService
Creating a new KtorFxService
Broadcasting "-1198330446" to 0 clients
The first creation of KtorFxService happens when I connect a client to the service. The second happens when I attempt to broadcast.
I'll create the issue with a link to a github repository for the MCVE in a few hours.