Class: LittleGhost::AgentInterruptions

Inherits:
Object
  • Object
show all
Defined in:
lib/little_ghost/agent_interruptions.rb

Overview

:nodoc: all

Defined Under Namespace

Classes: Batch, Response, Ticket

Constant Summary collapse

MAX_BATCH_SIZE =
100
MAX_INTERRUPTION_COUNT =
1_000

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeAgentInterruptions

Returns a new instance of AgentInterruptions.



82
83
84
85
86
87
88
89
90
91
# File 'lib/little_ghost/agent_interruptions.rb', line 82

def initialize
  @mutex = Mutex.new
  @queue = []
  @tickets_by_id = {}
  @waiters = Hash.new(0)
  @delivered = nil
  @closed_error = nil
  @operation_id = nil
  @target_operation_id = nil
end

Instance Attribute Details

#operation_idObject (readonly)

Returns the value of attribute operation_id.



80
81
82
# File 'lib/little_ghost/agent_interruptions.rb', line 80

def operation_id
  @operation_id
end

#target_operation_idObject (readonly)

Returns the value of attribute target_operation_id.



80
81
82
# File 'lib/little_ghost/agent_interruptions.rb', line 80

def target_operation_id
  @target_operation_id
end

Instance Method Details

#bind(operation_id, target_operation_id:) ⇒ Object



93
94
95
96
97
98
# File 'lib/little_ghost/agent_interruptions.rb', line 93

def bind(operation_id, target_operation_id:)
  @mutex.synchronize do
    @operation_id = operation_id
    @target_operation_id = target_operation_id || operation_id
  end
end

#close(error) ⇒ Object



184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/little_ghost/agent_interruptions.rb', line 184

def close(error)
  tickets = @mutex.synchronize do
    return if @closed_error

    @closed_error = error
    values = [*@delivered&.tickets, *@queue].compact
    @delivered = nil
    @queue.clear
    values
  end
  tickets.each { |ticket| ticket.reject(error) }
end

#deliverObject



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/little_ghost/agent_interruptions.rb', line 134

def deliver
  @mutex.synchronize do
    return if @delivered || @queue.empty?

    batch_key = @queue.first.batch_key
    tickets = []
    tickets << @queue.shift
    if batch_key
      while tickets.length < MAX_BATCH_SIZE && @queue.first&.batch_key == batch_key
        tickets << @queue.shift
      end
    end
    @delivered = Batch.new(tickets: tickets.freeze)
  end
end

#enqueue(message, id: SecureRandom.uuid, batch_key: nil, metadata: {}) ⇒ Object



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/little_ghost/agent_interruptions.rb', line 100

def enqueue(message, id: SecureRandom.uuid, batch_key: nil, metadata: {})
  id = String(id)
  raise ArgumentError, "interruption_id cannot be empty" if id.empty?

  batch_key = String(batch_key) unless batch_key.nil?
   = .to_h
  @mutex.synchronize do
    raise @closed_error if @closed_error

    existing = @tickets_by_id[id]
    if existing
      unless existing.message.to_h == message.to_h &&
          existing.batch_key == batch_key &&
          existing. == 
        raise ArgumentError, "interruption_id has already been used with different input"
      end

      @waiters[existing] += 1
      return existing
    end
    if @tickets_by_id.length >= MAX_INTERRUPTION_COUNT
      raise AgentInterruptError, "Agent interruption capacity reached"
    end

    ticket = Ticket.new(message, id: id.freeze, batch_key: batch_key&.freeze, metadata:)
    @tickets_by_id[id] = ticket
    @waiters[ticket] = 1
    @queue << ticket
    ticket
  end
rescue TypeError, NoMethodError
  raise ArgumentError, "interruption_id, batch_key, and metadata are invalid"
end

#finishObject



164
165
166
167
168
169
170
171
172
# File 'lib/little_ghost/agent_interruptions.rb', line 164

def finish
  @mutex.synchronize do
    return true if @closed_error
    return false if @delivered || !@queue.empty?

    @closed_error = AgentInterruptError.new("Agent is not currently running")
    true
  end
end

#pending?Boolean

Returns:

  • (Boolean)


160
161
162
# File 'lib/little_ghost/agent_interruptions.rb', line 160

def pending?
  @mutex.synchronize { !@queue.empty? }
end

#release(ticket, withdraw: false) ⇒ Object



174
175
176
177
178
179
180
181
182
# File 'lib/little_ghost/agent_interruptions.rb', line 174

def release(ticket, withdraw: false)
  @mutex.synchronize do
    @waiters[ticket] -= 1
    return unless withdraw && @waiters[ticket] <= 0 && @queue.delete(ticket)

    @tickets_by_id.delete(ticket.id)
    @waiters.delete(ticket)
  end
end

#resolve(batch, response) ⇒ Object



150
151
152
153
154
155
156
157
158
# File 'lib/little_ghost/agent_interruptions.rb', line 150

def resolve(batch, response)
  tickets = @mutex.synchronize do
    return unless batch && @delivered.equal?(batch)

    @delivered = nil
    batch.tickets
  end
  tickets.each { |ticket| ticket.resolve(response) }
end