Symptom

Under concurrent GPIO or LLM requests that read/write Pi hardware, Ktor route handlers could stall or time out. The IO dispatcher thread pool would fill with threads parked inside runBlocking waiting for gRPC responses from krill-pi4j.

Root cause

ServerPiManager.getServerInfo(), readPinState(), setHigh(), and setLow() all wrapped the underlying gRPC calls (which are already suspend functions from Pi4jClient) in runBlocking { }. This blocked whichever thread called them — typically an IO dispatcher thread from a Ktor route handler — until the gRPC response arrived. Under concurrent requests, this could exhaust the IO thread pool (64 threads by default) and stall unrelated coroutines such as DB writes and SSE emissions. The PiManager interface declared these as plain fun, forcing callers to block regardless of whether they were in a coroutine context.

Fix

Prevention