Using a CachedThreadPool with KTOR

Using KTOR 1.5.1 to implement a REST service (netty based), quite a few calls take a long time to complete. They are I/O bound, mostly heavy database work. While I understand the CachedThreadPool may create quite a few threads, the nature of the system is such that no more than 100 or so calls are processed at a given time, so expanding the pool to 100 threads is more reasonable than having the callers wait a long time for a free thread because all other threads are all sitting there waiting for database queries to complete.

Is there an easy way to do this?

You can consider to adopt Dispatcher.IO or a new one.

val Dispatchers.DB get() = Dispatchers.IO

withContext(Dispatchers.DB) {
    // your query here

So, just to confirm, if I create a new Dispatchers.DB backed by a CachedThreadPool, the withContext block will free the Netty thread while the query is running - is this correct?

I suppose yes in your case.
I have to ensure to not use runBlocking in your workflow.

Considering your target, generate a large number of thread waiting on the same pool can be an expensive strategy, I suggest you to tune the size of the thread pool to the number of connection configured in the DB pool, this reduce the Thread count and lock contention.
If your DB pool handles 10 connection, then you can create only 10 thread to execute queries, any other thread is a waste.

This almost works:

val env = commandLineEnvironment(args)
val server = embeddedServer(Netty, env) {
// TODO: figure out the double initialization of parent and child EventLoopGroup
    configureBootstrap = {
        group(NioEventLoopGroup(16, Executors.newCachedThreadPool()))
server.start(wait = true)

However, inside the NettyApplicationEngine bootstraps initialization, it calls group() twice, which then causes an IllegalStateException. Perhaps a bug?