Skip to content

Lifespans and leadership

Use @app.lifespan() for process resources such as shared connections. Use @app.lifespan(scope="leader") for services that should run only while this replica holds the controller Lease. For cleanup tied to a Kubernetes object's deletion, use a finalizer.

Register startup and cleanup together

A lifespan is an async generator: setup runs before yield, and cleanup belongs in finally so it also runs when the application is cancelled. This complete example logs both process and leadership transitions:

import logging
from collections.abc import AsyncIterator

from cloudcoil.models.kubernetes.core.v1 import ConfigMap

from cloudcoil.application import Application, LifecycleEvent, LifecycleType

logger = logging.getLogger(__name__)
app = Application("config-observer", leader_election=True)
configs = app.controller(ConfigMap, label_selector="example.com/observe=true")

@app.lifespan()
async def process(event: LifecycleEvent) -> AsyncIterator[None]:
    logger.info("Process starting")
    try:
        yield
    finally:
        logger.info("Process exiting: %s", event.type)

@app.lifespan(scope="leader")
async def leadership(event: LifecycleEvent) -> AsyncIterator[None]:
    logger.info("Leadership acquired: %s", event.identity)
    try:
        yield
    finally:
        if event.type == LifecycleType.LEADERSHIP_LOST:
            logger.warning("Leadership lost: %s", event.error)
        logger.info("Leader scope exiting: %s", event.type)

@configs.reconcile()
async def observe(config: ConfigMap) -> None:
    logger.info("Observed %s/%s", config.namespace, config.name)

if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    app.main()

A hook can take no arguments when it does not need an event. Each scope accepts one hook; compose several connections with async with or AsyncExitStack inside that hook. Do not add @asynccontextmanager: the application wraps the generator. Setup must reach yield before dependent components start.

Event contract

Scope Entry event.type Exit event.type Replicas
process (default) STARTUP SHUTDOWN, FAILURE or LEADERSHIP_LOST Every running replica
leader LEADERSHIP_ACQUIRED SHUTDOWN, LEADERSHIP_LOST or FAILURE The elected controller replica

event.scope identifies the scope. The same event object's type and error are updated before the yielded scope exits; read them inside cleanup instead of saving the entry value. error is None for normal shutdown and carries the exception for failure or leadership loss. identity identifies the election participant for leader hooks; process hooks have no election identity.

Leader hooks require leader election and at least one controller. Standbys run process hooks and serve admission, but do not enter leader hooks. Register hooks before run; late registration fails. Manifest generation and installation do not enter lifespans.

Shutdown order

On normal stop, the runtime drains controller work within its shutdown budget, then exits the leader hook and releases the Lease. Renewal continues during normal draining and leader cleanup. Webhook requests and controllers finish before the process hook closes its resources. Application-owned clients close last; a caller-supplied Config remains the caller's responsibility.

On leadership loss, workers are cancelled and joined before leader cleanup. Cleanup sees LEADERSHIP_LOST; lease release is attempted afterward with ownership guards. Cleanup must tolerate already having lost ownership. Use it to stop local services, not to delete shared state that a successor may be using.

Leadership loss ends this manager instance. It does not reacquire in-process; restart the application to participate again. Other fatal component errors also stop sibling components and propagate. Hook failures propagate rather than being silently treated as successful shutdown.

Leases coordinate cooperative replicas; they cannot fence an external operation that has already started. Keep handlers idempotent and cancellation-aware. See runtime election details for timing, permissions and health behavior, and the runnable lifespan pattern.