Skip to content

Commit de8b4bf

Browse files
authored
Merge pull request #282 from Integration-Automation/feat/bulkhead
Add bulkhead concurrency isolation and rate-limit header parsing
2 parents 289409b + 2ff4c00 commit de8b4bf

15 files changed

Lines changed: 435 additions & 1 deletion

File tree

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
## Table of Contents
1515

16+
- [What's new (2026-06-21) — Bulkhead & Rate-Limit Headers](#whats-new-2026-06-21--bulkhead--rate-limit-headers)
1617
- [What's new (2026-06-21) — Streaming Latency Percentiles](#whats-new-2026-06-21--streaming-latency-percentiles)
1718
- [What's new (2026-06-21) — Service-Level Objectives (SLO)](#whats-new-2026-06-21--service-level-objectives-slo)
1819
- [What's new (2026-06-21) — Chaos Experiments](#whats-new-2026-06-21--chaos-experiments)
@@ -126,6 +127,12 @@
126127

127128
---
128129

130+
## What's new (2026-06-21) — Bulkhead & Rate-Limit Headers
131+
132+
Cap concurrency, honor server back-off. Full reference: [`docs/source/Eng/doc/new_features/v74_features_doc.rst`](docs/source/Eng/doc/new_features/v74_features_doc.rst).
133+
134+
- **`Bulkhead` / `next_delay` / `parse_retry_after` / `parse_ratelimit`** (`AC_bulkhead_run`, `AC_retry_after`): `resilience` recovers and `rate_limit` paces, but nothing capped *simultaneous* in-flight calls (a slow dependency could exhaust every worker) and the HTTP client ignored `Retry-After`/`RateLimit-*`. This adds a bulkhead (bounded-concurrency permit that sheds load with `BulkheadFullError` when full) and parsers for the server's advised delay (delta-seconds or HTTP-date). Non-blocking permit counting → deterministic, no threads in tests. Pure-stdlib.
135+
129136
## What's new (2026-06-21) — Streaming Latency Percentiles
130137

131138
Mergeable p99 for load/soak runs. Full reference: [`docs/source/Eng/doc/new_features/v73_features_doc.rst`](docs/source/Eng/doc/new_features/v73_features_doc.rst).

README/README_zh-CN.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
## 目录
1414

15+
- [本次更新 (2026-06-21) — 隔舱与速率限制标头](#本次更新-2026-06-21--隔舱与速率限制标头)
1516
- [本次更新 (2026-06-21) — 流式延迟百分位](#本次更新-2026-06-21--流式延迟百分位)
1617
- [本次更新 (2026-06-21) — 服务等级目标(SLO)](#本次更新-2026-06-21--服务等级目标slo)
1718
- [本次更新 (2026-06-21) — 混沌实验](#本次更新-2026-06-21--混沌实验)
@@ -125,6 +126,12 @@
125126

126127
---
127128

129+
## 本次更新 (2026-06-21) — 隔舱与速率限制标头
130+
131+
限制并发、遵守服务器退避。完整参考:[`docs/source/Zh/doc/new_features/v74_features_doc.rst`](../docs/source/Zh/doc/new_features/v74_features_doc.rst)
132+
133+
- **`Bulkhead` / `next_delay` / `parse_retry_after` / `parse_ratelimit`**(`AC_bulkhead_run``AC_retry_after`):`resilience` 恢复、`rate_limit` 调速,但没有任何东西限制*同时*进行的调用(缓慢依赖会耗尽所有 worker),且 HTTP 客户端忽略 `Retry-After`/`RateLimit-*`。本功能补上隔舱(满载时以 `BulkheadFullError` 卸除负载的 bounded-concurrency 许可)以及服务器建议延迟(delta 秒或 HTTP-date)的解析器。非阻塞许可计数 → 确定、测试免线程。纯标准库。
134+
128135
## 本次更新 (2026-06-21) — 流式延迟百分位
129136

130137
load/soak 测试的可合并 p99。完整参考:[`docs/source/Zh/doc/new_features/v73_features_doc.rst`](../docs/source/Zh/doc/new_features/v73_features_doc.rst)

README/README_zh-TW.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
## 目錄
1414

15+
- [本次更新 (2026-06-21) — 隔艙與速率限制標頭](#本次更新-2026-06-21--隔艙與速率限制標頭)
1516
- [本次更新 (2026-06-21) — 串流延遲百分位](#本次更新-2026-06-21--串流延遲百分位)
1617
- [本次更新 (2026-06-21) — 服務等級目標(SLO)](#本次更新-2026-06-21--服務等級目標slo)
1718
- [本次更新 (2026-06-21) — 混沌實驗](#本次更新-2026-06-21--混沌實驗)
@@ -125,6 +126,12 @@
125126

126127
---
127128

129+
## 本次更新 (2026-06-21) — 隔艙與速率限制標頭
130+
131+
限制並行、遵守伺服器退避。完整參考:[`docs/source/Zh/doc/new_features/v74_features_doc.rst`](../docs/source/Zh/doc/new_features/v74_features_doc.rst)
132+
133+
- **`Bulkhead` / `next_delay` / `parse_retry_after` / `parse_ratelimit`**(`AC_bulkhead_run``AC_retry_after`):`resilience` 復原、`rate_limit` 調速,但沒有任何東西限制*同時*進行的呼叫(緩慢相依會耗盡所有 worker),且 HTTP 用戶端忽略 `Retry-After`/`RateLimit-*`。本功能補上隔艙(滿載時以 `BulkheadFullError` 卸除負載的 bounded-concurrency 許可)以及伺服器建議延遲(delta 秒或 HTTP-date)的剖析器。非阻塞許可計數 → 具決定性、測試免執行緒。純標準函式庫。
134+
128135
## 本次更新 (2026-06-21) — 串流延遲百分位
129136

130137
load/soak 測試的可合併 p99。完整參考:[`docs/source/Zh/doc/new_features/v73_features_doc.rst`](../docs/source/Zh/doc/new_features/v73_features_doc.rst)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
Bulkhead & Rate-Limit Headers
2+
=============================
3+
4+
``resilience`` recovers from failures and ``rate_limit`` paces calls, but
5+
nothing capped the number of *simultaneous* in-flight calls to one resource
6+
(so a slow dependency could exhaust every worker), and the HTTP client read
7+
``Retry-After`` / ``RateLimit-*`` response headers but honored none of them.
8+
This adds a bulkhead (bounded-concurrency permit with load-shedding) and a
9+
parser for the server's advised delay.
10+
11+
Pure standard library (``threading`` + ``email.utils``); the permit counting is
12+
non-blocking (reject when full), so it is deterministic and CI-testable without
13+
spawning threads. Imports no ``PySide6``.
14+
15+
Headless API
16+
------------
17+
18+
.. code-block:: python
19+
20+
from je_auto_control import Bulkhead, BulkheadFullError, next_delay
21+
22+
payments = Bulkhead(max_concurrent=4, name="payments")
23+
try:
24+
result = payments.run(call_payment_api, order)
25+
except BulkheadFullError:
26+
defer(order) # shed load instead of piling on
27+
28+
# honor the server's back-off after an HTTP call
29+
wait = next_delay(response) # from Retry-After / RateLimit-* headers
30+
if wait:
31+
sleep(wait)
32+
33+
``Bulkhead`` caps simultaneous holders to ``max_concurrent`` — ``try_enter`` /
34+
``release``, a context manager, and ``run(func)`` all reject (``BulkheadFullError``)
35+
when full, isolating one slow dependency from exhausting the rest.
36+
``parse_retry_after`` understands both the delta-seconds and HTTP-date forms;
37+
``parse_ratelimit`` reads the ``RateLimit-Limit/Remaining/Reset`` convention;
38+
``next_delay`` combines them into the wait a flow should observe after a
39+
``429`` / ``503``.
40+
41+
Executor commands
42+
-----------------
43+
44+
``AC_bulkhead_run`` runs an ``actions`` list under a named bulkhead (``name``,
45+
``max_concurrent``) and returns ``{entered, in_flight, record?}``.
46+
``AC_retry_after`` takes an HTTP ``response`` (``{status, headers}``) and returns
47+
``{delay}``. Both are exposed as MCP tools (``ac_bulkhead_run`` /
48+
``ac_retry_after``) and as Script Builder commands under **Flow**.

docs/source/Eng/eng_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ Comprehensive guides for all AutoControl features.
9696
doc/new_features/v71_features_doc
9797
doc/new_features/v72_features_doc
9898
doc/new_features/v73_features_doc
99+
doc/new_features/v74_features_doc
99100
doc/ocr_backends/ocr_backends_doc
100101
doc/observability/observability_doc
101102
doc/operations_layer/operations_layer_doc
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
隔艙與速率限制標頭
2+
=================
3+
4+
``resilience`` 從失敗中復原,``rate_limit`` 為呼叫調速,但沒有任何東西能限制對單一資源*同時*進行
5+
的呼叫數(因此一個緩慢的相依會耗盡所有 worker),而 HTTP 用戶端會讀取 ``Retry-After`` /
6+
``RateLimit-*`` 回應標頭卻不予理會。本功能補上一個隔艙(bounded-concurrency 許可,含負載卸除)
7+
以及一個解析伺服器建議延遲的剖析器。
8+
9+
純標準函式庫(``threading`` + ``email.utils``);許可計數為非阻塞(滿載即拒絕),因此具決定性、
10+
不需開執行緒即可在 CI 測試。不匯入 ``PySide6``。
11+
12+
無頭 API
13+
--------
14+
15+
.. code-block:: python
16+
17+
from je_auto_control import Bulkhead, BulkheadFullError, next_delay
18+
19+
payments = Bulkhead(max_concurrent=4, name="payments")
20+
try:
21+
result = payments.run(call_payment_api, order)
22+
except BulkheadFullError:
23+
defer(order) # 卸除負載而非繼續堆積
24+
25+
# HTTP 呼叫後遵守伺服器的退避
26+
wait = next_delay(response) # 來自 Retry-After / RateLimit-* 標頭
27+
if wait:
28+
sleep(wait)
29+
30+
``Bulkhead`` 把同時持有者上限設為 ``max_concurrent`` —— ``try_enter`` / ``release``、context
31+
manager 與 ``run(func)`` 在滿載時皆拒絕(``BulkheadFullError``),把一個緩慢相依與其餘隔離。
32+
``parse_retry_after`` 同時理解 delta 秒數與 HTTP-date 兩種形式;``parse_ratelimit`` 讀取
33+
``RateLimit-Limit/Remaining/Reset`` 慣例;``next_delay`` 把它們結合成流程在 ``429`` / ``503`` 後
34+
應遵守的等待。
35+
36+
執行器命令
37+
----------
38+
39+
``AC_bulkhead_run`` 在具名隔艙(``name``、``max_concurrent``)下執行 ``actions`` 清單,回傳
40+
``{entered, in_flight, record?}``。``AC_retry_after`` 接受 HTTP ``response``(``{status, headers}``)
41+
回傳 ``{delay}``。兩者皆以 MCP 工具(``ac_bulkhead_run`` / ``ac_retry_after``)以及 Script Builder
42+
中 **Flow** 分類下的命令提供。

docs/source/Zh/zh_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ AutoControl 所有功能的完整使用指南。
9696
doc/new_features/v71_features_doc
9797
doc/new_features/v72_features_doc
9898
doc/new_features/v73_features_doc
99+
doc/new_features/v74_features_doc
99100
doc/ocr_backends/ocr_backends_doc
100101
doc/observability/observability_doc
101102
doc/operations_layer/operations_layer_doc

je_auto_control/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,10 @@
371371
)
372372
# Mergeable streaming latency digest + exact percentiles
373373
from je_auto_control.utils.percentiles import LatencyDigest, exact_percentiles
374+
# Bulkhead concurrency isolation + rate-limit header parsing
375+
from je_auto_control.utils.bulkhead import (
376+
Bulkhead, BulkheadFullError, next_delay, parse_ratelimit, parse_retry_after,
377+
)
374378
# Background popup/interrupt watchdog (unattended automation)
375379
from je_auto_control.utils.watchdog import (
376380
PopupWatchdog, WatchdogRule, default_popup_watchdog,
@@ -868,6 +872,8 @@ def start_autocontrol_gui(*args, **kwargs):
868872
"run_experiment",
869873
"BurnRule", "burn_alerts", "burn_rate", "default_burn_rules", "evaluate_slo",
870874
"LatencyDigest", "exact_percentiles",
875+
"Bulkhead", "BulkheadFullError", "next_delay", "parse_ratelimit",
876+
"parse_retry_after",
871877
# MCP server
872878
"AuditLogger", "HttpMCPServer", "MCPContent", "MCPPrompt",
873879
"MCPPromptArgument", "MCPResource", "MCPServer", "MCPTool",

je_auto_control/gui/script_builder/command_schema.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1575,6 +1575,24 @@ def _add_resilience_specs(specs: List[CommandSpec]) -> None:
15751575
),
15761576
description="Run 'actions' (JSON view) via a named circuit breaker.",
15771577
))
1578+
specs.append(CommandSpec(
1579+
"AC_bulkhead_run", "Flow", "Bulkhead (Bounded Concurrency)",
1580+
fields=(
1581+
FieldSpec("name", FieldType.STRING),
1582+
FieldSpec("max_concurrent", FieldType.INT, default=4),
1583+
FieldSpec("actions", FieldType.STRING,
1584+
placeholder="[[\"AC_click_mouse\", {...}]]"),
1585+
),
1586+
description="Run 'actions' (JSON view) under a named bulkhead permit.",
1587+
))
1588+
specs.append(CommandSpec(
1589+
"AC_retry_after", "Flow", "Parse Retry-After / RateLimit",
1590+
fields=(
1591+
FieldSpec("response", FieldType.STRING,
1592+
placeholder='{"status": 429, "headers": {"Retry-After": "30"}}'),
1593+
),
1594+
description="Server-advised wait (seconds) from an HTTP response; {delay}.",
1595+
))
15781596
specs.append(CommandSpec(
15791597
"AC_rate_limit", "Flow", "Rate Limit (Token Bucket)",
15801598
fields=(
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"""Bulkhead concurrency isolation + server rate-limit header parsing."""
2+
from je_auto_control.utils.bulkhead.bulkhead import (
3+
Bulkhead, BulkheadFullError, next_delay, parse_ratelimit, parse_retry_after,
4+
)
5+
6+
__all__ = [
7+
"Bulkhead", "BulkheadFullError", "next_delay", "parse_ratelimit",
8+
"parse_retry_after",
9+
]

0 commit comments

Comments
 (0)