Command Timeout
Configure KubeMQ command execution timeouts and handle expiry using the Kotlin SDK RPC client.
Overview
A command timeout is the deadline you attach to a single RPC call so a caller never blocks forever waiting on a handler that isn't there or isn't responding. Commands are synchronous by design — the sender is suspended until a reply arrives — so without a bound, a missing subscriber or a crashed handler turns one request into an indefinite hang that ties up a coroutine and cascades into upstream timeouts.
The timeout is set per call with timeoutMs on the command builder, and it's enforced by the broker itself, not by client-side polling: the broker tracks the deadline server-side and fails the request the moment it expires, regardless of what the calling coroutine is doing. When the window elapses with no response, sendCommand returns a response with a populated error instead of a clean success — your signal to retry or fall back.
Gotchas: a command timeout is a broker-enforced deadline, not a coroutine withTimeout or job cancellation, so don't assume cancelling the coroutine also stops the broker from waiting; a slow-but-alive handler and a completely absent one produce the same timeout error, so you can't tell them apart from the response alone; and setting timeoutMs too short under normal load turns transient latency into false failures.
Prerequisites
- KubeMQ server running on
localhost:50000 - Kotlin SDK installed (
implementation("io.kubemq.sdk:kubemq-sdk-kotlin:1.0.1"))
Code
package io.kubemq.sdk.examples.commands
import io.kubemq.sdk.client.KubeMQClient
import io.kubemq.sdk.cq.commandMessage
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
private const val ADDRESS = "localhost:50000"
private const val CLIENT_ID = "kotlin-commands-command-timeout"
private const val CHANNEL = "kotlin-commands.command-timeout"
fun main() = runBlocking {
val client = KubeMQClient.cq {
address = ADDRESS
clientId = CLIENT_ID
}
client.use {
try {
client.createCommandsChannel(CHANNEL)
// Subscribe to handle commands (handler intentionally delays 3s)
val subJob = launch {
client.subscribeToCommands {
channel = CHANNEL
}.collect { cmd ->
println("Handler received: ${String(cmd.body)}, delaying 3s...")
delay(3000)
val response = cmd.respond {
executed = true
}
client.sendCommandResponse(response)
println("Handler sent response.")
}
}
delay(500)
// Send command with short timeout (expect timeout)
println("Sending command with 1s timeout (handler takes 3s)...")
try {
val resp = client.sendCommand(commandMessage {
channel = CHANNEL
body = "Slow command".toByteArray()
timeoutMs = 1000
})
if (!resp.executed && resp.error.isNotEmpty()) {
println("Timeout (expected): ${resp.error}")
println(" executed=${resp.executed}")
} else {
println("Unexpected success: executed=${resp.executed}")
}
} catch (e: Exception) {
println("Exception (timeout): ${e.message}")
}
// Wait for handler to finish before sending second command
delay(4000)
// Send command with sufficient timeout (expect success)
println("\nSending command with 5s timeout (handler takes 3s)...")
try {
val resp = client.sendCommand(commandMessage {
channel = CHANNEL
body = "Normal command".toByteArray()
timeoutMs = 5000
})
println("Success: executed=${resp.executed}")
} catch (e: Exception) {
println("Error: ${e.message}")
}
subJob.cancel()
} finally {
try { client.deleteCommandsChannel(CHANNEL) } catch (_: Exception) {}
}
println("Done.")
}
}How It Works
- The handler intentionally delays 3 seconds to simulate slow processing.
- A 1-second timeout expires before the handler responds, resulting in a timeout error.
- A 5-second timeout allows the handler enough time to respond successfully.
- Set
timeoutMsbased on expected processing time plus network latency.
Related
Was this page helpful?