I am doing my first steps with coroutines. I wrote...
# coroutines
l
I am doing my first steps with coroutines. I wrote a simple multi-client echo server using ktor, can someone please review my code and point out potential spaghetti?
Copy code
import io.ktor.network.selector.ActorSelectorManager
import io.ktor.network.sockets.*
import kotlinx.coroutines.*
import <http://kotlinx.coroutines.io|kotlinx.coroutines.io>.*
import <http://java.net|java.net>.InetSocketAddress
import java.nio.channels.ClosedChannelException

suspend fun main() {
	val echoServer = EchoServer()

	supervisorScope {
		launch {
			echoServer.start()
		}

		while (readLine() != "exit") {
			println("unknown command")
		}
		println("stopping server")
		echoServer.stop()
	}
}

class EchoServer {
	private val listener: ServerSocket =
		aSocket(ActorSelectorManager(<http://Dispatchers.IO|Dispatchers.IO>)).tcp().bind(InetSocketAddress(12345))
	private val clients = mutableListOf<Socket>()

	suspend fun start() {
		coroutineScope {
			while (true) {
				val client =
					try {
						listener.accept()
					} catch (_: ClosedChannelException) {
						break
					}
				launch {
					handleClient(client)
				}
			}
		}
	}

	private suspend fun handleClient(client: Socket) {
		println("new client connected")
		clients.add(client)
		val reader = client.openReadChannel()
		val writer = client.openWriteChannel(true)
		while (true) {
			val message = reader.readUTF8Line() ?: run {
				println("client disconnected")
				return
			}
			writer.writeStringUtf8("$message\n")
		}
	}

	fun stop() {
		listener.close()
		clients.forEach {
			it.close()
		}
		clients.clear()
	}
}
o
I suspect (though I'm not sure) that the server will never start, because
suspend fun main()
uses an event loop and is single-threaded iirc. Therefore, since the code after
launch
doesn't suspend, the server can never start.
l
it starts just fine
o
I don't see a definition of
readLine()
though, maybe it suspends and I just can't see it
ah, it's in stdlib. and no it doesn't suspend. I guess I might be wrong about
suspend fun main()
then
still, it's good practice to put functions that block inside
<http://Dispatchers.IO|Dispatchers.IO>
basically wrapping the loop in
supervisorScope
in
withContext(<http://Dispatchers.IO|Dispatchers.IO>)
oh right, the
suspend fun main()
is a language level feature, so it specifies no dispatcher, meaning it uses
Dispatchers.Default
g
No, it doesn't use Default dispatcher, Default dispatcher is a library feature, not a part of stdlib
o
what I meant by that was, when it is time to launch / scope / etc., when you interact with the coroutines library, it will use the default dispatcher
what's not obvious is that you're right, it doesn't start on the default dispatcher, it actually starts on the main thread.
to show what I mean:
Copy code
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch

suspend fun main() {
    println("[M] I'm on ${Thread.currentThread().name}")
    coroutineScope {
        println("[CS] I'm on ${Thread.currentThread().name}")
        launch {
            println("[L] I'm on ${Thread.currentThread().name}")
        }
    }
    println("[M2] I'm on ${Thread.currentThread().name}")
}
->
Copy code
[M] I'm on main
[CS] I'm on main
[L] I'm on DefaultDispatcher-worker-2
[M2] I'm on DefaultDispatcher-worker-2