Skip to content
Open
Show file tree
Hide file tree
Changes from 143 commits
Commits
Show all changes
164 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
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
b495564
Add Elastic Defensiveness (#899)
AbdulRahmanAlHamali Dec 4, 2025
a72d41f
dual circuit breaker implementation for switching between adaptive an…
adriangudas Dec 17, 2025
be0d3a2
Merge branch 'main' into pid-take-2
AbdulRahmanAlHamali Dec 17, 2025
f8db2ef
fix demo, and delete redundant demo
AbdulRahmanAlHamali Dec 17, 2025
2dd2a87
fix linter failures
AbdulRahmanAlHamali Dec 17, 2025
6d593a3
rerun experiments
AbdulRahmanAlHamali Dec 18, 2025
70cff45
create a class instead of a module for ExperimentFlags
adriangudas Dec 19, 2025
2948cef
Revert "create a class instead of a module for ExperimentFlags"
adriangudas Dec 19, 2025
c53d656
Merge branch 'main' into pid-take-2
AbdulRahmanAlHamali Dec 19, 2025
7d09e32
rerun experiments
AbdulRahmanAlHamali Dec 19, 2025
ce5f917
Jan 5: add tests, and fix demo (#961)
adriangudas Jan 5, 2026
77cb2b0
Allow using the fiber scheduler if present (#972)
AbdulRahmanAlHamali Jan 12, 2026
7a4c941
Update PID controller to store and use last ideal error rate for impr…
Abuudiii Jan 16, 2026
89d8752
Added CSV and PNG files for all experiments ran.
Abuudiii Jan 19, 2026
d873937
Merge pull request #983 from Shopify/abdullah/fixed-redundant-ideal-e…
Abuudiii Jan 19, 2026
4b8c4c6
Optimized metric usage in adaptive circuit breaker (#985)
Abuudiii Jan 19, 2026
ebdd1f8
Add jitter to sleep (#990)
AbdulRahmanAlHamali Jan 21, 2026
e39603f
initial commit
adriangudas Jan 12, 2026
a753114
don't use a separate WorkerState object
adriangudas Jan 12, 2026
537204f
use a class variable so that it exists before the class instance is c…
adriangudas Jan 12, 2026
a33e259
re-run experiments
adriangudas Jan 12, 2026
a2e865f
clean up terminology
adriangudas Jan 12, 2026
97973a9
reduce code duplication in dual circuit breaker
adriangudas Jan 12, 2026
cef49bb
add experiments
adriangudas Jan 12, 2026
d8738ed
remove the ability to stop the update thread (for now)
adriangudas Jan 13, 2026
53f889d
attempt to fix tests
adriangudas Jan 15, 2026
e36461f
stop the thread if all circuit breakers are destroyed (fixes tests)
adriangudas Jan 15, 2026
0a495da
make sure initialize doesn't run twice
adriangudas Jan 16, 2026
af67ec2
add experiment results
adriangudas Jan 19, 2026
43c2b0b
working single thread implementation
adriangudas Jan 23, 2026
8837cad
merge in latest metrics optimizations, and fix tests
adriangudas Jan 23, 2026
440f4d0
revert changes to pid_controller_test
adriangudas Jan 23, 2026
2be4606
re-run experiments
adriangudas Jan 23, 2026
8ff192a
use fibers if SEMIAN_PID_CONTROLLER_USE_FIBERS is true
adriangudas Jan 23, 2026
02d22cb
add experiments
adriangudas Jan 23, 2026
6f4df13
Merge pull request #973 from Shopify/adriangudas/adaptive-cb-single-t…
adriangudas Feb 2, 2026
c9eef57
Add dead zone ratio to PID controller for noise suppression (#1032)
Abuudiii Feb 27, 2026
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 @@ -576,6 +576,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
175 changes: 175 additions & 0 deletions examples/dual_circuit_breaker_demo.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
#!/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
class ExperimentFlags
@enabled = false

def enable_adaptive!
@enabled = true
end

def disable_adaptive!
@enabled = false
end

def 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],
)

experiment_flags = ExperimentFlags.new
Semian::DualCircuitBreaker.adaptive_circuit_breaker_selector(->(_resource) { experiment_flags.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

experiment_flags.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

experiment_flags.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?
experiment_flags.disable_adaptive!
puts " Switched to LEGACY"
else
experiment_flags.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)
7 changes: 4 additions & 3 deletions experiments/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

source "https://rubygems.org"

gemspec path: ".."

# Graphing library for visualization
gem "gruff", "~> 0.23"

# Alternative: Use rubyplot for pure Ruby plotting (no ImageMagick dependency)
# gem 'rubyplot', '~> 0.1'
gem "logger"
gem "csv"
12 changes: 12 additions & 0 deletions experiments/Gemfile.lock

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

Binary file removed experiments/example_output.png
Binary file not shown.
Loading
Loading