specweave 1.0.28 → 1.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +39 -0
- package/package.json +1 -1
- package/plugins/specweave/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave/commands/check-hooks.md +43 -0
- package/plugins/specweave/hooks/lib/circuit-breaker.sh +381 -0
- package/plugins/specweave/hooks/lib/logging.sh +231 -0
- package/plugins/specweave/hooks/lib/metrics.sh +347 -0
- package/plugins/specweave/hooks/lib/semaphore.sh +216 -0
- package/plugins/specweave/hooks/universal/fail-fast-wrapper.sh +156 -22
- package/plugins/specweave/scripts/hook-health.sh +441 -0
- package/plugins/specweave-ado/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-alternatives/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-backend/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-confluent/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-cost-optimizer/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-diagrams/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-docs/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-figma/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-frontend/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-github/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-infrastructure/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-jira/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-kafka/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-kafka-streams/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-kubernetes/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-ml/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-mobile/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-n8n/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-payments/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-plugin-dev/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-release/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-testing/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-ui/.claude-plugin/plugin.json +1 -1
- /package/plugins/specweave/hooks/{hooks.json.bak → hooks.json} +0 -0
package/CLAUDE.md
CHANGED
|
@@ -1343,6 +1343,45 @@ echo '{"decision": "block", "reason": "Error message here"}'
|
|
|
1343
1343
|
exit 2
|
|
1344
1344
|
```
|
|
1345
1345
|
|
|
1346
|
+
### Hook Concurrency System (v1.0.30+)
|
|
1347
|
+
|
|
1348
|
+
**All hooks use proper concurrency primitives via `fail-fast-wrapper.sh`:**
|
|
1349
|
+
|
|
1350
|
+
**Semaphore** (`hooks/lib/semaphore.sh`):
|
|
1351
|
+
- Limits concurrent hooks to `HOOK_MAX_CONCURRENT` (default: 15)
|
|
1352
|
+
- Graceful degradation when slots unavailable (returns safe default)
|
|
1353
|
+
- Auto-cleanup of stale locks (>30s old)
|
|
1354
|
+
|
|
1355
|
+
**Circuit Breaker** (`hooks/lib/circuit-breaker.sh`):
|
|
1356
|
+
- Per-hook circuit breakers (not global!)
|
|
1357
|
+
- States: CLOSED → (5 failures) → OPEN → (30s) → HALF_OPEN → (3 successes) → CLOSED
|
|
1358
|
+
- Prevents cascade failures from broken hooks
|
|
1359
|
+
|
|
1360
|
+
**Metrics** (`hooks/lib/metrics.sh`):
|
|
1361
|
+
- Tracks success/failure/timeout/skipped per hook
|
|
1362
|
+
- Calculates latency percentiles (p50, p95, p99)
|
|
1363
|
+
- Health score (0-100) per hook
|
|
1364
|
+
|
|
1365
|
+
**Configuration:**
|
|
1366
|
+
```bash
|
|
1367
|
+
HOOK_MAX_CONCURRENT=15 # Max concurrent hooks
|
|
1368
|
+
HOOK_TIMEOUT=5 # Hook execution timeout (seconds)
|
|
1369
|
+
HOOK_DEBUG=1 # Enable debug logging
|
|
1370
|
+
HOOK_ACQUIRE_TIMEOUT_MS=3000 # Semaphore acquire timeout
|
|
1371
|
+
```
|
|
1372
|
+
|
|
1373
|
+
**Health Dashboard:**
|
|
1374
|
+
```bash
|
|
1375
|
+
bash plugins/specweave/scripts/hook-health.sh # Full dashboard
|
|
1376
|
+
bash plugins/specweave/scripts/hook-health.sh --status # Quick status
|
|
1377
|
+
bash plugins/specweave/scripts/hook-health.sh --reset # Reset circuit breakers
|
|
1378
|
+
```
|
|
1379
|
+
|
|
1380
|
+
**Root Cause of Process Storm (Fixed in v1.0.30):**
|
|
1381
|
+
The old system detected "process storms" (>25 concurrent processes) and blocked ALL hooks.
|
|
1382
|
+
This caused cascading failures where even safe hooks were blocked.
|
|
1383
|
+
The new system uses proper semaphore-based concurrency limiting with graceful degradation.
|
|
1384
|
+
|
|
1346
1385
|
---
|
|
1347
1386
|
|
|
1348
1387
|
## Quick Reference
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specweave",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.30",
|
|
4
4
|
"description": "Spec-driven development framework for Claude Code. AI-native workflow with living documentation, intelligent agents, and multilingual support (9 languages). Enterprise-grade traceability with permanent specs and temporary increments.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sw",
|
|
3
3
|
"description": "SpecWeave framework. Provides increment planning (PM, Architect, Tech Lead agents), specification generation, TDD workflow, living docs sync, and brownfield support. Essential for all SpecWeave projects.",
|
|
4
|
-
"version": "1.0.0
|
|
4
|
+
"version": "1.0.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "SpecWeave Team",
|
|
7
7
|
"url": "https://spec-weave.com"
|
|
@@ -185,7 +185,50 @@ Run with auto-fix: `/sw:check-hooks --fix`
|
|
|
185
185
|
### "Permission denied"
|
|
186
186
|
Check hook file permissions: `chmod +x plugins/*/hooks/*.sh`
|
|
187
187
|
|
|
188
|
+
## Quick Health Dashboard
|
|
189
|
+
|
|
190
|
+
For a quick visual dashboard of hook health, run:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
bash plugins/specweave/scripts/hook-health.sh
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Or use specific views:
|
|
197
|
+
```bash
|
|
198
|
+
bash plugins/specweave/scripts/hook-health.sh --status # Quick status
|
|
199
|
+
bash plugins/specweave/scripts/hook-health.sh --metrics # Detailed metrics
|
|
200
|
+
bash plugins/specweave/scripts/hook-health.sh --reset # Reset circuit breakers
|
|
201
|
+
bash plugins/specweave/scripts/hook-health.sh --clean # Clean stale state
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Concurrency System (v1.0.30+)
|
|
205
|
+
|
|
206
|
+
The hook system uses proper concurrency primitives:
|
|
207
|
+
|
|
208
|
+
### Semaphore
|
|
209
|
+
- Limits concurrent hook execution (default: 15)
|
|
210
|
+
- Graceful degradation when slots unavailable
|
|
211
|
+
- Automatic cleanup of stale locks
|
|
212
|
+
|
|
213
|
+
### Circuit Breaker
|
|
214
|
+
- Per-hook circuit breakers with 3 states:
|
|
215
|
+
- CLOSED: Normal operation
|
|
216
|
+
- OPEN: Too many failures, fail fast
|
|
217
|
+
- HALF_OPEN: Testing recovery
|
|
218
|
+
|
|
219
|
+
### Metrics
|
|
220
|
+
- Success/failure/timeout tracking
|
|
221
|
+
- Latency percentiles (p50, p95, p99)
|
|
222
|
+
- Health score calculation
|
|
223
|
+
|
|
224
|
+
### Configuration
|
|
225
|
+
Environment variables:
|
|
226
|
+
- `HOOK_MAX_CONCURRENT` - Max concurrent hooks (default: 15)
|
|
227
|
+
- `HOOK_TIMEOUT` - Hook execution timeout in seconds (default: 5)
|
|
228
|
+
- `HOOK_DEBUG` - Enable debug logging (1 = enabled)
|
|
229
|
+
|
|
188
230
|
## See Also
|
|
189
231
|
|
|
190
232
|
- Hook Health Check Architecture: `.specweave/increments/0037-project-specific-tasks/architecture/HOOK-HEALTH-CHECK-ARCHITECTURE.md`
|
|
191
233
|
- Hook Development Guide: `.specweave/docs/public/guides/hook-development.md`
|
|
234
|
+
- Concurrency Libraries: `plugins/specweave/hooks/lib/`
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# circuit-breaker.sh - Proper Circuit Breaker Pattern for SpecWeave Hooks
|
|
3
|
+
#
|
|
4
|
+
# PROBLEM SOLVED:
|
|
5
|
+
# The old circuit breaker was too simple - it just counted failures and blocked everything.
|
|
6
|
+
# This implementation follows the proper circuit breaker pattern with three states:
|
|
7
|
+
# - CLOSED: Normal operation, requests flow through
|
|
8
|
+
# - OPEN: Too many failures, requests are rejected immediately (fail-fast)
|
|
9
|
+
# - HALF-OPEN: Testing if system recovered, allows limited requests
|
|
10
|
+
#
|
|
11
|
+
# DESIGN:
|
|
12
|
+
# - Failure threshold before opening (default: 5 failures in 60s window)
|
|
13
|
+
# - Recovery timeout before half-open (default: 30s)
|
|
14
|
+
# - Success threshold to close (default: 3 successes in half-open)
|
|
15
|
+
# - Per-hook circuit breakers (not global)
|
|
16
|
+
# - Sliding window for failure counting
|
|
17
|
+
# - Automatic state transitions
|
|
18
|
+
#
|
|
19
|
+
# USAGE:
|
|
20
|
+
# source circuit-breaker.sh
|
|
21
|
+
#
|
|
22
|
+
# # Before executing hook:
|
|
23
|
+
# if cb_allow_request "hook-name"; then
|
|
24
|
+
# # Execute hook
|
|
25
|
+
# if hook_succeeded; then
|
|
26
|
+
# cb_record_success "hook-name"
|
|
27
|
+
# else
|
|
28
|
+
# cb_record_failure "hook-name"
|
|
29
|
+
# fi
|
|
30
|
+
# else
|
|
31
|
+
# # Circuit is open, return safe default
|
|
32
|
+
# fi
|
|
33
|
+
#
|
|
34
|
+
# v1.0.0 - Initial implementation (2025-12-17)
|
|
35
|
+
|
|
36
|
+
set -o pipefail
|
|
37
|
+
|
|
38
|
+
# === Configuration ===
|
|
39
|
+
CB_STATE_DIR="${SPECWEAVE_STATE_DIR:-.specweave/state}/circuit-breakers"
|
|
40
|
+
CB_FAILURE_THRESHOLD="${CB_FAILURE_THRESHOLD:-5}" # Failures before OPEN
|
|
41
|
+
CB_FAILURE_WINDOW_SEC="${CB_FAILURE_WINDOW_SEC:-60}" # Sliding window for counting failures
|
|
42
|
+
CB_RECOVERY_TIMEOUT_SEC="${CB_RECOVERY_TIMEOUT_SEC:-30}" # Time in OPEN before HALF-OPEN
|
|
43
|
+
CB_SUCCESS_THRESHOLD="${CB_SUCCESS_THRESHOLD:-3}" # Successes in HALF-OPEN to CLOSE
|
|
44
|
+
CB_DEBUG="${CB_DEBUG:-0}"
|
|
45
|
+
|
|
46
|
+
# States
|
|
47
|
+
CB_STATE_CLOSED="CLOSED"
|
|
48
|
+
CB_STATE_OPEN="OPEN"
|
|
49
|
+
CB_STATE_HALF_OPEN="HALF_OPEN"
|
|
50
|
+
|
|
51
|
+
# === Initialization ===
|
|
52
|
+
_cb_init() {
|
|
53
|
+
mkdir -p "$CB_STATE_DIR" 2>/dev/null || true
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# === Logging ===
|
|
57
|
+
_cb_log() {
|
|
58
|
+
[[ "$CB_DEBUG" == "1" ]] && echo "[CB $(date +%H:%M:%S)] $*" >&2
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# === State File Paths ===
|
|
62
|
+
_cb_state_file() {
|
|
63
|
+
local name="$1"
|
|
64
|
+
echo "$CB_STATE_DIR/${name}.state"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
_cb_failures_file() {
|
|
68
|
+
local name="$1"
|
|
69
|
+
echo "$CB_STATE_DIR/${name}.failures"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
_cb_successes_file() {
|
|
73
|
+
local name="$1"
|
|
74
|
+
echo "$CB_STATE_DIR/${name}.successes"
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# === Read/Write State ===
|
|
78
|
+
_cb_read_state() {
|
|
79
|
+
local name="$1"
|
|
80
|
+
local state_file
|
|
81
|
+
state_file=$(_cb_state_file "$name")
|
|
82
|
+
|
|
83
|
+
if [[ -f "$state_file" ]]; then
|
|
84
|
+
cat "$state_file" 2>/dev/null || echo "$CB_STATE_CLOSED"
|
|
85
|
+
else
|
|
86
|
+
echo "$CB_STATE_CLOSED"
|
|
87
|
+
fi
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
_cb_write_state() {
|
|
91
|
+
local name="$1"
|
|
92
|
+
local state="$2"
|
|
93
|
+
local state_file
|
|
94
|
+
state_file=$(_cb_state_file "$name")
|
|
95
|
+
|
|
96
|
+
_cb_init
|
|
97
|
+
echo "$state" > "$state_file" 2>/dev/null || true
|
|
98
|
+
_cb_log "State transition for $name: -> $state"
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# === Timestamp helpers ===
|
|
102
|
+
_cb_now() {
|
|
103
|
+
date +%s
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
_cb_file_mtime() {
|
|
107
|
+
local file="$1"
|
|
108
|
+
if [[ "$(uname)" == "Darwin" ]]; then
|
|
109
|
+
stat -f %m "$file" 2>/dev/null || echo "0"
|
|
110
|
+
else
|
|
111
|
+
stat -c %Y "$file" 2>/dev/null || echo "0"
|
|
112
|
+
fi
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# === Failure Tracking (Sliding Window) ===
|
|
116
|
+
_cb_count_recent_failures() {
|
|
117
|
+
local name="$1"
|
|
118
|
+
local failures_file
|
|
119
|
+
failures_file=$(_cb_failures_file "$name")
|
|
120
|
+
|
|
121
|
+
[[ ! -f "$failures_file" ]] && echo "0" && return
|
|
122
|
+
|
|
123
|
+
local now
|
|
124
|
+
now=$(_cb_now)
|
|
125
|
+
local window_start=$((now - CB_FAILURE_WINDOW_SEC))
|
|
126
|
+
local count=0
|
|
127
|
+
|
|
128
|
+
# Read failure timestamps and count those within window
|
|
129
|
+
while IFS= read -r timestamp; do
|
|
130
|
+
[[ -z "$timestamp" ]] && continue
|
|
131
|
+
if [[ "$timestamp" -ge "$window_start" ]]; then
|
|
132
|
+
count=$((count + 1))
|
|
133
|
+
fi
|
|
134
|
+
done < "$failures_file"
|
|
135
|
+
|
|
136
|
+
echo "$count"
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
_cb_add_failure() {
|
|
140
|
+
local name="$1"
|
|
141
|
+
local failures_file
|
|
142
|
+
failures_file=$(_cb_failures_file "$name")
|
|
143
|
+
|
|
144
|
+
_cb_init
|
|
145
|
+
local now
|
|
146
|
+
now=$(_cb_now)
|
|
147
|
+
|
|
148
|
+
# Append timestamp
|
|
149
|
+
echo "$now" >> "$failures_file" 2>/dev/null || true
|
|
150
|
+
|
|
151
|
+
# Cleanup old entries (keep only last 100)
|
|
152
|
+
if [[ -f "$failures_file" ]]; then
|
|
153
|
+
tail -100 "$failures_file" > "${failures_file}.tmp" 2>/dev/null && \
|
|
154
|
+
mv "${failures_file}.tmp" "$failures_file" 2>/dev/null || true
|
|
155
|
+
fi
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
_cb_clear_failures() {
|
|
159
|
+
local name="$1"
|
|
160
|
+
local failures_file
|
|
161
|
+
failures_file=$(_cb_failures_file "$name")
|
|
162
|
+
rm -f "$failures_file" 2>/dev/null || true
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
# === Success Tracking (Half-Open State) ===
|
|
166
|
+
_cb_count_successes() {
|
|
167
|
+
local name="$1"
|
|
168
|
+
local successes_file
|
|
169
|
+
successes_file=$(_cb_successes_file "$name")
|
|
170
|
+
|
|
171
|
+
[[ ! -f "$successes_file" ]] && echo "0" && return
|
|
172
|
+
|
|
173
|
+
wc -l < "$successes_file" 2>/dev/null | tr -d ' '
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
_cb_add_success() {
|
|
177
|
+
local name="$1"
|
|
178
|
+
local successes_file
|
|
179
|
+
successes_file=$(_cb_successes_file "$name")
|
|
180
|
+
|
|
181
|
+
_cb_init
|
|
182
|
+
echo "$(_cb_now)" >> "$successes_file" 2>/dev/null || true
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
_cb_clear_successes() {
|
|
186
|
+
local name="$1"
|
|
187
|
+
local successes_file
|
|
188
|
+
successes_file=$(_cb_successes_file "$name")
|
|
189
|
+
rm -f "$successes_file" 2>/dev/null || true
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
# === State Transitions ===
|
|
193
|
+
_cb_check_should_open() {
|
|
194
|
+
local name="$1"
|
|
195
|
+
local failure_count
|
|
196
|
+
failure_count=$(_cb_count_recent_failures "$name")
|
|
197
|
+
|
|
198
|
+
if [[ "$failure_count" -ge "$CB_FAILURE_THRESHOLD" ]]; then
|
|
199
|
+
_cb_log "$name: Failure threshold reached ($failure_count >= $CB_FAILURE_THRESHOLD)"
|
|
200
|
+
return 0 # Should open
|
|
201
|
+
fi
|
|
202
|
+
return 1 # Should not open
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
_cb_check_should_half_open() {
|
|
206
|
+
local name="$1"
|
|
207
|
+
local state_file
|
|
208
|
+
state_file=$(_cb_state_file "$name")
|
|
209
|
+
|
|
210
|
+
[[ ! -f "$state_file" ]] && return 1
|
|
211
|
+
|
|
212
|
+
local state_mtime
|
|
213
|
+
state_mtime=$(_cb_file_mtime "$state_file")
|
|
214
|
+
local now
|
|
215
|
+
now=$(_cb_now)
|
|
216
|
+
local age=$((now - state_mtime))
|
|
217
|
+
|
|
218
|
+
if [[ "$age" -ge "$CB_RECOVERY_TIMEOUT_SEC" ]]; then
|
|
219
|
+
_cb_log "$name: Recovery timeout reached (${age}s >= ${CB_RECOVERY_TIMEOUT_SEC}s)"
|
|
220
|
+
return 0 # Should transition to half-open
|
|
221
|
+
fi
|
|
222
|
+
return 1
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
_cb_check_should_close() {
|
|
226
|
+
local name="$1"
|
|
227
|
+
local success_count
|
|
228
|
+
success_count=$(_cb_count_successes "$name")
|
|
229
|
+
|
|
230
|
+
if [[ "$success_count" -ge "$CB_SUCCESS_THRESHOLD" ]]; then
|
|
231
|
+
_cb_log "$name: Success threshold reached ($success_count >= $CB_SUCCESS_THRESHOLD)"
|
|
232
|
+
return 0 # Should close
|
|
233
|
+
fi
|
|
234
|
+
return 1
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
# === Public API ===
|
|
238
|
+
|
|
239
|
+
# Check if request should be allowed
|
|
240
|
+
# Returns 0 if allowed, 1 if circuit is open
|
|
241
|
+
cb_allow_request() {
|
|
242
|
+
local name="${1:-default}"
|
|
243
|
+
|
|
244
|
+
_cb_init
|
|
245
|
+
|
|
246
|
+
local state
|
|
247
|
+
state=$(_cb_read_state "$name")
|
|
248
|
+
|
|
249
|
+
case "$state" in
|
|
250
|
+
"$CB_STATE_CLOSED")
|
|
251
|
+
_cb_log "$name: CLOSED - allowing request"
|
|
252
|
+
return 0
|
|
253
|
+
;;
|
|
254
|
+
|
|
255
|
+
"$CB_STATE_OPEN")
|
|
256
|
+
# Check if we should transition to half-open
|
|
257
|
+
if _cb_check_should_half_open "$name"; then
|
|
258
|
+
_cb_write_state "$name" "$CB_STATE_HALF_OPEN"
|
|
259
|
+
_cb_clear_successes "$name"
|
|
260
|
+
_cb_log "$name: OPEN -> HALF_OPEN - allowing test request"
|
|
261
|
+
return 0
|
|
262
|
+
fi
|
|
263
|
+
_cb_log "$name: OPEN - rejecting request (fail-fast)"
|
|
264
|
+
return 1
|
|
265
|
+
;;
|
|
266
|
+
|
|
267
|
+
"$CB_STATE_HALF_OPEN")
|
|
268
|
+
_cb_log "$name: HALF_OPEN - allowing test request"
|
|
269
|
+
return 0
|
|
270
|
+
;;
|
|
271
|
+
|
|
272
|
+
*)
|
|
273
|
+
# Unknown state, default to closed
|
|
274
|
+
_cb_write_state "$name" "$CB_STATE_CLOSED"
|
|
275
|
+
return 0
|
|
276
|
+
;;
|
|
277
|
+
esac
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
# Record successful request
|
|
281
|
+
cb_record_success() {
|
|
282
|
+
local name="${1:-default}"
|
|
283
|
+
|
|
284
|
+
local state
|
|
285
|
+
state=$(_cb_read_state "$name")
|
|
286
|
+
|
|
287
|
+
case "$state" in
|
|
288
|
+
"$CB_STATE_CLOSED")
|
|
289
|
+
# Clear any old failures on success
|
|
290
|
+
# (helps prevent lingering failures from keeping count high)
|
|
291
|
+
;;
|
|
292
|
+
|
|
293
|
+
"$CB_STATE_HALF_OPEN")
|
|
294
|
+
_cb_add_success "$name"
|
|
295
|
+
if _cb_check_should_close "$name"; then
|
|
296
|
+
_cb_write_state "$name" "$CB_STATE_CLOSED"
|
|
297
|
+
_cb_clear_failures "$name"
|
|
298
|
+
_cb_clear_successes "$name"
|
|
299
|
+
_cb_log "$name: HALF_OPEN -> CLOSED (recovered)"
|
|
300
|
+
fi
|
|
301
|
+
;;
|
|
302
|
+
esac
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
# Record failed request
|
|
306
|
+
cb_record_failure() {
|
|
307
|
+
local name="${1:-default}"
|
|
308
|
+
|
|
309
|
+
local state
|
|
310
|
+
state=$(_cb_read_state "$name")
|
|
311
|
+
|
|
312
|
+
_cb_add_failure "$name"
|
|
313
|
+
|
|
314
|
+
case "$state" in
|
|
315
|
+
"$CB_STATE_CLOSED")
|
|
316
|
+
if _cb_check_should_open "$name"; then
|
|
317
|
+
_cb_write_state "$name" "$CB_STATE_OPEN"
|
|
318
|
+
_cb_log "$name: CLOSED -> OPEN (too many failures)"
|
|
319
|
+
fi
|
|
320
|
+
;;
|
|
321
|
+
|
|
322
|
+
"$CB_STATE_HALF_OPEN")
|
|
323
|
+
# Any failure in half-open immediately opens circuit
|
|
324
|
+
_cb_write_state "$name" "$CB_STATE_OPEN"
|
|
325
|
+
_cb_clear_successes "$name"
|
|
326
|
+
_cb_log "$name: HALF_OPEN -> OPEN (failed during recovery)"
|
|
327
|
+
;;
|
|
328
|
+
esac
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
# Get circuit breaker status
|
|
332
|
+
cb_get_status() {
|
|
333
|
+
local name="${1:-default}"
|
|
334
|
+
|
|
335
|
+
_cb_init
|
|
336
|
+
|
|
337
|
+
local state
|
|
338
|
+
state=$(_cb_read_state "$name")
|
|
339
|
+
local failures
|
|
340
|
+
failures=$(_cb_count_recent_failures "$name")
|
|
341
|
+
local successes
|
|
342
|
+
successes=$(_cb_count_successes "$name")
|
|
343
|
+
|
|
344
|
+
echo "{\"name\":\"$name\",\"state\":\"$state\",\"failures\":$failures,\"successes\":$successes,\"failure_threshold\":$CB_FAILURE_THRESHOLD,\"recovery_timeout_sec\":$CB_RECOVERY_TIMEOUT_SEC}"
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
# Force reset circuit breaker
|
|
348
|
+
cb_reset() {
|
|
349
|
+
local name="${1:-default}"
|
|
350
|
+
|
|
351
|
+
_cb_write_state "$name" "$CB_STATE_CLOSED"
|
|
352
|
+
_cb_clear_failures "$name"
|
|
353
|
+
_cb_clear_successes "$name"
|
|
354
|
+
_cb_log "$name: Force reset to CLOSED"
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
# List all circuit breakers
|
|
358
|
+
cb_list_all() {
|
|
359
|
+
_cb_init
|
|
360
|
+
|
|
361
|
+
local result="["
|
|
362
|
+
local first=true
|
|
363
|
+
|
|
364
|
+
for state_file in "$CB_STATE_DIR"/*.state; do
|
|
365
|
+
[[ ! -f "$state_file" ]] && continue
|
|
366
|
+
|
|
367
|
+
local name
|
|
368
|
+
name=$(basename "$state_file" .state)
|
|
369
|
+
|
|
370
|
+
if [[ "$first" == "true" ]]; then
|
|
371
|
+
first=false
|
|
372
|
+
else
|
|
373
|
+
result="$result,"
|
|
374
|
+
fi
|
|
375
|
+
|
|
376
|
+
result="$result$(cb_get_status "$name")"
|
|
377
|
+
done
|
|
378
|
+
|
|
379
|
+
result="$result]"
|
|
380
|
+
echo "$result"
|
|
381
|
+
}
|