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.