Class: Stoplight::Infrastructure::Memory::Storage::State

Inherits:
Object
  • Object
show all
Includes:
Domain::_StateStore
Defined in:
lib/stoplight/infrastructure/memory/storage/state.rb,
sig/_private/stoplight/infrastructure/memory/storage/state.rbs

Overview

Thread-safe in-memory state storage for a single light.

Manages light state transitions and ensures notification deduplication through atomic transition detection. Each transition method returns whether the calling thread was first to trigger that transition, enabling exactly-once notification semantics.

Examples:

Basic usage

state = State.new(clock: SystemClock.new, cool_off_time: 60)

# Multiple threads may call this concurrently
if state.transition_to_color(Color::RED)
  # Only one thread reaches here - send notification
  notifier.notify(circuit_name, :opened)
end

Inspecting current state

snapshot = state.state_snapshot
snapshot.locked_state        # => "unlocked"
snapshot.breached_at         # => 2025-01-15 10:30:00 UTC
snapshot.recovery_scheduled_after  # => 2025-01-15 10:31:00 UTC

See Also:

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(clock:, cool_off_time:) ⇒ State

Returns a new instance of State.

Parameters:



32
33
34
35
36
37
# File 'lib/stoplight/infrastructure/memory/storage/state.rb', line 32

def initialize(clock:, cool_off_time:)
  @locked_state = Stoplight::State::UNLOCKED
  @mutex = Thread::Mutex.new
  @clock = clock
  @cool_off_time = cool_off_time
end

Instance Attribute Details

#breached_atTime?

Returns the value of attribute breached_at.

Returns:

  • (Time, nil)


90
91
92
# File 'lib/stoplight/infrastructure/memory/storage/state.rb', line 90

def breached_at
  @breached_at
end

#clockDomain::_Clock (readonly)

Returns the value of attribute clock.

Returns:



92
93
94
# File 'lib/stoplight/infrastructure/memory/storage/state.rb', line 92

def clock
  @clock
end

#cool_off_timetimestamp (readonly)

Returns the value of attribute cool_off_time.

Returns:

  • (timestamp)


93
94
95
# File 'lib/stoplight/infrastructure/memory/storage/state.rb', line 93

def cool_off_time
  @cool_off_time
end

#locked_statestate

Returns the value of attribute locked_state.

Returns:

  • (state)


86
87
88
# File 'lib/stoplight/infrastructure/memory/storage/state.rb', line 86

def locked_state
  @locked_state
end

#mutexMutex (readonly)

Returns the value of attribute mutex.

Returns:

  • (Mutex)


91
92
93
# File 'lib/stoplight/infrastructure/memory/storage/state.rb', line 91

def mutex
  @mutex
end

#recovered_atTime?

Returns the value of attribute recovered_at.

Returns:

  • (Time, nil)


87
88
89
# File 'lib/stoplight/infrastructure/memory/storage/state.rb', line 87

def recovered_at
  @recovered_at
end

#recovery_scheduled_afterTime?

Returns the value of attribute recovery_scheduled_after.

Returns:

  • (Time, nil)


88
89
90
# File 'lib/stoplight/infrastructure/memory/storage/state.rb', line 88

def recovery_scheduled_after
  @recovery_scheduled_after
end

#recovery_started_atTime?

Returns the value of attribute recovery_started_at.

Returns:

  • (Time, nil)


89
90
91
# File 'lib/stoplight/infrastructure/memory/storage/state.rb', line 89

def recovery_started_at
  @recovery_started_at
end

Instance Method Details

#clearObject



74
75
76
77
78
79
80
81
82
# File 'lib/stoplight/infrastructure/memory/storage/state.rb', line 74

def clear
  mutex.synchronize do
    self.locked_state = Stoplight::State::UNLOCKED
    self.recovered_at = nil
    self.recovery_scheduled_after = nil
    self.breached_at = nil
    self.recovery_started_at = nil
  end
end

#set_state(state) ⇒ Object



39
40
41
42
43
# File 'lib/stoplight/infrastructure/memory/storage/state.rb', line 39

def set_state(state)
  mutex.synchronize do
    self.locked_state = state
  end
end

#state_snapshotObject



45
46
47
48
49
50
51
52
53
54
55
# File 'lib/stoplight/infrastructure/memory/storage/state.rb', line 45

def state_snapshot
  mutex.synchronize do
    Domain::StateSnapshot.new(
      time: clock.current_time,
      locked_state:,
      recovery_scheduled_after:,
      recovery_started_at:,
      breached_at:
    )
  end
end

#transition_to_color(color) ⇒ Object

Combined method that performs the state transition based on color

Parameters:

  • color

    The color to transition to ("GREEN", "YELLOW", or "RED")

Returns:

  • true if this is the first instance to detect this transition



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

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

#transition_to_greenObject

Transitions to GREEN state and ensures only one notification

Returns:

  • true if this is the first instance to detect this transition



98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/stoplight/infrastructure/memory/storage/state.rb', line 98

def transition_to_green
  mutex.synchronize do
    if recovered_at
      false
    else
      self.recovered_at = clock.current_time
      self.recovery_started_at = nil
      self.breached_at = nil
      self.recovery_scheduled_after = nil
      true
    end
  end
end

#transition_to_redObject

Transitions to RED state and ensures only one notification

Returns:

  • true if this is the first instance to detect this transition



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/stoplight/infrastructure/memory/storage/state.rb', line 135

def transition_to_red
  mutex.synchronize do
    current_time = clock.current_time

    self.recovery_scheduled_after = current_time + cool_off_time
    self.recovery_started_at = nil
    self.recovered_at = nil

    if breached_at
      false
    else
      self.breached_at = current_time
      true
    end
  end
end

#transition_to_yellowObject

Transitions to YELLOW (recovery) state and ensures only one notification

Returns:

  • true if this is the first instance to detect this transition



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/stoplight/infrastructure/memory/storage/state.rb', line 115

def transition_to_yellow
  mutex.synchronize do
    if recovery_started_at.nil?
      self.recovery_started_at = clock.current_time
      self.recovery_scheduled_after = nil
      self.recovered_at = nil
      self.breached_at = nil
      true
    else
      self.recovery_scheduled_after = nil
      self.recovered_at = nil
      self.breached_at = nil
      false
    end
  end
end