https://kotlinlang.org logo
#coroutines
Title
# coroutines
l

Lost Illusion

11/25/2023, 11:17 AM
I'm using Kotlin 1.8.20, openjdk 17. I've been working on a server in Windows, it listens to sockets. Once I took it over to Linux on my VPS (1 vcore), it did not seem to get to the listening logic. I was able to break the problem down to this: Example A:
Copy code
launch {
  launch { println("b") }
  delay(5000}
}
Same result on Windows and the VPS. B is printed pretty much instantly. Example B:
Copy code
launch {
  delay(1)
  launch { println("b") }
  delay(5000)
}
Output is pretty much the same as Example A on Windows. On the VPS, it takes about 5 seconds (or until it gets to the end of the block?) for B to be printed. This becomes a problem when the delay(1) and inner-launch is replaced with actual logic, and inside of a loop... the inner launches will never start as it waits until the end of the block. Any ideas?
s

Sam

11/25/2023, 12:32 PM
When you repro the problem with this code, how do you call it? What scope and dispatcher are you using? I don't see anything in the code you shared so far that would account for the behaviour you're seeing 🤔
d

Daniel Pitts

11/25/2023, 3:42 PM
It's possible the "actual logic" has some blocking behavior. This is forbidden for coroutines.
e

ephemient

11/25/2023, 4:35 PM
"forbidden" isn't quite right; it's expected that some things you
launch(<http://Dispatchers.IO|Dispatchers.IO>)
may be blocking
but yes, dispatch order isn't guaranteed and depending on the dispatcher, it may not be multithreaded
l

Lost Illusion

11/25/2023, 7:32 PM
i can't get the original examples to cause the problem again (or haven't tried it enough), but i've been working back to it, dumbing it down a bit more for a reproducible example and I've found out I'm pretty sure it's just random when it occurs. Here's where I know it happened for sure, but I've gotten it down to examples similar to B where it has happened again, I just didn't save them. If the issue occurs, B won't print until after a socket connection was accepted.
Copy code
suspend fun main(): Unit = coroutineScope {
    run()
}

suspend fun run() = coroutineScope {
    val socket = javax.net.ServerSocketFactory.getDefault().createServerSocket(143)

    println("start ${System.currentTimeMillis()}")

    launch {
        delay(1)

        launch {
            println("b ${System.currentTimeMillis()}")
        }

        socket.accept()
    }
}
should I be wrapping the socket.accept() with a different context? this is how the code looks like not dumbed-down (minus abstractions) since it doesnt seem like there's a specific part causing it, rather a random thing. Here the launch block is never called.
Copy code
coroutineScope {
    while (isActive) {
        val transport = socket.accept()
        launch { handle(transport) }
    }
}
I tried using Dispatchers.IO for that and it seemed to work 🙂. Is there a simple reason in the difference between Windows and the VPS? Also, it seems that the problem just happens later on now. What exactly should be getting ran with Dispatchers.IO? I think I have updated all socket read/writes + accepting. Java Sockets should be the only blocking API in my code.
a

ashmelev

11/25/2023, 9:54 PM
In Linux ports below 1024 require admin (i.e. root) privileges. If you are launching your app as a non-root user you will not have rights to open the port
l

Lost Illusion

11/25/2023, 10:21 PM
I launch it as root, and the launch does get launched maybe the first time? eventually they stop launching. I don't think it's an issue of the sockets being open, it's just that the launch blocks don't get executed until the end of the parent block.
a

ashmelev

11/25/2023, 10:38 PM
In that case, I think what you are looking for is a "channel" not a Socket. Something like :
Copy code
val serverSocket: Future<AsynchronousSocketChannel> = AsynchronousServerSocketChannel.open().bind(InetSocketAddress(123)).accept()
That returns a Future and should not block
Be aware that, while a channel can be read/written (shared) by multiple threads, only one thread can read/write at any given time. You are responsible for the mutex handling
l

Lost Illusion

11/25/2023, 11:05 PM
Is there any reason why there's this difference between the vps and my windows computer. I even ran it through docker on Windows, so it runs through wsl. it's a pretty simple protocol, command-response. There shouldn't be any reading or writing at the same time by different threads. I don't see how that causes the launch block to not be executed at all either, unless this is an alternative solution?
I will be able to try the channel thing later.
e

ephemient

11/26/2023, 1:42 AM
number of cores impacts number of threads in Dispatchers.Default which may make certain outcomes more or less likely
but your blocking code should always have been on Dispatchers.IO regardless
l

Lost Illusion

11/26/2023, 3:21 PM
fair, seems that everything has worked itself out since switching to Dispatchers.IO. thanks 🙂