Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
165 commits
Select commit Hold shift + click to select a range
4ef2649
add adaptive circuit breaker
AbdulRahmanAlHamali Sep 23, 2025
b19e198
add ability to enable adaptive circuit breaker
AbdulRahmanAlHamali Sep 26, 2025
824f229
fix constructor
AbdulRahmanAlHamali Oct 1, 2025
d14c397
address deadlock issues and use standard clamp function
AbdulRahmanAlHamali Oct 3, 2025
fec549a
add time freeze to tests
AbdulRahmanAlHamali Oct 3, 2025
23f6ec8
remove unnecessary stubbing
AbdulRahmanAlHamali Oct 3, 2025
ef7ec52
cap ideal error rate, and fix tests
AbdulRahmanAlHamali Oct 3, 2025
54e036e
more test fixing
AbdulRahmanAlHamali Oct 3, 2025
e037ead
use a discrete time window in pid controller
AbdulRahmanAlHamali Oct 3, 2025
3f59687
add unprotected ping
AbdulRahmanAlHamali Oct 3, 2025
ef08850
fix bugs and improve example
AbdulRahmanAlHamali Oct 3, 2025
4af0551
add ability to enable adaptive circuit breaker
AbdulRahmanAlHamali Sep 26, 2025
9ce68c6
use a discrete time window in pid controller
AbdulRahmanAlHamali Oct 3, 2025
9f4357e
Add P2 estimator
kris-gaudel Oct 14, 2025
6795d2e
Use P2 estimatori in PID
kris-gaudel Oct 14, 2025
e1e055c
Add results
kris-gaudel Oct 14, 2025
2c64138
Update variable names
kris-gaudel Oct 14, 2025
6bf05dd
Interpolate instead of round
kris-gaudel Oct 14, 2025
56c1db0
Add missing case
kris-gaudel Oct 21, 2025
2e43221
Remove platform
kris-gaudel Oct 21, 2025
645880d
Check if 1 hr has elapsed before changing ideal error rate (#800)
nirmitparikh8 Oct 20, 2025
4a6f482
Fix cold start issue (#809)
nirmitparikh8 Oct 21, 2025
86a733e
Remove outputs
kris-gaudel Oct 21, 2025
56ed006
testing different circuit breaking scenarios (#794)
Aguasvivas22 Oct 22, 2025
90cefeb
remove unintentional change
AbdulRahmanAlHamali Oct 22, 2025
f96df80
remove AI fluff
AbdulRahmanAlHamali Oct 22, 2025
3546cf1
use standard "error function" name instead of health
AbdulRahmanAlHamali Oct 22, 2025
814c5ed
remove pings from the error function
AbdulRahmanAlHamali Oct 22, 2025
18f4ed0
remove unintentionally commited change
AbdulRahmanAlHamali Oct 22, 2025
7961ecc
Inherit from `CircuitBreaker`
kris-gaudel Oct 22, 2025
2b6fc8d
Appease linter
kris-gaudel Oct 22, 2025
d40fa7a
Revert "Appease linter"
kris-gaudel Oct 22, 2025
129a595
Automatic lint
kris-gaudel Oct 22, 2025
f907d59
Comment out broken tests for ACB
kris-gaudel Oct 22, 2025
f68c1df
Skip broken test
kris-gaudel Oct 22, 2025
cbf0cab
Add behaviour
kris-gaudel Oct 22, 2025
b4caf65
Remove params not needed
kris-gaudel Oct 22, 2025
47dbd90
Merge pull request #812 from Shopify/kris-gaudel/cb-class-inherit
kris-gaudel Oct 22, 2025
45bef95
separate the service from the adapter (#817)
AbdulRahmanAlHamali Oct 23, 2025
ab36f2f
add test for gradual increase in errors
Aguasvivas22 Oct 23, 2025
bf47253
add test for oscillating errors
Aguasvivas22 Oct 23, 2025
f1e05f3
Merge pull request #819 from Shopify/pid-take-2-experiments-3
Aguasvivas22 Oct 23, 2025
ea7ed59
Merge pull request #818 from Shopify/pid-take-2-experiments-2
Aguasvivas22 Oct 23, 2025
a369832
Use floating point arithmetic
kris-gaudel Oct 23, 2025
222a8c6
Automatic lint
kris-gaudel Oct 23, 2025
d62da4d
Add demo script
kris-gaudel Oct 23, 2025
060279d
Unit test for P2 estimator
kris-gaudel Oct 23, 2025
f3e0873
Create helper class and refactor tests
Aguasvivas22 Oct 23, 2025
e55f44f
Merge pull request #823 from Shopify/cb_helper
Aguasvivas22 Oct 24, 2025
5a2553c
Clean up PID Controller, and fix its tests (#820)
AbdulRahmanAlHamali Oct 24, 2025
0600df6
add new tests for sudden spikes
adriangudas Oct 24, 2025
0e27576
add classic, and adaptive, tests for error spikes of different sizes
adriangudas Oct 24, 2025
196340d
Add p90 demo
kris-gaudel Oct 24, 2025
f191d09
Merge branch 'pid-take-2' into kris-gaudel/p2-estimator-tests
kris-gaudel Oct 24, 2025
61a0bf2
Merge pull request #822 from Shopify/kris-gaudel/p2-estimator-tests
kris-gaudel Oct 24, 2025
384adea
fix 0.01% typo (should be 1%)
adriangudas Oct 24, 2025
ab29e63
add "sudden error spikes" experiments and results #824
adriangudas Oct 24, 2025
d1e10e5
fix adaptive circuit breaker tests
AbdulRahmanAlHamali Oct 24, 2025
4fceeb4
Automatic fixes from rubucop
kris-gaudel Oct 27, 2025
5e29f52
fix adaptive circuit breaker tests (#828)
AbdulRahmanAlHamali Oct 27, 2025
caccf15
Remove clock dep inj
kris-gaudel Oct 27, 2025
966ed74
Merge branch 'main' into pid-take-2
AbdulRahmanAlHamali Oct 27, 2025
4f9de61
fix linting error (#830)
AbdulRahmanAlHamali Oct 27, 2025
41c8648
re-add attr_reader so experiments can report on rejection rate
adriangudas Oct 27, 2025
f872dd2
expose request_rate attribute (fixes PR 820) #832
adriangudas Oct 27, 2025
629ab08
Add thread timing utilization metrics for experiments (#829)
adriangudas Oct 28, 2025
00d11b4
add a lower bound integral windup test
Aguasvivas22 Oct 28, 2025
02df23f
Merge pull request #838 from Shopify/test_lower_bound_windup
Aguasvivas22 Oct 30, 2025
df37c03
Merge branch 'pid-take-2' into fix-adaptive-circuit-breaker-tests
kris-gaudel Oct 30, 2025
1bb1004
Semian per thread pattern
kris-gaudel Oct 30, 2025
8ae141b
Update gemfile
kris-gaudel Oct 30, 2025
86637e3
increase error threshold
kris-gaudel Oct 30, 2025
80549de
Update gemfile
kris-gaudel Oct 30, 2025
e5129f5
anti-windup
kris-gaudel Oct 30, 2025
bf43c4d
wind up test
kris-gaudel Oct 30, 2025
d67cbce
Clamp on saturation
kris-gaudel Oct 30, 2025
1dd0fdb
Fix test
kris-gaudel Oct 30, 2025
f2f662d
Add an experiment with multiple services and latency degradation of o…
AbdulRahmanAlHamali Oct 31, 2025
88dccaf
Update images
kris-gaudel Oct 31, 2025
2ad64db
Fix "error P" reporting in summary output (#839)
adriangudas Oct 31, 2025
0860b25
Cleanup parameters
kris-gaudel Oct 31, 2025
dc563e8
Address race condition
kris-gaudel Oct 31, 2025
3a6470e
Clean up comments
kris-gaudel Oct 31, 2025
b1b0378
notify on state transitions and on controller updates
Aguasvivas22 Oct 30, 2025
816b123
Same ideal error
kris-gaudel Oct 31, 2025
28eb197
Merge branch 'pid-take-2' into kris-gaudel/diff-semians-diff-threads
kris-gaudel Oct 31, 2025
428ffff
subscribe to notificaton to replace polling thread
Aguasvivas22 Oct 31, 2025
7097171
Merge pull request #849 from Shopify/notify_adapative_changes
Aguasvivas22 Oct 31, 2025
efed14a
Merge branch 'pid-take-2' into fix-adaptive-circuit-breaker-tests
kris-gaudel Oct 31, 2025
eb990bd
add vertical lines on classic CB state changes and more helper flexib…
Aguasvivas22 Nov 3, 2025
5509817
Merge pull request #853 from Shopify/recalc_error_rate
Aguasvivas22 Nov 3, 2025
11e99e3
Merge branch 'pid-take-2' into fix-adaptive-circuit-breaker-tests
kris-gaudel Nov 3, 2025
cfd3222
Lint, fix Fernando's tests to not use mock clock
kris-gaudel Nov 3, 2025
10aca61
Merge pull request #845 from Shopify/fix-adaptive-circuit-breaker-tests
kris-gaudel Nov 3, 2025
abcd264
Merge branch 'pid-take-2' into kris-gaudel/integral-windup-v2
kris-gaudel Nov 3, 2025
60dfcb6
Add comment
kris-gaudel Nov 3, 2025
dac8441
Merge pull request #847 from Shopify/kris-gaudel/integral-windup-v2
kris-gaudel Nov 3, 2025
fdd251e
Merge branch 'pid-take-2' into kris-gaudel/diff-semians-diff-threads
kris-gaudel Nov 3, 2025
4e8f39c
Revert error threshold
kris-gaudel Nov 4, 2025
da357cc
Add service name
kris-gaudel Nov 4, 2025
096c11f
Remove images
kris-gaudel Nov 4, 2025
8865e6c
Monitoring
kris-gaudel Nov 5, 2025
67ee84d
Add runtime dep to suppress warning msg
kris-gaudel Nov 5, 2025
5fcba93
Update lock file
kris-gaudel Nov 5, 2025
804d8d4
Update lock
kris-gaudel Nov 5, 2025
ff910f2
Remove dep from main semian
kris-gaudel Nov 5, 2025
ba9269f
Only add logger dep to experiments
kris-gaudel Nov 5, 2025
0cf0d43
Remove `window_number`
kris-gaudel Nov 5, 2025
90dd815
Add image back
kris-gaudel Nov 5, 2025
861e89c
Fix aggregate metrics
kris-gaudel Nov 5, 2025
2cf2f23
Merge pull request #846 from Shopify/kris-gaudel/diff-semians-diff-th…
kris-gaudel Nov 5, 2025
cb2690f
Add automated workflow for pid (#855)
nirmitparikh8 Nov 7, 2025
83779e3
Introduce a slow query experiment (#857)
AbdulRahmanAlHamali Nov 10, 2025
fe69b9f
Commit experiment result tables and fix bug in experiment helper (#872)
AbdulRahmanAlHamali Nov 14, 2025
f32fc05
Replace p90 and P2Estimator with Simple Exponential Smoother (SES) (#…
kris-gaudel Nov 20, 2025
a2fc6fb
Remove Throughput and Duration Graphs
Aguasvivas22 Nov 20, 2025
86bd5fd
regenerating graphs
Aguasvivas22 Nov 20, 2025
3668177
add test which holds the error rate near the target error rate
Aguasvivas22 Nov 20, 2025
5c17afc
Merge pull request #884 from Shopify/remove_throughput_duration_graphs
Aguasvivas22 Nov 20, 2025
447faeb
Merge pull request #887 from Shopify/near_target_error_rate
Aguasvivas22 Nov 21, 2025
60ee6a5
improve experiement visualization (#892)
AbdulRahmanAlHamali Nov 25, 2025
8fbfeec
use lambda to implement dual circuit breaker
adriangudas Nov 25, 2025
f70a232
fix tests
adriangudas Nov 26, 2025
a7d7a57
Sliding window implementation for PID controller (#874)
AbdulRahmanAlHamali Nov 26, 2025
4c34eff
fix experiments that were not working (#896)
AbdulRahmanAlHamali Nov 27, 2025
6a3963d
Remove unnecessary comments
kris-gaudel Nov 27, 2025
d1d1d28
Remove unnecessary variables and init smoother to 5%
kris-gaudel Nov 27, 2025
deceebb
Update experiment results
kris-gaudel Nov 27, 2025
facd604
Merge pull request #897 from Shopify/kris-gaudel/set-initial-er
kris-gaudel Nov 28, 2025
3840787
class method to allow setting a global selector for adaptive config
adriangudas Dec 1, 2025
4dd3dc2
remove unneeded methods that are no longer used
adriangudas Dec 1, 2025
8461f90
allow adaptive_circuit_breaker_selector to be unset
adriangudas Dec 2, 2025
5a7d114
make dynamic configuration call only on acquire
adriangudas Dec 2, 2025
db496a0
require resource to be provided
adriangudas Dec 2, 2025
3b22d16
fix: avoid an extra lookup to use_adaptive in metrics
adriangudas Dec 2, 2025
90e8144
various fixes
adriangudas Dec 2, 2025
7eaeb97
nit: cleanup
adriangudas Dec 2, 2025
757ec08
fix logging to both circuit breakers at the same time
adriangudas Dec 2, 2025
b495564
Add Elastic Defensiveness (#899)
AbdulRahmanAlHamali Dec 4, 2025
be42433
use logic from both acquire methods in dual_circuit_breaker
adriangudas Dec 4, 2025
4e34d7b
fix last error tracking test by using properly scoped exceptions vari…
adriangudas Dec 4, 2025
afd0314
fix demos
adriangudas Dec 5, 2025
0b1879d
remove unused attr_reader for name
adriangudas Dec 8, 2025
1fb0caa
nit: typo fix
adriangudas Dec 8, 2025
b2bea2d
nit: remove TODO comment
adriangudas Dec 8, 2025
c12f983
Merge branch 'pid-take-2' into adriangudas/adaptive-semian-feature-fl…
adriangudas Dec 8, 2025
245eaaf
don't return if variables are nil (dangerous)
adriangudas Dec 8, 2025
11ed33a
address PR comments
adriangudas Dec 9, 2025
30921b8
make active_circuit_breaker readable for tests
adriangudas Dec 9, 2025
c1d450e
improve track both breakers test with assert_equal
adriangudas Dec 9, 2025
1cdb33d
replace "legacy" with "classic", notify on change in cb type
kris-gaudel Dec 9, 2025
523cff7
replace legacy with classic
kris-gaudel Dec 9, 2025
6740875
notify on change in cb type, remove verbose comments, and refactor `h…
kris-gaudel Dec 9, 2025
5d5b75e
metrics functions for dcb example
kris-gaudel Dec 9, 2025
d4000b5
have error handling parity between classic and adaptive
AbdulRahmanAlHamali Dec 10, 2025
88d3fb1
fix exceptions parameter passing
kris-gaudel Dec 10, 2025
0d348b1
reference as instance variable
kris-gaudel Dec 10, 2025
9342a19
use acquire directly from circuit breakers
AbdulRahmanAlHamali Dec 10, 2025
8d56a3e
Merge branch 'adriangudas/adaptive-semian-feature-flag-gate' of https…
AbdulRahmanAlHamali Dec 10, 2025
714c105
fix dcb test and remove verbose content
kris-gaudel Dec 10, 2025
0d97b5e
fix double counting of errors
kris-gaudel Dec 10, 2025
22eb598
undo the move of method to public
AbdulRahmanAlHamali Dec 15, 2025
5c9d9d0
make active_breaker_type public
AbdulRahmanAlHamali Dec 16, 2025
f23b7fa
rerun experiments
AbdulRahmanAlHamali Dec 16, 2025
25be568
add back-reference for protected resource object to circuit breaker s…
Aguasvivas22 Dec 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions .github/workflows/automated-experiment-result-checker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
name: "Automated Experiment Result Checker"

# yamllint disable-line rule:truthy
on:
pull_request:
types: [opened, reopened, synchronize]

concurrency:
group: ${{ github.ref }}-automated-experiment-result-checker
cancel-in-progress: true

permissions:
contents: write
pull-requests: write
jobs:
automated-experiment-result-checker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0

- name: Check for updated experiment result graphs and tables
run: |
set -e
cd "$(git rev-parse --show-toplevel)"

# TODO: Include lower bound windup experiment once we have a way to make it run in a reasonable time.
# Find all PNGs and CSV files, excluding those with "windup" in their filename
mapfile -t all_pngs < <(find experiments/results/main_graphs -type f -name '*.png' ! -name '*windup*.png' | sort)
mapfile -t all_csvs < <(find experiments/results/csv -type f -name '*.csv' ! -name '*windup*.csv' 2>/dev/null | sort)

# Combine all result files
all_files=("${all_pngs[@]}" "${all_csvs[@]}")

# Find all changed PNGs and CSVs in the latest commit
mapfile -t changed_files < <(git diff --name-only --diff-filter=AM HEAD~1..HEAD | grep -E '^experiments/results/main_graphs/.*\.png$|^experiments/results/csv/.*\.csv$' | grep -v windup | sort)

# Report any files that are not updated in the latest commit
declare -a not_updated=()
for file in "${all_files[@]}"; do
if ! printf "%s\n" "${changed_files[@]}" | grep -qx "$file"; then
not_updated+=("$file")
fi
done

if [ ${#not_updated[@]} -gt 0 ]; then
echo "❌ The following result files have NOT been updated in the latest commit:"
for f in "${not_updated[@]}"; do
echo " - $f"
done
echo ""
echo "Every commit must update all non-windup experiment result graphs and CSV files. You may be missing updates."
echo "Run:"
echo ""
echo " cd experiments"
echo " bundle install"
echo " bundle exec ruby run_all_experiments.rb"
echo ""
echo "Commit the updated graphs and CSV files to resolve this check."
exit 1
fi

echo "✅ All non-windup experiment result graphs and CSV files are up to date for this commit!"



2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
branches: [ '**' ]
workflow_call:

concurrency:
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ group :test do
gem "pry-byebug", require: false
gem "toxiproxy"
gem "webrick"
gem "rubystats"

gem "bigdecimal"
gem "mutex_m"
Expand Down
5 changes: 5 additions & 0 deletions Gemfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,52 @@ It is possible to disable Circuit Breaker with environment variable
For more information about configuring these parameters, please read
[this post](https://shopify.engineering/circuit-breaker-misconfigured).

#### Adaptive Circuit Breaker (Experimental)

Semian also includes an experimental adaptive circuit breaker that uses a [PID controller](https://en.wikipedia.org/wiki/Proportional%E2%80%93integral%E2%80%93derivative_controller)
to dynamically adjust the rejection rate based on real-time error rates. Unlike the
traditional circuit breaker with fixed thresholds, the adaptive circuit breaker continuously
monitors error rates and adjusts its behavior accordingly.

##### How It Works

The adaptive circuit breaker uses the error function:
```
P = (error_rate - ideal_error_rate) - (1 - (error_rate - ideal_error_rate)) * rejection_rate
```

This formula ensures that:
- Rejection rate increases when the service is unhealthy
- Rejection rate decreases when the service recovers
- The system finds an equilibrium that protects against cascading failures while allowing recovery

##### Adaptive Circuit Breaker Configuration

To enable the adaptive circuit breaker, simply set:

- **adaptive_circuit_breaker**. Enable adaptive circuit breaker instead of traditional. Defaults to `false`.

Example configuration:
```ruby
Semian.register(
:my_service,
adaptive_circuit_breaker: true, # Use adaptive instead of traditional
bulkhead: false # Can be combined with bulkhead
)
```

The adaptive circuit breaker uses carefully tuned internal parameters based on extensive testing:
- PID controller gains optimized for stability and responsiveness
- 10-second window for rate calculations
- 1-hour history for ideal error rate calculation (p90)
- 1-second interval for background health checks

The adaptive circuit breaker can be disabled with the environment variable
`SEMIAN_ADAPTIVE_CIRCUIT_BREAKER_DISABLED=1`.

**Note**: When `adaptive_circuit_breaker: true` is set, traditional circuit breaker
parameters (`error_threshold`, `error_timeout`, etc.) are ignored.

### Bulkheading

For some applications, circuit breakers are not enough. This is best illustrated
Expand Down
174 changes: 174 additions & 0 deletions examples/dual_circuit_breaker_demo.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

require "bundler/setup"
require "semian"

# Example: Dual Circuit Breaker Demo
# This demonstrates how to use both legacy and adaptive circuit breakers
# simultaneously, switching between them at runtime based on a callable.

# Simulate a feature flag that can be toggled
module ExperimentFlags
@enabled = false

def self.enable_adaptive!
@enabled = true
end

def self.disable_adaptive!
@enabled = false
end

def self.use_adaptive_circuit_breaker?
@enabled
end
end

# Helper function to print state of all Semian objects between each phase
def print_semian_state
puts "\n=== Semian Resources State ===\n"
Semian.resources.values.each do |resource|
puts "Resource: #{resource.name}"

# Bulkhead info
if resource.bulkhead
puts " Bulkhead: tickets=#{resource.tickets}, count=#{resource.count}"
else
puts " Bulkhead: disabled"
end

# Circuit breaker info
cb = resource.circuit_breaker
if cb.nil?
puts " Circuit Breaker: disabled"
elsif cb.is_a?(Semian::DualCircuitBreaker)
puts " Circuit Breaker: DualCircuitBreaker"
metrics = cb.metrics
puts " Active: #{metrics[:active]}"
puts " Classic: state=#{metrics[:classic][:state]}, open=#{metrics[:classic][:open]}, half_open=#{metrics[:classic][:half_open]}"
puts " Adaptive: rejection_rate=#{metrics[:adaptive][:rejection_rate]}, error_rate=#{metrics[:adaptive][:error_rate]}"
elsif cb.is_a?(Semian::AdaptiveCircuitBreaker)
puts " Circuit Breaker: AdaptiveCircuitBreaker"
puts " open=#{cb.open?}, closed=#{cb.closed?}, half_open=#{cb.half_open?}"
else
puts " Circuit Breaker: Legacy"
puts " state=#{cb.state&.value}, open=#{cb.open?}, closed=#{cb.closed?}, half_open=#{cb.half_open?}"
puts " last_error=#{cb.last_error&.class}"
end
puts ""
end
puts "=== END STATE OUTPUT ===\n\n"
end

# Register a resource with dual circuit breaker mode
resource = Semian.register(
:my_service,
# Enable dual circuit breaker mode
dual_circuit_breaker: true,

# Legacy circuit breaker parameters (required)
success_threshold: 2,
error_threshold: 3,
error_timeout: 10,

# Adaptive circuit breaker parameters (optional, has defaults)
seed_error_rate: 0.01,

# Common parameters
tickets: 5,
timeout: 0.5,
exceptions: [RuntimeError],
)

Semian::DualCircuitBreaker.adaptive_circuit_breaker_selector(->(_resource) { ExperimentFlags.use_adaptive_circuit_breaker? })

puts "=== Dual Circuit Breaker Demo ===\n\n"

# Helper to simulate service calls
def simulate_call(success: true)
if success
"Success!"
else
raise "Service error"
end
end

# Test with legacy circuit breaker (use_adaptive returns false)
puts "Phase 1: Using LEGACY circuit breaker (use_adaptive=false)"
puts "The first 3 requests will succeed, the rest will fail."
puts "-" * 50

ExperimentFlags.disable_adaptive!

10.times do |i|
result = Semian[:my_service].acquire do
simulate_call(success: i < 3) # First 3 succeed, rest fail
end
puts " Request #{i + 1}: #{result}"
rescue => e
puts " Request #{i + 1}: Failed - #{e.class.name}: #{e.message}"
end

print_semian_state

# Reset both circuit breakers
puts "\n" + "=" * 50
puts "Resetting circuit breakers..."
resource.circuit_breaker.reset

# Test with adaptive circuit breaker (use_adaptive returns true)
puts "\nPhase 2: Using ADAPTIVE circuit breaker (use_adaptive=true)"
puts "The first 3 requests will succeed, then the rest will be failures."
puts "The adaptive circuit breaker is not expected to open yet."
puts "-" * 50

ExperimentFlags.enable_adaptive!

10.times do |i|
begin
result = Semian[:my_service].acquire do
simulate_call(success: i < 3) # First 3 succeed, rest fail
end
puts " Request #{i + 1}: #{result}"
rescue => e
puts " Request #{i + 1}: Failed - #{e.class.name}: #{e.message}"
end
sleep 0.05 # Small delay to see adaptive behavior
end

print_semian_state

# Demonstrate dynamic switching
puts "\n" + "=" * 50
puts "Phase 3: Dynamic switching between circuit breakers"
puts "-" * 50

5.times do |i|
# Toggle every 2 requests
if i.even?
ExperimentFlags.disable_adaptive!
puts " Switched to LEGACY"
else
ExperimentFlags.enable_adaptive!
puts " Switched to ADAPTIVE"
end

begin
result = Semian[:my_service].acquire do
simulate_call(success: true)
end
puts " Request #{i + 1}: #{result}"
rescue => e
puts " Request #{i + 1}: Failed - #{e.class.name}"
end
end

puts "\n=== Demo Complete ===\n"
puts "Both circuit breakers tracked all requests, but only the active one"
puts "was used for decision-making based on the adaptive_circuit_breaker_selector callable."

print_semian_state

# Cleanup
Semian.destroy(:my_service)
Loading
Loading