Skip to content

blackbull.env

blackbull.env

Runtime configuration sourced from environment variables.

All server settings live in :class:Settings. Retrieve the current configuration with :func:get_settings, which reads environment variables once and returns an immutable snapshot.

Environment variables

BLACKBULL_ENV production | development (default) | test BB_WORKERS Number of worker processes. 0 resolves to os.cpu_count(). Default: 1. BB_MAX_CONNECTIONS Maximum simultaneous TCP connections accepted per worker. When the cap is reached, new connections receive HTTP/1.1 503 Service Unavailable with Retry-After: 1 (a load-balancer-friendly response, not a silent reset). 0 disables the cap and relies on the OS file-descriptor limit instead. Default: 1024. BB_STREAM_QUEUE_DEPTH asyncio.Queue depth for HTTP/2 per-stream request-body events. Limits memory growth when an ASGI handler is slower than the client. Default: 64. BB_WS_QUEUE_DEPTH asyncio.Queue depth for WebSocket inbound events per connection. Default: 256. BB_ASYNC_LOGGING 1 | true | yes to enable; 0 | false | no to disable. When enabled, a QueueHandler is installed on the blackbull logger so that logger.debug/info calls in the event loop are non-blocking. Default: true. BB_ACCESS_LOG 1 | true | yes to enable; 0 | false | no to disable. When disabled, the blackbull.access logger is silenced (level set to WARNING) so no access log records are formatted or emitted. Useful in production where a separate log aggregator consumes structured logs and the per-request overhead of the access logger is undesirable. Default: true. BB_SOCKET_BACKLOG listen() backlog depth for the server socket. Increasing this reduces silent connection drops during burst traffic when the accept loop falls behind. Capped by net.core.somaxconn on Linux. Default: 1024. BB_SOCKET_SNDBUF Kernel send-buffer size (bytes) set on each accepted TCP socket via SO_SNDBUF. The kernel doubles the requested value internally. Larger values improve throughput for large responses (≥64 kB). 0 leaves the kernel default unchanged. Default: 262144 (256 kB requested → ~512 kB effective). BB_SOCKET_RCVBUF Kernel receive-buffer size (bytes) set on each accepted TCP socket via SO_RCVBUF. Same doubling rule as BB_SOCKET_SNDBUF. 0 leaves the kernel default unchanged. Default: 262144 (256 kB requested → ~512 kB effective). BB_SOCKET_REUSEPORT 1 | true | yes to enable; 0 | false | no to disable. When enabled and the OS supports SO_REUSEPORT, each worker binds its own listening socket so the kernel distributes incoming connections across workers independently, eliminating thundering-herd and improving CPU affinity. Has no effect with a single worker or on platforms without SO_REUSEPORT. Default: true. BB_REQUEST_TIMEOUT Maximum seconds a single HTTP/2 stream (request + response) is allowed to run before it is forcibly cancelled with RST_STREAM CANCEL. Prevents slow or stalled handlers from holding stream slots indefinitely. 0 disables the timeout. Default: 0 (disabled). BB_H2_INITIAL_WINDOW_SIZE Per-stream flow-control window size (bytes) advertised to HTTP/2 peers in the server's initial SETTINGS frame. Larger values allow peers to send more data per stream before waiting for WINDOW_UPDATE. Default: 1048576 (1 MiB). BB_H2_CONNECTION_WINDOW_SIZE Connection-level flow-control window size (bytes) advertised to HTTP/2 peers via an initial WINDOW_UPDATE on stream 0 after the SETTINGS handshake. Must be ≥ 65535 (the RFC default); values below that are silently ignored. Default: 4194304 (4 MiB). BB_H2_MAX_CONCURRENT_STREAMS Maximum number of HTTP/2 streams the server accepts at the same time per connection, advertised to peers in the initial SETTINGS frame (RFC 7540 §6.5.2 — SETTINGS_MAX_CONCURRENT_STREAMS, identifier 0x0003). Incoming streams that would exceed this limit receive RST_STREAM REFUSED_STREAM and are not dispatched to the application. Default: 100. BB_H2_ACTIVE_STREAMS Maximum number of HTTP/2 stream handlers that run concurrently per connection. When > 0, each connection gets an asyncio.Semaphore of this size; newly-spawned stream tasks queue for the semaphore instead of running immediately. This prevents one high-mux connection from monopolising the event loop and starving other connections on the same worker. 0 disables the semaphore (no cap beyond BB_H2_MAX_CONCURRENT_STREAMS). Default: 0 (disabled). BB_COMPRESSION_MIN_SIZE Minimum response body size in bytes below which :class:~blackbull.middleware.compression.Compression skips compression entirely. Raising this threshold under load reduces CPU pressure at the cost of slightly larger small responses. Default: 100. BB_COMPRESSION_EXECUTOR_THRESHOLD Body size in bytes above which compression is offloaded to a thread-pool executor so the event loop can continue processing other requests during the (CPU-heavy) compress call. 0 always compresses on the event loop (disables offloading). Default: 65536 (64 KiB). BB_COMPRESSION_MAX_INFLIGHT Maximum number of compression offloads allowed to be running concurrently in the asyncio default thread pool. When at or above this cap, additional eligible responses are served uncompressed rather than queued — bounded fall-back rather than unbounded queue growth. Tied to executor size: setting this above os.cpu_count() + 4 provides no benefit (Python's default executor pool size). 0 disables backpressure (unbounded queue, pre-0.29 behaviour). Default: os.cpu_count() * 2. BB_FRAME_YIELD_EVERY Number of stream tasks spawned per connection before the frame loop inserts await asyncio.sleep(0) to let the event loop dispatch the queued tasks. Under burst traffic (e.g. 500 VUs all sending at once) the frame loop can process many HEADERS frames without yielding, which stalls all waiting tasks and inflates p99 latency. Yielding every N spawns caps the maximum synchronous run to N × ~50 µs regardless of burst size. 0 disables cooperative yielding (legacy behaviour). Default: 8.

Settings dataclass

Immutable snapshot of all runtime settings.

Construct via :func:get_settings rather than directly so that environment variables are read at the right time.

apply_event_loop_policy(cfg=None)

Install uvloop as the asyncio event loop policy if BB_UVLOOP=1.

Call this once before each asyncio.run() entry point. Safe to call multiple times (subsequent calls are no-ops when the policy is already set). If uvloop is not installed a warning is logged and the standard policy is kept; the server still starts.

get_settings() cached

Read environment variables and return an immutable :class:Settings.

Cached: first call parses env vars and builds the dataclass; subsequent calls return the same instance. Settings are server-process-wide configuration, not per-request data — there's no reason to re-parse os.environ on every request. Profile showed _int_env and _int_env_nonneg consuming ~5–6% of CPU in the HTTP/1.1 hot path before this cache.

Tests that mutate environment between cases must call :func:reset_settings_cache in their teardown.

reset_settings_cache()

Clear the cached :class:Settings.

Call this in test teardown if the test mutated env vars that :func:get_settings reads. Without this, the cached settings reflect whatever environment was visible the first time get_settings() ran in the process.