lyng/docs/LaunchPool.md
2026-04-18 23:37:21 +03:00

3.4 KiB

LaunchPool

LaunchPool is a bounded-concurrency task pool: you submit lambdas with launch, and the pool runs them using a fixed number of worker coroutines.

Constructor

LaunchPool(maxWorkers, maxQueueSize = Channel.UNLIMITED)
Parameter Description
maxWorkers Maximum number of tasks that run in parallel.
maxQueueSize Maximum number of tasks that may wait in the queue. When the queue is full, launch suspends the caller until space becomes available. Defaults to Channel.UNLIMITED (no bound).

Methods

launch(lambda): Deferred

Schedules lambda for execution and returns a Deferred for its result.

  • Suspends if the queue is full (maxQueueSize reached).
  • Throws IllegalStateException if the pool is already closed or cancelled.
  • Any exception thrown by lambda is captured in the returned Deferred and does not escape the pool.
val pool = LaunchPool(4)
val d1 = pool.launch { computeSomething() }
val d2 = pool.launch { computeOther() }
pool.closeAndJoin()
println(d1.await())
println(d2.await())

closeAndJoin()

Stops accepting new tasks and suspends until all queued and running tasks complete normally. After this call, any further launch throws IllegalStateException. Idempotent — safe to call multiple times.

cancel()

Immediately closes the queue and cancels all worker coroutines. Queued but unstarted tasks are discarded. After this call, launch throws IllegalStateException. Idempotent.

cancelAndJoin()

Like cancel(), but also suspends until all worker coroutines have stopped. Useful when you need to be sure no worker code is still running before proceeding. Idempotent.

Exception handling

Exceptions from submitted lambdas are captured per-task in the returned Deferred. The pool itself continues running after a task failure:

val pool = LaunchPool(2)
val good = pool.launch { 42 }
val bad  = pool.launch { throw IllegalArgumentException("boom") }
pool.closeAndJoin()

assertEquals(42, good.await())
assertThrows(IllegalArgumentException) { bad.await() }

Bounded queue / back-pressure

When maxQueueSize is set, the producer suspends if the queue fills up, providing automatic back-pressure:

// 1 worker, queue of 2 — producer can be at most 2 tasks ahead of what's running
val pool = LaunchPool(1, 2)
val d1 = pool.launch { delay(10); "a" }
val d2 = pool.launch { delay(10); "b" }
val d3 = pool.launch { delay(10); "c" }   // suspends until d1 is picked up by the worker
pool.closeAndJoin()

Collecting all results

launch returns a Deferred, so you can collect results via map:

val pool = LaunchPool(4)
val jobs = (1..10).map { n -> pool.launch { n * n } }
pool.closeAndJoin()
val results = jobs.map { (it as Deferred).await() }
// results == [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

Concurrency limit in practice

With maxWorkers = 2, at most 2 tasks run simultaneously regardless of how many are queued:

val mu = Mutex()
var active = 0
var maxSeen = 0

val pool = LaunchPool(2)
(1..8).map {
    pool.launch {
        mu.withLock { active++; if (active > maxSeen) maxSeen = active }
        delay(5)
        mu.withLock { active-- }
    }
}
pool.closeAndJoin()
assert(maxSeen <= 2)

See also

  • parallelism.mdlaunch, Deferred, Mutex, Channel, and coroutine basics
  • Channel.md — the underlying channel primitive used by LaunchPool