Predictable bulkhead isolation and bounded concurrency for Python asyncio.
Project description
Bulklink
Simple admission control. Strong isolation. Predictable behavior under load.
Bulklink is a small, typed, zero-dependency library for bulkhead isolation and
bounded concurrency in Python asyncio applications.
Current package version: 0.4.0. The documented 0.4.x public contract is stable.
What problem does it solve?
Without admission control, one slow dependency can attract hundreds of concurrent operations, consume connections and memory, and damage unrelated parts of an application.
Bulklink creates independent compartments:
from bulklink import AsyncBulkhead
payments = AsyncBulkhead(
label="payments",
parallelism=10,
waiting_room=50,
wait_limit=2.0,
)
reports = AsyncBulkhead(
label="reports",
parallelism=2,
waiting_room=5,
)
Slow reports can use at most two execution slots. They cannot consume the ten slots reserved for payments.
Quick start
async def send_payment(order: object) -> object:
async with payments.slot():
return await payment_api.send(order)
Or:
result = await payments.execute(payment_api.send, order)
Reject instead of waiting when immediate capacity is required:
result = await payments.execute_now(payment_api.send, order)
Use a shorter limit for one call without extending the bulkhead default:
result = await payments.execute_within(0.25, payment_api.send, order)
Respect an absolute request deadline measured with the event-loop clock:
loop = asyncio.get_running_loop()
result = await payments.execute_before(
loop.time() + 0.25,
payment_api.send,
order,
)
The deadline limits admission only. Once admitted, Bulklink does not cancel the protected operation when the deadline passes.
Or decorate an async function:
@payments
async def send_payment(order: object) -> object:
return await payment_api.send(order)
Behavior
For each bulkhead:
- up to
parallelismoperations may execute; - up to
waiting_roomoperations may wait in FIFO order; - an operation is rejected immediately when both areas are full;
- a waiting operation is rejected when
wait_limitexpires; - exceptions and task cancellation release capacity safely;
close()rejects queued and future operations without interrupting active work;wait_closed()waits until all active operations have released their slots;resize()changes capacity without cancelling active work or bypassing FIFO order;execute_before()rejects work whose absolute admission deadline has expired.
Graceful shutdown
await payments.close_and_wait()
close_and_wait() stops new admission, rejects queued work, and waits for operations
already running to finish. Cancelling the caller does not cancel protected operations.
Observe state transitions
from bulklink import BulkheadEvent
def record_event(event: BulkheadEvent) -> None:
print(event.kind.value, event.in_flight, event.waiting)
payments.add_event_handler(record_event)
Handlers are synchronous, run outside the coordinator lock, and receive immutable metadata only. They never receive operation arguments, results, or exceptions. Handler failures are reported through the event loop exception handler without changing bulkhead state.
Diagnose capacity pressure
report = await payments.capacity_report()
print(report.summary)
for finding in report.findings:
print(finding.severity.value, finding.message)
The report combines the current snapshot with cumulative admission history. It is immutable, conservative with small samples, and never changes the bulkhead configuration.
Measure activity between snapshots
before = await payments.status()
# Later
after = await payments.status()
interval = after.since(before)
print(interval.admitted)
print(interval.rejected)
print(interval.average_wait_seconds)
The interval is computed locally from immutable cumulative snapshots. Bulklink does not reset counters, retain historical windows, or create a background metrics task.
Change capacity safely
await payments.resize(20)
Increasing capacity admits queued operations in FIFO order. Reducing capacity never cancels active work; existing operations drain naturally before admission resumes at the new limit.
Manage named bulkheads together
from bulklink import BulkheadRegistry
registry = BulkheadRegistry()
payments = registry.create("payments", parallelism=10, waiting_room=20)
reports = registry.create("reports", parallelism=2)
await registry.close_and_wait()
The registry is optional. It enforces unique names, returns immutable ordered snapshots,
and coordinates shutdown without replacing direct AsyncBulkhead usage.
Designed to coexist with Relinker
Bulklink and Relinker solve different stages:
- Bulklink decides whether one operation may start;
- Relinker decides whether a failed operation should be attempted again.
Bulklink deliberately uses AsyncBulkhead, execute(), slot(), and status(),
rather than Relinker's policy, retry, result, budget, run_async(), and snapshot()
terminology.
See Using Bulklink with Relinker.
Non-goals
Bulklink does not provide retries, backoff, jitter, circuit breakers, HTTP-specific behavior, requests-per-second limits, or distributed coordination.
Development
python -m pip install -e ".[dev]"
./scripts/ci.sh
Documentation
- Documentation index
- Getting started
- Using Bulklink with Relinker
- Production checklist
- Capacity diagnostics
- Interval metrics
- Dynamic capacity
- Named bulkhead registry
- Architecture
License
MIT.
Validation and benchmarks
Bulklink is checked on Python 3.10 through 3.14 on Linux, with additional Windows and macOS validation. The suite includes deterministic race tests, generated model-oriented sequences, adversarial stress, executable examples, clean-wheel installation, and consumer-facing typing checks.
Run the complete local verification on Linux or macOS:
./scripts/ci.sh
Record a local performance baseline without enforcing unstable timing thresholds:
python -m benchmarks.run --output benchmark-results.json
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bulklink-0.4.0.tar.gz.
File metadata
- Download URL: bulklink-0.4.0.tar.gz
- Upload date:
- Size: 23.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eea64c550e3b299dc511cb9cfb26dda8dd716e87b9d9a267b600deca6919f754
|
|
| MD5 |
b606f2001de82f59fc67b5e3310fda2d
|
|
| BLAKE2b-256 |
685b3c74dca39fc1631fa983ae347260aaab33c0ac0651d2d72cc3ae205a72bb
|
Provenance
The following attestation bundles were made for bulklink-0.4.0.tar.gz:
Publisher:
release.yml on igors93/bulklink
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bulklink-0.4.0.tar.gz -
Subject digest:
eea64c550e3b299dc511cb9cfb26dda8dd716e87b9d9a267b600deca6919f754 - Sigstore transparency entry: 1755966827
- Sigstore integration time:
-
Permalink:
igors93/bulklink@278eaf5c6ecb83d66356cb16f53d602d02819346 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/igors93
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@278eaf5c6ecb83d66356cb16f53d602d02819346 -
Trigger Event:
push
-
Statement type:
File details
Details for the file bulklink-0.4.0-py3-none-any.whl.
File metadata
- Download URL: bulklink-0.4.0-py3-none-any.whl
- Upload date:
- Size: 26.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0006b392c58f8493cd0d6ed88b1f5d7f88713752b29eb86b95dfdea41b976f1d
|
|
| MD5 |
4754df57ee10d63bdf8d7c13954cc404
|
|
| BLAKE2b-256 |
dcd0f5045813bf56954fbdfcbc985d588c7c39b5a4fb3e56022b1f9044995350
|
Provenance
The following attestation bundles were made for bulklink-0.4.0-py3-none-any.whl:
Publisher:
release.yml on igors93/bulklink
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bulklink-0.4.0-py3-none-any.whl -
Subject digest:
0006b392c58f8493cd0d6ed88b1f5d7f88713752b29eb86b95dfdea41b976f1d - Sigstore transparency entry: 1755966846
- Sigstore integration time:
-
Permalink:
igors93/bulklink@278eaf5c6ecb83d66356cb16f53d602d02819346 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/igors93
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@278eaf5c6ecb83d66356cb16f53d602d02819346 -
Trigger Event:
push
-
Statement type: