Suspend Stormify
Coroutine-aware wrapper around a Stormify instance.
Obtained via Stormify.suspending. Provides a suspending transaction { } that acquires a connection from a SuspendConnectionPool, runs the block on the platform IO dispatcher, wires coroutine cancellation to the underlying DB cancel primitive, and correctly handles nesting via savepoints.
The blocking Stormify instance passed to the constructor continues to work independently — SuspendStormify is purely additive. A single process can use both APIs side-by-side: some modules blocking, others suspending. They share no mutable state; each transaction { } call (blocking or suspend) gets its own connection.
Basic usage
val stormify = Stormify(KdbcDataSource("jdbc:sqlite:app.db"))
val async = stormify.suspending(PoolConfig(maxConnections = 8))
async.transaction {
val user = create(User(email = "a@b.c"))
create(Profile(userId = user.id))
}Nested transactions
Calling transaction from within another transaction { } block on the same coroutine lineage reuses the outer connection via a savepoint. The outer transaction's final commit/rollback governs overall atomicity; the inner savepoint only bounds the rollback scope if the inner block throws.
Cancellation
When a coroutine running a transaction { } is cancelled, the pool's cancel hook dispatches Connection.cancel — which on Native maps directly to the driver-specific async-cancel primitive (libpq PQcancel, sqlite3_interrupt, mariadb_cancel, dpiConn_breakExecution). This causes the currently-blocking query to return with an error, the transaction rolls back, and the connection is evicted from the pool (on the assumption that its state may be inconsistent after a forced cancel). On JVM the default Connection.cancel is a no-op — cancellation still unwinds the coroutine but does not interrupt an in-flight query.
launch { } inside transaction — strip semantics
If you launch { } a child coroutine inside a transaction { }, the child inherits ConnectionElement via standard coroutine context propagation. Do not perform parallel DB work from that child — the parent's connection is already in use by the parent block, and driver-level APIs forbid sharing a connection across threads. The safe pattern is:
async.transaction { // outer tx, connection A
launch {
async.transaction { // nested: opens savepoint on A, NOT parallel work
...
}
}
}If you truly want parallel DB operations, start a separate top-level transaction from outside any transaction { } block — each gets its own pool connection.
Properties
Functions
Execute block inside a transaction. Commits on success, rolls back on any throwable.