Class: Ask::State::Memory

Inherits:
Adapter
  • Object
show all
Defined in:
lib/ask/state.rb

Overview

In-process state backend backed by a Hash. All operations are thread-safe via a Mutex. Data is not persisted — lost on process exit.

Instance Method Summary collapse

Methods inherited from Adapter

glob_to_like, glob_to_regex

Constructor Details

#initializeMemory

Returns a new instance of Memory.



194
195
196
197
198
199
200
# File 'lib/ask/state.rb', line 194

def initialize
  @data = {}
  @locks = {}
  @queues = {}
  @lists = {}
  @mutex = Mutex.new
end

Instance Method Details

#acquire_lock(key, ttl: 10) ⇒ Object

-- locking --



279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/ask/state.rb', line 279

def acquire_lock(key, ttl: 10)
  @mutex.synchronize do
    existing = @locks[key]
    if existing.nil? || existing.expired?
      lock = Lock.new(
        id: key,
        token: SecureRandom.hex(16),
        expires_at: Time.now + ttl
      )
      @locks[key] = lock
      lock
    end
  end
end

#clearObject



244
245
246
247
248
249
250
251
# File 'lib/ask/state.rb', line 244

def clear
  @mutex.synchronize do
    @data.clear
    @locks.clear
    @queues.clear
    @lists.clear
  end
end

#closeObject

-- lifecycle --



371
372
373
374
375
376
377
378
# File 'lib/ask/state.rb', line 371

def close
  @mutex.synchronize do
    @data.clear
    @locks.clear
    @queues.clear
    @lists.clear
  end
end

#delete(key) ⇒ Object



222
223
224
# File 'lib/ask/state.rb', line 222

def delete(key)
  @mutex.synchronize { @data.delete(key) }
end

#dequeue(queue) ⇒ Object



321
322
323
324
325
326
327
328
# File 'lib/ask/state.rb', line 321

def dequeue(queue)
  @mutex.synchronize do
    q = @queues[queue]
    return nil unless q&.any?

    q.shift
  end
end

#enqueue(queue, value) ⇒ Object

-- queues --



308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/ask/state.rb', line 308

def enqueue(queue, value)
  @mutex.synchronize do
    @queues[queue] ||= []
    entry = QueueEntry.new(
      id: SecureRandom.uuid,
      value: value,
      enqueued_at: Time.now
    )
    @queues[queue] << entry
    entry
  end
end

#exists?(key) ⇒ Boolean

Returns:

  • (Boolean)


253
254
255
256
257
258
259
# File 'lib/ask/state.rb', line 253

def exists?(key)
  @mutex.synchronize do
    expiry = @data[key]&.dig(:expires_at)
    return false if expiry && Time.now >= expiry
    @data.key?(key)
  end
end

#get(key) ⇒ Object

-- key-value --



204
205
206
207
208
209
210
211
# File 'lib/ask/state.rb', line 204

def get(key)
  @mutex.synchronize do
    expiry = @data[key]&.dig(:expires_at)
    return nil if expiry && Time.now >= expiry

    @data[key]&.dig(:value)
  end
end

#keys(pattern: nil) ⇒ Object



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/ask/state.rb', line 261

def keys(pattern: nil)
  @mutex.synchronize do
    # Filter out expired keys
    active = @data.select do |_k, v|
      expiry = v[:expires_at]
      expiry.nil? || Time.now < expiry
    end

    keys = active.keys.map(&:to_s)
    return keys unless pattern

     regex = self.class.glob_to_regex(pattern)
    keys.select { |k| k.match?(regex) }
  end
end

#list_append(key, value, max_length: nil) ⇒ Object

-- lists --



338
339
340
341
342
343
344
# File 'lib/ask/state.rb', line 338

def list_append(key, value, max_length: nil)
  @mutex.synchronize do
    @lists[key] ||= []
    @lists[key] << value
    @lists[key].shift if max_length && @lists[key].length > max_length
  end
end

#list_range(key, start = 0, stop = -1)) ⇒ Object



346
347
348
349
350
351
352
353
354
355
356
# File 'lib/ask/state.rb', line 346

def list_range(key, start = 0, stop = -1)
  @mutex.synchronize do
    list = @lists[key] || []
    return list if start == 0 && stop == -1

    stop = list.length - 1 if stop == -1 || stop >= list.length
    return [] if start > stop

    list[start..stop] || []
  end
end

#list_remove(key, value) ⇒ Object



358
359
360
361
362
363
364
365
366
367
# File 'lib/ask/state.rb', line 358

def list_remove(key, value)
  @mutex.synchronize do
    list = @lists[key]
    return 0 unless list

    before = list.length
    list.delete(value)
    before - list.length
  end
end

#queue_depth(queue) ⇒ Object



330
331
332
333
334
# File 'lib/ask/state.rb', line 330

def queue_depth(queue)
  @mutex.synchronize do
    (@queues[queue] || []).length
  end
end

#release_lock(key, lock) ⇒ Object



294
295
296
297
298
299
300
301
302
303
304
# File 'lib/ask/state.rb', line 294

def release_lock(key, lock)
  @mutex.synchronize do
    current = @locks[key]
    if current && current.token == lock.token && !current.expired?
      @locks.delete(key)
      true
    else
      false
    end
  end
end

#set(key, value, ttl: nil) ⇒ Object



213
214
215
216
217
218
219
220
# File 'lib/ask/state.rb', line 213

def set(key, value, ttl: nil)
  @mutex.synchronize do
    @data[key] = {
      value: value,
      expires_at: ttl ? Time.now + ttl : nil
    }
  end
end

#set_if_not_exists(key, value, ttl: nil) ⇒ Object



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/ask/state.rb', line 226

def set_if_not_exists(key, value, ttl: nil)
  @mutex.synchronize do
    if @data.key?(key)
      expiry = @data[key][:expires_at]
      return false if expiry.nil? || Time.now < expiry

      # Key expired — treat as nonexistent
      @data.delete(key)
    end

    @data[key] = {
      value: value,
      expires_at: ttl ? Time.now + ttl : nil
    }
    true
  end
end