How to use structured concurrency correctly with blocking IO code?

I try to use Kotlin for a P2P application and started with the basic structure for a network node with a server and a connection class for handling the sending and receiving of messages.

I need to use blocking IO as my use case it to use Tor and non-blocking IO does not support SocksProxy.

I tried to stick with the concepts of structured concurrency as far as I understand it (being a Kotlin beginner) but the only way I get my code running without getting blocked is by using CoroutineScope(Dispatchers.IO).launch {...}.

As far I understand my current version would require manual resource management, as if the parent scope terminates my standalone scopes would not get canceled.

Beside the usage in the blocking IO functions I also did not find a way how to use the existing scope for the channel receivers. Also here it only works by using a standalone scope.
I guess the problem here is that the sender at that channel is the blocking IO scope already running in a standalone scope…

Hope to deal with such a setup in the right way?
Any help/feedback very appreciated! Thanks in advance!!

My code is posted here:

https://play.kotlinlang.org/#eyJ2ZXJzaW9uIjoiMS4zLjExIiwicGxhdGZvcm0iOiJqYXZhIiwiYXJncyI6IiIsIm5vbmVNYXJrZXJzIjp0cnVlLCJ0aGVtZSI6ImlkZWEiLCJmb2xkZWRCdXR0b24iOnRydWUsInJlYWRPbmx5IjpmYWxzZSwiY29kZSI6ImltcG9ydCBrb3RsaW54LmNvcm91dGluZXMuKlxuaW1wb3J0IGtvdGxpbnguY29yb3V0aW5lcy5jaGFubmVscy5DaGFubmVsXG5pbXBvcnQga290bGlueC5jb3JvdXRpbmVzLmNoYW5uZWxzLmNvbnN1bWVFYWNoXG5pbXBvcnQgamF2YS5pby5PYmplY3RJbnB1dFN0cmVhbVxuaW1wb3J0IGphdmEuaW8uT2JqZWN0T3V0cHV0U3RyZWFtXG5pbXBvcnQgamF2YS5uZXQuU2VydmVyU29ja2V0XG5pbXBvcnQgamF2YS5uZXQuU29ja2V0XG5cbmZ1biBtYWluKCkge1xuICAgIHJ1bkJsb2NraW5nIHtcbiAgICAgICAgdmFsIG5vZGUxID0gTm9kZShTb2NrZXRGYWN0b3J5KCkpXG4gICAgICAgIG5vZGUxLnN0YXJ0U2VydmVyKDExMTEpXG5cbiAgICAgICAgdmFsIG5vZGUyID0gTm9kZShTb2NrZXRGYWN0b3J5KCkpXG4gICAgICAgIG5vZGUyLnN0YXJ0U2VydmVyKDIyMjIpXG5cbiAgICAgICAgdmFsIGFkZHJlc3MgPSBBZGRyZXNzLmxvY2FsaG9zdCgxMTExKVxuICAgICAgICBub2RlMi5jb25uZWN0KGFkZHJlc3MpXG4gICAgICAgIG5vZGUyLnNlbmQoYWRkcmVzcywgXCJNZXNzYWdlIGZyb20gbG9jYWxob3N0OjIyMjIgdG8gbG9jYWxob3N0OjExMTFcIilcblxuICAgICAgICBub2RlMS5zaHV0ZG93bigpXG4gICAgICAgIG5vZGUyLnNodXRkb3duKClcbiAgICB9XG59XG5cbmNsYXNzIE5vZGUodmFsIHNvY2tldEZhY3Rvcnk6IFNvY2tldEZhY3RvcnkpIHtcbiAgICBwcml2YXRlIHZhbCBpbmJvdW5kQ29ubmVjdGlvbnM6IE11dGFibGVMaXN0PENvbm5lY3Rpb24+ID0gbXV0YWJsZUxpc3RPZigpXG4gICAgcHJpdmF0ZSB2YWwgb3V0Ym91bmRDb25uZWN0aW9uczogSGFzaE1hcDxBZGRyZXNzLCBDb25uZWN0aW9uPiA9IGhhc2hNYXBPZigpXG4gICAgcHJpdmF0ZSB2YXIgc2VydmVyOiBTZXJ2ZXI/ID0gbnVsbFxuXG4gICAgc3VzcGVuZCBmdW4gc3RhcnRTZXJ2ZXIoc2VydmVyUG9ydDogSW50KSB7XG4gICAgICAgIGNvcm91dGluZVNjb3BlIHtcbiAgICAgICAgICAgIC8vIEl0IHdvcmtzIHdoZW4gdXNpbmcgYSBzdGFuZGFsb25lIHNjb3BlIHdpdGggYSBzaW5nbGUgdGhyZWFkIGV4ZWN1dG9yLCBidXQgSSBkb3VidCB0aGF0IHRoaXMgaXMgdGhlIFxuICAgICAgICAgICAgLy8gcmlnaHQgd2F5IHRvIGRvbyBpdC4uLiBidXQgdXNpbmcgdGhlIHNjb3BlIG9mIHRoZSBjYWxsZXIgd2UgZ2V0IGJsb2NrZWQuLi5cbiAgICAgICAgICAgIHZhbCBzb2NrZXRIYW5kbGVyID0gQ2hhbm5lbDxTb2NrZXQ+KClcbiAgICAgICAgICAgIENvcm91dGluZVNjb3BlKERpc3BhdGNoZXJzLkRlZmF1bHQpLmxhdW5jaCB7XG4gICAgICAgICAgICAgICAgc29ja2V0SGFuZGxlci5jb25zdW1lRWFjaCB7IHNvY2tldCAtPlxuICAgICAgICAgICAgICAgICAgICBwcmludGxuKFwiUmVjZWl2ZWQgJHtzb2NrZXR9IGF0IGhhbmRsZXJcIilcbiAgICAgICAgICAgICAgICAgICAgdmFsIGNvbm5lY3Rpb24gPSBnZXRDb25uZWN0aW9uQW5kU3RhcnRMaXN0ZW4oc29ja2V0KVxuICAgICAgICAgICAgICAgICAgICBpbmJvdW5kQ29ubmVjdGlvbnMuYWRkKGNvbm5lY3Rpb24pXG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB2YWwgc2VydmVyU29ja2V0ID0gc29ja2V0RmFjdG9yeS5jcmVhdGVTZXJ2ZXJTb2NrZXQoc2VydmVyUG9ydClcbiAgICAgICAgICAgIHNlcnZlciA9IFNlcnZlcihzZXJ2ZXJTb2NrZXQsIHNvY2tldEhhbmRsZXIsIENvcm91dGluZVNjb3BlKERpc3BhdGNoZXJzLklPKSlcbiAgICAgICAgICAgICAgICAuYWxzbyB7IGl0Lmxpc3RlbigpIH1cbiAgICAgICAgfVxuICAgIH1cblxuICAgIHN1c3BlbmQgZnVuIHNlbmQoYWRkcmVzczogQWRkcmVzcywgbWVzc2FnZTogU3RyaW5nKSB7XG4gICAgICAgIHZhbCBvdXRib3VuZENvbm5lY3Rpb24gPSBvdXRib3VuZENvbm5lY3Rpb25zLmdldE9yRWxzZShhZGRyZXNzKSB7IGNvbm5lY3QoYWRkcmVzcykgfVxuICAgICAgICBvdXRib3VuZENvbm5lY3Rpb24uc2VuZChtZXNzYWdlKVxuICAgIH1cblxuICAgIHN1c3BlbmQgZnVuIGNvbm5lY3QoYWRkcmVzczogQWRkcmVzcykgPVxuICAgICAgICBjb3JvdXRpbmVTY29wZSB7XG4gICAgICAgICAgICB2YWwgc29ja2V0ID0gc29ja2V0RmFjdG9yeS5jcmVhdGVTb2NrZXQoYWRkcmVzcylcbiAgICAgICAgICAgIGdldENvbm5lY3Rpb25BbmRTdGFydExpc3Rlbihzb2NrZXQpLmFsc28geyBvdXRib3VuZENvbm5lY3Rpb25zW2FkZHJlc3NdID0gaXQgfVxuICAgICAgICB9XG5cbiAgICBwcml2YXRlIHN1c3BlbmQgZnVuIGdldENvbm5lY3Rpb25BbmRTdGFydExpc3Rlbihzb2NrZXQ6IFNvY2tldCk6IENvbm5lY3Rpb24gPSBjb3JvdXRpbmVTY29wZSB7XG4gICAgICAgIC8vIEl0IHdvcmtzIHdoZW4gdXNpbmcgYSBzdGFuZGFsb25lIHNjb3BlIHdpdGggYSBzaW5nbGUgdGhyZWFkIGV4ZWN1dG9yLCBidXQgSSBkb3VidCB0aGF0IHRoaXMgaXMgdGhlIFxuICAgICAgICAvLyByaWdodCB3YXkgdG8gZG8gaXQuLi4gYnV0IHVzaW5nIHRoZSBzY29wZSBvZiB0aGUgY2FsbGVyIHdlIGdldCBibG9ja2VkLi4uXG4gICAgICAgIHZhbCBtZXNzYWdlSGFuZGxlciA9IENoYW5uZWw8U3RyaW5nPigpXG4gICAgICAgIENvcm91dGluZVNjb3BlKERpc3BhdGNoZXJzLkRlZmF1bHQpLmxhdW5jaCB7XG4gICAgICAgICAgICBtZXNzYWdlSGFuZGxlci5jb25zdW1lRWFjaCB7IG1lc3NhZ2UgLT5cbiAgICAgICAgICAgICAgICBwcmludGxuKFwiUmVjZWl2ZWQgJyRtZXNzYWdlJ1wiKVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gQXMgd2UgbmVlZCB0byB1c2UgYmxvY2tpbmcgSU8gd2UgY3JlYXRlIGEgc3RhbmRhbG9uZSBzY29wZSB3aXRoIGEgc2luZ2xlIHRocmVhZCBleGVjdXRvclxuICAgICAgICBDb25uZWN0aW9uKHNvY2tldCwgbWVzc2FnZUhhbmRsZXIsIENvcm91dGluZVNjb3BlKERpc3BhdGNoZXJzLklPKSkuYWxzbyB7IGl0LnN0YXJ0TGlzdGVuKCkgfVxuICAgIH1cblxuICAgIGZ1biBzaHV0ZG93bigpIHtcbiAgICAgICAgc2VydmVyPy5zaHV0ZG93bigpXG4gICAgICAgIGluYm91bmRDb25uZWN0aW9ucy5mb3JFYWNoKHsgaXQuc2h1dGRvd24oKSB9KVxuICAgICAgICBvdXRib3VuZENvbm5lY3Rpb25zLnZhbHVlcy5mb3JFYWNoKHsgaXQuc2h1dGRvd24oKSB9KVxuICAgIH1cbn1cblxuY2xhc3MgU2VydmVyKFxuICAgIHByaXZhdGUgdmFsIHNlcnZlclNvY2tldDogU2VydmVyU29ja2V0LFxuICAgIHByaXZhdGUgdmFsIHNvY2tldEhhbmRsZXI6IENoYW5uZWw8U29ja2V0PixcbiAgICBzY29wZTogQ29yb3V0aW5lU2NvcGUsXG4pIDogQ29yb3V0aW5lU2NvcGUgYnkgc2NvcGUge1xuICAgIGZ1biBsaXN0ZW4oKSB7XG4gICAgICAgIGxhdW5jaCB7XG4gICAgICAgICAgICBydW5DYXRjaGluZyB7XG4gICAgICAgICAgICAgICAgd2hpbGUgKHRydWUpIHtcbiAgICAgICAgICAgICAgICAgICAgcHJpbnRsbihcIlNlcnZlciBsaXN0ZW5pbmcgb24gcG9ydCAke3NlcnZlclNvY2tldC5sb2NhbFBvcnR9IGZvciBuZXcgY29ubmVjdGlvbnNcIilcbiAgICAgICAgICAgICAgICAgICAgdmFsIHNvY2tldCA9IHNlcnZlclNvY2tldC5hY2NlcHQoKVxuICAgICAgICAgICAgICAgICAgICBwcmludGxuKFwiU2VydmVyIGFjY2VwdCBpbmJvdW5kIGNvbm5lY3Rpb24gb24gcG9ydCAke3NlcnZlclNvY2tldC5sb2NhbFBvcnR9XCIpXG4gICAgICAgICAgICAgICAgICAgIHNvY2tldEhhbmRsZXIuc2VuZChzb2NrZXQpXG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuXG4gICAgZnVuIHNodXRkb3duKCkge1xuICAgICAgICBydW5DYXRjaGluZyB7IHNlcnZlclNvY2tldC5jbG9zZSgpIH1cbiAgICAgICAgc29ja2V0SGFuZGxlci5jbG9zZSgpXG4gICAgfVxufVxuXG5jbGFzcyBDb25uZWN0aW9uKFxuICAgIHByaXZhdGUgdmFsIHNvY2tldDogU29ja2V0LFxuICAgIHByaXZhdGUgdmFsIG1lc3NhZ2VIYW5kbGVyOiBDaGFubmVsPFN0cmluZz4sXG4gICAgc2NvcGU6IENvcm91dGluZVNjb3BlLFxuKSA6IENvcm91dGluZVNjb3BlIGJ5IHNjb3BlIHtcbiAgICBwcml2YXRlIHZhbCBvYmplY3RPdXRwdXRTdHJlYW06IE9iamVjdE91dHB1dFN0cmVhbSA9IE9iamVjdE91dHB1dFN0cmVhbShzb2NrZXQuZ2V0T3V0cHV0U3RyZWFtKCkpXG4gICAgcHJpdmF0ZSB2YWwgb2JqZWN0SW5wdXRTdHJlYW06IE9iamVjdElucHV0U3RyZWFtID0gT2JqZWN0SW5wdXRTdHJlYW0oc29ja2V0LmdldElucHV0U3RyZWFtKCkpXG5cbiAgICBmdW4gc3RhcnRMaXN0ZW4oKSB7XG4gICAgICAgIGxhdW5jaCB7XG4gICAgICAgICAgICBydW5DYXRjaGluZyB7XG4gICAgICAgICAgICAgICAgd2hpbGUgKHRydWUpIHtcbiAgICAgICAgICAgICAgICAgICAgcHJpbnRsbihcIkxpc3RlbmluZyBmb3IgbWVzc2FnZXNcIilcbiAgICAgICAgICAgICAgICAgICAgdmFsIG1zZyA9IG9iamVjdElucHV0U3RyZWFtLnJlYWRPYmplY3QoKVxuICAgICAgICAgICAgICAgICAgICBpZiAobXNnIGlzIFN0cmluZykge1xuICAgICAgICAgICAgICAgICAgICAgICAgcHJpbnRsbihcIk1lc3NhZ2UgYXJyaXZlZCAnJG1zZydcIilcbiAgICAgICAgICAgICAgICAgICAgICAgIG1lc3NhZ2VIYW5kbGVyLnNlbmQobXNnKVxuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuXG4gICAgc3VzcGVuZCBmdW4gc2VuZChtZXNzYWdlOiBTdHJpbmcpID1cbiAgICAgICAgd2l0aENvbnRleHQoRGlzcGF0Y2hlcnMuSU8pIHtcbiAgICAgICAgICAgIG9iamVjdE91dHB1dFN0cmVhbS53cml0ZU9iamVjdChtZXNzYWdlKVxuICAgICAgICAgICAgb2JqZWN0T3V0cHV0U3RyZWFtLmZsdXNoKClcbiAgICAgICAgfVxuXG4gICAgZnVuIHNodXRkb3duKCkge1xuICAgICAgICBydW5DYXRjaGluZyB7IHNvY2tldC5jbG9zZSgpIH1cbiAgICAgICAgbWVzc2FnZUhhbmRsZXIuY2xvc2UoKVxuICAgIH1cbn1cblxuLy8gV2UgbmVlZCB0byB1c2UgYmxvY2tpbmcgSU8gQVBJIGFzIHdlIHVzZSBUb3IgYW5kIG5vbi1ibG9ja2luZyBJTyBBUEkgZG9lcyBub3Qgc3VwcG9ydCBTb2Nrc1Byb3h5XG4vLyBIZXJlIHdlIHVzZSBjbGVhciBuZXQsIGJ1dCBpbiByZWFsIGFwcCBpdCB3aWxsIHJ1biBvdmVyIFRvciBhbmQgZ2V0dGluZyB0aGUgU2VydmVyU29ja2V0IGFuZCBTb2NrZXQgdGFrZXNcbi8vIHNpZ25pZmljYW50IHRpbWVcbmNsYXNzIFNvY2tldEZhY3RvcnkoKSB7XG4gICAgc3VzcGVuZCBmdW4gY3JlYXRlU2VydmVyU29ja2V0KHNlcnZlclBvcnQ6IEludCkgPVxuICAgICAgICB3aXRoQ29udGV4dChEaXNwYXRjaGVycy5JTykge1xuICAgICAgICAgICAgcHJpbnRsbihcIkNyZWF0ZSBzZXJ2ZXJTb2NrZXQgd2l0aCBwb3J0ICRzZXJ2ZXJQb3J0XCIpXG4gICAgICAgICAgICBTZXJ2ZXJTb2NrZXQoc2VydmVyUG9ydClcbiAgICAgICAgfVxuXG4gICAgc3VzcGVuZCBmdW4gY3JlYXRlU29ja2V0KGFkZHJlc3M6IEFkZHJlc3MpID1cbiAgICAgICAgd2l0aENvbnRleHQoRGlzcGF0Y2hlcnMuSU8pIHtcbiAgICAgICAgICAgIHByaW50bG4oXCJDcmVhdGUgc29ja2V0IHRvIGFkZHJlc3MgJGFkZHJlc3NcIilcbiAgICAgICAgICAgIFNvY2tldChhZGRyZXNzLmhvc3QsIGFkZHJlc3MucG9ydClcbiAgICAgICAgfVxufVxuXG5kYXRhIGNsYXNzIEFkZHJlc3ModmFsIGhvc3Q6IFN0cmluZywgdmFsIHBvcnQ6IEludCkge1xuICAgIGNvbXBhbmlvbiBvYmplY3Qge1xuICAgICAgICBmdW4gbG9jYWxob3N0KHBvcnQ6IEludCkgPSBBZGRyZXNzKFwiMTI3LjAuMC4xXCIsIHBvcnQpXG4gICAgfVxuXG4gICAgb3ZlcnJpZGUgZnVuIHRvU3RyaW5nKCk6IFN0cmluZyA9IGhvc3QgKyBcIjpcIiArIHBvcnRcbn0iLCJ0byI6IjkwIiwiZnJvbSI6IjgxIn0=

That’s quite a lot of code to digest. If I understand your main concern correctly, then it is not really related to blocking IO. You seem to try to design your code in the way that suspend functions don’t… suspend.

I believe it is (almost?) always a bad idea to start background tasks from suspend functions. Suspend functions are marked as “suspend” for a reason. They tell their callers: “I will wait for whatever is needed to be done”. Scheduling asynchronous tasks may be confusing to the caller.

So my suggestion is to let suspend functions suspend. Then, the caller could choose whether invoke some function synchronously (by invoking it directly) or asynchronously (with launch()/async()).

So:

  1. Remove all these CoroutineScope(Dispatchers.Default).launch { } and invoke their body directly - as a result, your functions will suspend.
  2. Make Server.listen(), Connection.startListen() also synchronous, so make them suspendable and remove launch() / replace with withContext().
  3. Whenever you need to start some kind of a long running service, but you don’t want to wait for it, start it inside launch().

For example, your Node.startServer() will become something like this:

suspend fun startServer(serverPort: Int) {
    coroutineScope {
        val socketHandler = Channel<Socket>()
        val serverSocket = socketFactory.createServerSocket(serverPort)
        server = Server(serverSocket, socketHandler, CoroutineScope(Dispatchers.IO))

        launch { server.listen() }
        
        socketHandler.consumeEach { socket ->
            println("Received ${socket} at handler")
            val connection = getConnectionAndStartListen(socket)
            inboundConnections.add(connection)
        }
            
    }
}

It starts two long-running operations: listen() and consumeEach() and waits for both of them. Then use startServer() like this:

launch { node1.startServer(1111) }

As I said above, this way the caller is in control of how to invoke a long running operation. The code is synchronous by default, but you can make it asynchronous where it is needed. In your original code you made almost everything asynchronous, because you were concerned about “blocking”. If you prefer asynchronous code then you don’t really need coroutines :slight_smile:

After such redesign you have a proper structured concurrency, because all coroutines are descendants of the coroutine started by runBlocking().

Also, note that blocking IO operations are not really cancellable. You need to make sure that you close all resources, because otherwise coroutines may freeze waiting in a blocking IO even after cancellation.

Great thanks. I managed to resolved most cases already similar to what you suggested. Only the connection handler I am still making troubles… I don’t want that the outside caller has to know about the internals of Node. So when a new connection gets created from an incoming connection I need to setup the async listeners but as that code is inside the socket listener its a bit more complicated as the other cases.

Damn my reply got deleted from the spam filter…

Hope this one will make it:

Thanks @broot for your explainations! Got more clear now.

Here is the fixed code:
https://play.kotlinlang.org/#eyJ2ZXJzaW9uIjoiMS4zLjExIiwicGxhdGZvcm0iOiJqYXZhIiwiYXJncyI6IiIsIm5vbmVNYXJrZXJzIjp0cnVlLCJ0aGVtZSI6ImlkZWEiLCJmb2xkZWRCdXR0b24iOnRydWUsInJlYWRPbmx5IjpmYWxzZSwiY29kZSI6ImltcG9ydCBrb3RsaW54LmNvcm91dGluZXMuKlxuaW1wb3J0IGtvdGxpbnguY29yb3V0aW5lcy5jaGFubmVscy5DaGFubmVsXG5pbXBvcnQga290bGlueC5jb3JvdXRpbmVzLmNoYW5uZWxzLmNvbnN1bWVFYWNoXG5pbXBvcnQgamF2YS5pby5PYmplY3RJbnB1dFN0cmVhbVxuaW1wb3J0IGphdmEuaW8uT2JqZWN0T3V0cHV0U3RyZWFtXG5pbXBvcnQgamF2YS5uZXQuU2VydmVyU29ja2V0XG5pbXBvcnQgamF2YS5uZXQuU29ja2V0XG5cbmZ1biBtYWluKCkge1xuICAgIHJ1bkJsb2NraW5nIHtcbiAgICAgICAgdmFsIG5vZGUxID0gTm9kZShTb2NrZXRGYWN0b3J5KCkpXG4gICAgICAgIG5vZGUxLnN0YXJ0U2VydmVyKDExMTEpXG4gICAgICAgIGxhdW5jaCB7IG5vZGUxLnNldHVwQmFja2dyb3VuZFRhc2tzKCkgfVxuXG4gICAgICAgIG5vZGUxLmFkZExpc3RlbmVyKENoYW5uZWw8U3RyaW5nPigpXG4gICAgICAgICAgICAuYWxzbyB7XG4gICAgICAgICAgICAgICAgbGF1bmNoIHtcbiAgICAgICAgICAgICAgICAgICAgaXQuY29uc3VtZUVhY2ggeyBtZXNzYWdlIC0+XG4gICAgICAgICAgICAgICAgICAgICAgICBwcmludGxuKFwiTWVzc2FnZUxpc3RlbmVyIHJlY2VpdmVkICckbWVzc2FnZSdcIilcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0pXG5cbiAgICAgICAgdmFsIG5vZGUyID0gTm9kZShTb2NrZXRGYWN0b3J5KCkpXG4gICAgICAgIG5vZGUyLnN0YXJ0U2VydmVyKDIyMjIpXG4gICAgICAgIGxhdW5jaCB7IG5vZGUyLnNldHVwQmFja2dyb3VuZFRhc2tzKCkgfVxuXG4gICAgICAgIHZhbCBhZGRyZXNzID0gQWRkcmVzcy5sb2NhbGhvc3QoMTExMSlcbiAgICAgICAgdmFsIGNvbm5lY3Rpb24gPSBub2RlMi5jb25uZWN0KGFkZHJlc3MpXG4gICAgICAgIGxhdW5jaCB7IG5vZGUyLnNldHVwQ29ubmVjdGlvbkhhbmRsZXIoY29ubmVjdGlvbikgfVxuICAgICAgICBub2RlMi5zZW5kKGFkZHJlc3MsIFwiTWVzc2FnZSBmcm9tIGxvY2FsaG9zdDoyMjIyIHRvIGxvY2FsaG9zdDoxMTExXCIpXG5cbiAgICAgICAgbm9kZTEuc2h1dGRvd24oKVxuICAgICAgICBub2RlMi5zaHV0ZG93bigpXG4gICAgfVxufVxuXG5jbGFzcyBOb2RlKHZhbCBzb2NrZXRGYWN0b3J5OiBTb2NrZXRGYWN0b3J5KSB7XG4gICAgcHJpdmF0ZSB2YXIgbWVzc2FnZUxpc3RlbmVycyA9IG11dGFibGVMaXN0T2Y8Q2hhbm5lbDxTdHJpbmc+PigpXG4gICAgcHJpdmF0ZSB2YWwgaW5ib3VuZENvbm5lY3Rpb25zOiBNdXRhYmxlTGlzdDxDb25uZWN0aW9uPiA9IG11dGFibGVMaXN0T2YoKVxuICAgIHByaXZhdGUgdmFsIG91dGJvdW5kQ29ubmVjdGlvbnM6IEhhc2hNYXA8QWRkcmVzcywgQ29ubmVjdGlvbj4gPSBoYXNoTWFwT2YoKVxuICAgIHByaXZhdGUgdmFyIHNlcnZlcjogU2VydmVyPyA9IG51bGxcbiAgICBwcml2YXRlIHZhbCBtZXNzYWdlSGFuZGxlciA9IENoYW5uZWw8U3RyaW5nPigpXG4gICAgcHJpdmF0ZSB2YWwgc29ja2V0SGFuZGxlciA9IENoYW5uZWw8U29ja2V0PigpXG5cbiAgICBzdXNwZW5kIGZ1biBzdGFydFNlcnZlcihzZXJ2ZXJQb3J0OiBJbnQpIHtcbiAgICAgICAgdmFsIHNlcnZlclNvY2tldCA9IHNvY2tldEZhY3RvcnkuY3JlYXRlU2VydmVyU29ja2V0KHNlcnZlclBvcnQpXG4gICAgICAgIHNlcnZlciA9IFNlcnZlcihzZXJ2ZXJTb2NrZXQsIHNvY2tldEhhbmRsZXIpXG4gICAgfVxuXG4gICAgc3VzcGVuZCBmdW4gc2V0dXBCYWNrZ3JvdW5kVGFza3MoKSB7XG4gICAgICAgIGNvcm91dGluZVNjb3BlIHtcbiAgICAgICAgICAgIGxhdW5jaCB7XG4gICAgICAgICAgICAgICAgd2l0aENvbnRleHQoRGlzcGF0Y2hlcnMuRGVmYXVsdCkge1xuICAgICAgICAgICAgICAgICAgICBzb2NrZXRIYW5kbGVyLmNvbnN1bWVFYWNoIHsgc29ja2V0IC0+XG4gICAgICAgICAgICAgICAgICAgICAgICBwcmludGxuKFwiUmVjZWl2ZWQgJHtzb2NrZXR9IGF0IGhhbmRsZXJcIilcbiAgICAgICAgICAgICAgICAgICAgICAgIHZhbCBjb25uZWN0aW9uID0gQ29ubmVjdGlvbihzb2NrZXQsIG1lc3NhZ2VIYW5kbGVyKS5hbHNvIHsgaW5ib3VuZENvbm5lY3Rpb25zLmFkZChpdCkgfVxuICAgICAgICAgICAgICAgICAgICAgICAgd2l0aENvbnRleHQoRGlzcGF0Y2hlcnMuSU8pIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYXVuY2ggeyBjb25uZWN0aW9uLmxpc3RlbigpIH1cbiAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGxhdW5jaCB7XG4gICAgICAgICAgICAgICAgd2l0aENvbnRleHQoRGlzcGF0Y2hlcnMuRGVmYXVsdCkge1xuICAgICAgICAgICAgICAgICAgICBtZXNzYWdlSGFuZGxlci5jb25zdW1lRWFjaCB7IG1lc3NhZ2UgLT5cbiAgICAgICAgICAgICAgICAgICAgICAgIHByaW50bG4oXCJSZWNlaXZlZCAnJG1lc3NhZ2UnXCIpXG4gICAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlTGlzdGVuZXJzLmZvckVhY2ggeyBpdC5zZW5kKG1lc3NhZ2UpIH1cbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGxhdW5jaCB7XG4gICAgICAgICAgICAgICAgc2VydmVyPy5saXN0ZW4oKVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuXG4gICAgc3VzcGVuZCBmdW4gY29ubmVjdChhZGRyZXNzOiBBZGRyZXNzKTogQ29ubmVjdGlvbiB7XG4gICAgICAgIHZhbCBzb2NrZXQgPSBzb2NrZXRGYWN0b3J5LmNyZWF0ZVNvY2tldChhZGRyZXNzKVxuICAgICAgICByZXR1cm4gQ29ubmVjdGlvbihzb2NrZXQsIG1lc3NhZ2VIYW5kbGVyKS5hbHNvIHsgb3V0Ym91bmRDb25uZWN0aW9uc1thZGRyZXNzXSA9IGl0IH1cbiAgICB9XG5cbiAgICBzdXNwZW5kIGZ1biBzZXR1cENvbm5lY3Rpb25IYW5kbGVyKGNvbm5lY3Rpb246IENvbm5lY3Rpb24pIHtcbiAgICAgICAgY29yb3V0aW5lU2NvcGUge1xuICAgICAgICAgICAgbGF1bmNoIHtcbiAgICAgICAgICAgICAgICBjb25uZWN0aW9uLmxpc3RlbigpXG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBzdXNwZW5kIGZ1biBzZW5kKGFkZHJlc3M6IEFkZHJlc3MsIG1lc3NhZ2U6IFN0cmluZykgPSBjb3JvdXRpbmVTY29wZSB7XG4gICAgICAgIG91dGJvdW5kQ29ubmVjdGlvbnMuZ2V0T3JFbHNlKGFkZHJlc3MpIHsgY29ubmVjdChhZGRyZXNzKSB9LmFsc28geyBpdC5zZW5kKG1lc3NhZ2UpIH1cbiAgICB9XG5cbiAgICBmdW4gYWRkTGlzdGVuZXIobGlzdGVuZXI6IENoYW5uZWw8U3RyaW5nPikge1xuICAgICAgICBtZXNzYWdlTGlzdGVuZXJzLmFkZChsaXN0ZW5lcilcbiAgICB9XG5cbiAgICBmdW4gc2h1dGRvd24oKSB7XG4gICAgICAgIHNlcnZlcj8uc2h1dGRvd24oKVxuICAgICAgICBpbmJvdW5kQ29ubmVjdGlvbnMuZm9yRWFjaCh7IGl0LnNodXRkb3duKCkgfSlcbiAgICAgICAgb3V0Ym91bmRDb25uZWN0aW9ucy52YWx1ZXMuZm9yRWFjaCh7IGl0LnNodXRkb3duKCkgfSlcbiAgICAgICAgbWVzc2FnZUxpc3RlbmVycy5mb3JFYWNoKHsgaXQuY2xvc2UoKSB9KVxuICAgIH1cbn1cblxuY2xhc3MgU2VydmVyKHByaXZhdGUgdmFsIHNlcnZlclNvY2tldDogU2VydmVyU29ja2V0LCBwcml2YXRlIHZhbCBzb2NrZXRIYW5kbGVyOiBDaGFubmVsPFNvY2tldD4pIHtcbiAgICBzdXNwZW5kIGZ1biBsaXN0ZW4oKSA9IGNvcm91dGluZVNjb3BlIHtcbiAgICAgICAgd2l0aENvbnRleHQoRGlzcGF0Y2hlcnMuSU8pIHtcbiAgICAgICAgICAgIHJ1bkNhdGNoaW5nIHtcbiAgICAgICAgICAgICAgICB3aGlsZSAodHJ1ZSkge1xuICAgICAgICAgICAgICAgICAgICBwcmludGxuKFwiU2VydmVyIGxpc3RlbmluZyBvbiBwb3J0ICR7c2VydmVyU29ja2V0LmxvY2FsUG9ydH0gZm9yIG5ldyBjb25uZWN0aW9uc1wiKVxuICAgICAgICAgICAgICAgICAgICB2YWwgc29ja2V0ID0gc2VydmVyU29ja2V0LmFjY2VwdCgpXG4gICAgICAgICAgICAgICAgICAgIHByaW50bG4oXCJTZXJ2ZXIgYWNjZXB0IGluYm91bmQgY29ubmVjdGlvbiBvbiBwb3J0ICR7c2VydmVyU29ja2V0LmxvY2FsUG9ydH1cIilcbiAgICAgICAgICAgICAgICAgICAgc29ja2V0SGFuZGxlci5zZW5kKHNvY2tldClcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBmdW4gc2h1dGRvd24oKSB7XG4gICAgICAgIHJ1bkNhdGNoaW5nIHsgc2VydmVyU29ja2V0LmNsb3NlKCkgfVxuICAgICAgICBzb2NrZXRIYW5kbGVyLmNsb3NlKClcbiAgICB9XG59XG5cbmNsYXNzIENvbm5lY3Rpb24ocHJpdmF0ZSB2YWwgc29ja2V0OiBTb2NrZXQsIHByaXZhdGUgdmFsIG1lc3NhZ2VIYW5kbGVyOiBDaGFubmVsPFN0cmluZz4pIHtcbiAgICBwcml2YXRlIHZhbCBvYmplY3RPdXRwdXRTdHJlYW06IE9iamVjdE91dHB1dFN0cmVhbSA9IE9iamVjdE91dHB1dFN0cmVhbShzb2NrZXQuZ2V0T3V0cHV0U3RyZWFtKCkpXG4gICAgcHJpdmF0ZSB2YWwgb2JqZWN0SW5wdXRTdHJlYW06IE9iamVjdElucHV0U3RyZWFtID0gT2JqZWN0SW5wdXRTdHJlYW0oc29ja2V0LmdldElucHV0U3RyZWFtKCkpXG5cbiAgICBzdXNwZW5kIGZ1biBsaXN0ZW4oKSA9IGNvcm91dGluZVNjb3BlIHtcbiAgICAgICAgd2l0aENvbnRleHQoRGlzcGF0Y2hlcnMuSU8pIHtcbiAgICAgICAgICAgIHJ1bkNhdGNoaW5nIHtcbiAgICAgICAgICAgICAgICB3aGlsZSAodHJ1ZSkge1xuICAgICAgICAgICAgICAgICAgICBwcmludGxuKFwiTGlzdGVuaW5nIGZvciBtZXNzYWdlc1wiKVxuICAgICAgICAgICAgICAgICAgICB2YWwgbXNnID0gb2JqZWN0SW5wdXRTdHJlYW0ucmVhZE9iamVjdCgpXG4gICAgICAgICAgICAgICAgICAgIGlmIChtc2cgaXMgU3RyaW5nKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBwcmludGxuKFwiTWVzc2FnZSBhcnJpdmVkICckbXNnJ1wiKVxuICAgICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZUhhbmRsZXIuc2VuZChtc2cpXG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBzdXNwZW5kIGZ1biBzZW5kKG1lc3NhZ2U6IFN0cmluZykgPVxuICAgICAgICB3aXRoQ29udGV4dChEaXNwYXRjaGVycy5JTykge1xuICAgICAgICAgICAgcHJpbnRsbihcIlNlbmQgbWVzc2FnZSAkbWVzc2FnZVwiKVxuICAgICAgICAgICAgb2JqZWN0T3V0cHV0U3RyZWFtLndyaXRlT2JqZWN0KG1lc3NhZ2UpXG4gICAgICAgICAgICBvYmplY3RPdXRwdXRTdHJlYW0uZmx1c2goKVxuICAgICAgICB9XG5cbiAgICBmdW4gc2h1dGRvd24oKSB7XG4gICAgICAgIHJ1bkNhdGNoaW5nIHsgc29ja2V0LmNsb3NlKCkgfVxuICAgICAgICBtZXNzYWdlSGFuZGxlci5jbG9zZSgpXG4gICAgfVxufVxuXG5jbGFzcyBTb2NrZXRGYWN0b3J5KCkge1xuICAgIHN1c3BlbmQgZnVuIGNyZWF0ZVNlcnZlclNvY2tldChzZXJ2ZXJQb3J0OiBJbnQpID1cbiAgICAgICAgd2l0aENvbnRleHQoRGlzcGF0Y2hlcnMuSU8pIHtcbiAgICAgICAgICAgIHByaW50bG4oXCJDcmVhdGUgc2VydmVyU29ja2V0IHdpdGggcG9ydCAkc2VydmVyUG9ydFwiKVxuICAgICAgICAgICAgU2VydmVyU29ja2V0KHNlcnZlclBvcnQpXG4gICAgICAgIH1cblxuICAgIHN1c3BlbmQgZnVuIGNyZWF0ZVNvY2tldChhZGRyZXNzOiBBZGRyZXNzKSA9XG4gICAgICAgIHdpdGhDb250ZXh0KERpc3BhdGNoZXJzLklPKSB7XG4gICAgICAgICAgICBwcmludGxuKFwiQ3JlYXRlIHNvY2tldCB0byBhZGRyZXNzICRhZGRyZXNzXCIpXG4gICAgICAgICAgICBTb2NrZXQoYWRkcmVzcy5ob3N0LCBhZGRyZXNzLnBvcnQpXG4gICAgICAgIH1cbn1cblxuZGF0YSBjbGFzcyBBZGRyZXNzKHZhbCBob3N0OiBTdHJpbmcsIHZhbCBwb3J0OiBJbnQpIHtcbiAgICBjb21wYW5pb24gb2JqZWN0IHtcbiAgICAgICAgZnVuIGxvY2FsaG9zdChwb3J0OiBJbnQpID0gQWRkcmVzcyhcIjEyNy4wLjAuMVwiLCBwb3J0KVxuICAgIH1cblxuICAgIG92ZXJyaWRlIGZ1biB0b1N0cmluZygpOiBTdHJpbmcgPSBob3N0ICsgXCI6XCIgKyBwb3J0XG59IiwidG8iOiI5MCIsImZyb20iOiI4MSJ9