Kopf supports asynchronous handler functions:

import asyncio
import kopf

async def create_fn(spec, **_):
    await asyncio.sleep(1.0)

Async functions have an additional benefit over the non-async ones to make the full stack trace available when exceptions occur or IDE breakpoints are used since the async functions are executed directly inside of Kopf’s event loop in the main thread.

Regular synchronous handlers, despite supported for convenience, are executed in parallel threads (via the default executor of the loop), and can only see the stack traces up to the thread entry point.


As with any async coroutines, it is the developer’s responsibility to make sure that all the internal function calls are either awaits of other async coroutines (e.g. await asyncio.sleep()), or the regular non-blocking functions calls.

Calling a synchronous function (e.g. HTTP API calls or time.sleep()) inside of an asynchronous function will block the whole operator process until the synchronous call if finished, i.e. even other resources processed in parallel, and the Kubernetes event-watching/-queueing cycles.

This can come unnoticed in the development environment with only a few resources and no external timeouts, but can hit hard in the production environments with high load.