Class: Jumbotron::Synchronization::Lease

Inherits:
Object
  • Object
show all
Defined in:
app/synchronization/jumbotron/synchronization/lease.rb

Overview

Narrow single-writer lease. Contract is backend-neutral; atomic acquire/release require a capable cache/store (MemoryStore or RedisCacheStore). NullStore is rejected.

Defined Under Namespace

Classes: Result

Constant Summary collapse

DEFAULT_TTL_SECONDS =
120
KEY_PREFIX =
"jumbotron:synchronization:lease"
COMPARE_AND_DELETE_LUA =
<<~LUA
  if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
  else
    return 0
  end
LUA

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(store: Rails.cache, ttl: DEFAULT_TTL_SECONDS) ⇒ Lease

Returns a new instance of Lease.



44
45
46
47
48
# File 'app/synchronization/jumbotron/synchronization/lease.rb', line 44

def initialize(store: Rails.cache, ttl: DEFAULT_TTL_SECONDS)
  @store = store
  @ttl = Integer(ttl)
  assert_capable_store!
end

Class Method Details

.normalized_acquisition_parts(acquisition) ⇒ Object



38
39
40
41
42
# File 'app/synchronization/jumbotron/synchronization/lease.rb', line 38

def self.normalized_acquisition_parts(acquisition)
  hash = acquisition.respond_to?(:to_h) ? acquisition.to_h : {}
  hash = hash.transform_keys(&:to_s)
  hash.keys.sort.map { |key| hash[key].to_s }
end

.scope(adapter_id:, endpoint:, acquisition:) ⇒ Object



29
30
31
32
33
34
35
36
# File 'app/synchronization/jumbotron/synchronization/lease.rb', line 29

def self.scope(adapter_id:, endpoint:, acquisition:)
  parts = [
    adapter_id.to_s,
    endpoint.to_s,
    *normalized_acquisition_parts(acquisition)
  ]
  parts.join(":")
end

Instance Method Details

#acquire(scope) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'app/synchronization/jumbotron/synchronization/lease.rb', line 50

def acquire(scope)
  assert_capable_store!
  token = SecureRandom.uuid
  key = cache_key(scope)
  written = store.write(
    key,
    token,
    unless_exist: true,
    expires_in: ttl,
    raw: true
  )
  if written
    Result.new(acquired: true, token: token, scope: scope)
  else
    Result.new(acquired: false, token: nil, scope: scope)
  end
end

#release(scope, token:) ⇒ Object



68
69
70
71
72
73
# File 'app/synchronization/jumbotron/synchronization/lease.rb', line 68

def release(scope, token:)
  assert_capable_store!
  return false if token.nil?

  compare_and_delete(cache_key(scope), token.to_s)
end