Slide 1

Slide 1 text

Graceful shutdown with Structured Concurrency 47deg.com

Slide 2

Slide 2 text

Who am I?

Slide 3

Slide 3 text

Graceful shutdown with Structured Concurrency Problem Resources Structured Concurrency Example

Slide 4

Slide 4 text

Resources DataSource Application File Handle Socket Handle

Slide 5

Slide 5 text

Microservice Scaling Demand Time

Slide 6

Slide 6 text

Microservice Staged Rollouts Service A Client 1.0 1.0 1.1 1.1

Slide 7

Slide 7 text

Microservice Scaling Microservice Staged Rollouts Graceful Shutdown

Slide 8

Slide 8 text

Closeable class MyServer : AutoCloseable { init { println("Initialising server") } fun routes(): Unit = println("Handling incoming requests") override fun close() = println("Closing server") }

Slide 9

Slide 9 text

Closeable fun main(): Unit { MyServer().use { it.routes() } } Initialising server Handling incoming requests Closing server Process finished with exit code 0

Slide 10

Slide 10 text

Closeable fun main(): Unit = runBlocking { MyServer().use { it.routes() awaitCancellation() } } Initialising server Handling incoming requests Process finished with exit code 143 (interrupted by signal 15: SIGTERM)

Slide 11

Slide 11 text

Termination Signals SIGTERM SIGINT SIGKILL

Slide 12

Slide 12 text

Structured Concurrency Structured Concurrency Parent Job Child Job A Child Job B Child Job A-1 Child Job A-2 Successful completion Cancellation Failure

Slide 13

Slide 13 text

Structured Concurrency Dependency Graph main Database MetricRegistry Server Event Processor …

Slide 14

Slide 14 text

SuspendApp Structured Concurrency pattern for Resource safety Multiplatform Library Only depends on KotlinX Coroutines

Slide 15

Slide 15 text

SuspendApp fun main(): Unit = SuspendApp { MyServer().use { it.routes() awaitCancellation() } } Initialising server Handling incoming requests Closing Server Process finished with exit code 143 (interrupted by signal 15: SIGTERM)

Slide 16

Slide 16 text

Closeable Missing support for Kotlin Multiplatform No support for suspend Structured Concurrency ExitCase indication

Slide 18

Slide 18 text

Resource - ExitCase Successful completion Cancellation Failure

Slide 19

Slide 19 text

Resource sealed class ExitCase { object Completed : ExitCase() data class Cancelled(val exception: CancellationException) : ExitCase() data class Failure(val failure: Throwable) : ExitCase() }

Slide 21

Slide 21 text

Resource class MyServer private constructor() { fun routes(): Unit = println("Handling incoming requests") suspend fun shutdown(exitCase: ExitCase) = withContext(Dispatchers.IO){ println("Shutting down server with $exitCase") } companion object { fun create(): Resource = Resource( { MyServer().also { println("Initialising server") } }, { server, exitCase -> server.shutdown(exitCase) } ) } }

Slide 22

Slide 22 text

Resource fun main(): Unit = SuspendApp { MyServer.create().use { it.routes() awaitCancellation() } } Initialising server Handling incoming requests Shutting down server with Cancelled(exception=JobCancellationException:--.) Process finished with exit code 143 (interrupted by signal 15: SIGTERM)

Slide 23

Slide 23 text

Structurred Concurrency Dependency Graph main Database MetricRegistry Server Event Processor …

Slide 24

Slide 24 text

Composing Resource fun database(config: HikariConfig): Resource = Resource.fromAutoCloseable { HikariDataSource(config) } private val metrics: Resource = Resource( acquire = { PrometheusMeterRegistry(PrometheusConfig.DEFAULT) }, release = { p, _: ExitCase -> p.close() } )

Slide 25

Slide 25 text

Composing Resource fun main(): Unit = SuspendApp { resource { val config = loadConfig() val registry: PrometheusMeterRegistry = metrics.bind() val database: HikariDataSource = database(config.hikariConfig).bind() } }

Slide 26

Slide 26 text

Composing Resource fun main(): Unit = SuspendApp { resource { val config = loadConfig() val registry: PrometheusMeterRegistry = metrics.bind() val database: HikariDataSource = database(config.hikariConfig).bind() val server = MyServer.create().bind() server.routes(registry, database) }.use { awaitCancellation() } }

Slide 27

Slide 27 text

ResourceScope suspend fun ResourceScope.database(config: HikariConfig): HikariDataSource = autoCloseable { HikariDataSource(config) } suspend fun ResourceScope.metrics(): PrometheusMeterRegistry = install({ PrometheusMeterRegistry(PrometheusConfig.DEFAULT) }) { p, _: ExitCase -> p.close() }

Slide 28

Slide 28 text

ResourceScope fun main(): Unit = SuspendApp { resource { val config = loadConfig() val registry = metrics() val database = database(config.hikariConfig) val server = MyServer.create() server.routes(registry, database) }.use { awaitCancellation() } }

Slide 29

Slide 29 text

ResourceScope fun main(): Unit = SuspendApp { resourceScope { val config = loadConfig() val registry = metrics() val database = database(config.hikariConfig) val server = MyServer.create() server.routes(registry, database) awaitCancellation() } }

Slide 30

Slide 30 text

Production Example

Slide 31

Slide 31 text

Resource offers type-safe DSL SuspendApp Structured Concurrency for Resource & Closeable Conclusion

Slide 32

Slide 32 text

Thanks!