Class: Stoplight::Infrastructure::Redis::Storage::State

Inherits:
Object
  • Object
show all
Defined in:
lib/stoplight/infrastructure/redis/storage/state.rb

Overview

Note:

Thread safety is guaranteed by Redis’s single-threaded execution model and the use of Lua scripts for atomic multistep operations.

Redis-backed state storage for a single circuit breaker.

Manages circuit breaker state transitions using Redis hashes and Lua scripts for atomic operations. Ensures notification deduplication across distributed processes - when multiple processes detect the same circuit condition, only one will receive true from transition methods.

All state is stored in a single Redis hash with fields:

  • locked_state: forced lock (UNLOCKED, LOCKED_GREEN, LOCKED_RED)

  • breached_at: timestamp (float) when circuit opened

  • recovery_scheduled_after: timestamp (float) when recovery probe allowed

  • recovery_started_at: timestamp (float) when recovery probe began

Examples:

Basic usage

state = State.new(
  clock: SystemClock.new,
  redis: Redis.new,
  scripting: Scripting.new(redis:),
  key_space: KeySpace.build(light_name: "payments", system_name: "main"),
  cool_off_time: 60
)

# Multiple processes may call this concurrently
if state.transition_to_color(Color::RED)
  # Only one process reaches here - send notification
  notifier.notify("payments", :opened)
end

See Also:

Instance Method Summary collapse

Constructor Details

#initialize(clock:, redis:, scripting:, key_space:, cool_off_time:) ⇒ State

Returns a new instance of State.



42
43
44
45
46
47
48
49
50
# File 'lib/stoplight/infrastructure/redis/storage/state.rb', line 42

def initialize(clock:, redis:, scripting:, key_space:, cool_off_time:)
  @redis = redis
  @scripting = scripting
  @key_space = key_space
  @clock = clock
  @cool_off_time = cool_off_time

  @state_key = key_space.key(:state)
end

Instance Method Details

#clearObject



73
74
75
76
77
# File 'lib/stoplight/infrastructure/redis/storage/state.rb', line 73

def clear
  redis.with do |client|
    client.del(state_key)
  end
end

#set_state(state) ⇒ Object



52
53
54
55
56
57
# File 'lib/stoplight/infrastructure/redis/storage/state.rb', line 52

def set_state(state)
  redis.with do |client|
    client.hset(state_key, "locked_state", state)
  end
  state
end

#state_snapshotObject



59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/stoplight/infrastructure/redis/storage/state.rb', line 59

def state_snapshot
  breached_at_raw, locked_state, recovery_scheduled_after_raw, recovery_started_at_raw = redis.with do |client|
    client.hmget(state_key, :breached_at, :locked_state, :recovery_scheduled_after, :recovery_started_at)
  end

  Domain::StateSnapshot.new(
    breached_at: breached_at_raw && clock.at(breached_at_raw.to_f),
    locked_state: locked_state || Stoplight::State::UNLOCKED,
    recovery_scheduled_after: recovery_scheduled_after_raw && clock.at(recovery_scheduled_after_raw.to_f),
    recovery_started_at: recovery_started_at_raw && clock.at(recovery_started_at_raw.to_f),
    time: clock.current_time
  )
end

#transition_to_color(color) ⇒ Object



79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/stoplight/infrastructure/redis/storage/state.rb', line 79

def transition_to_color(color)
  case color
  when Color::GREEN
    transition_to_green
  when Color::YELLOW
    transition_to_yellow
  when Color::RED
    transition_to_red
  else
    raise ArgumentError, "Invalid color: #{color}"
  end
end