Class: Arcp::Runtime::LeaseManager

Inherits:
Object
  • Object
show all
Defined in:
lib/arcp/runtime/lease_manager.rb

Overview

Tracks per-job leases and bound budget counters. The runtime asks ‘#check!(job_id, capability:)` before every authority op.

Instance Method Summary collapse

Constructor Details

#initialize(clock: Arcp::SystemClock.new, enforce_model_use: false) ⇒ LeaseManager

Returns a new instance of LeaseManager.



8
9
10
11
12
13
14
# File 'lib/arcp/runtime/lease_manager.rb', line 8

def initialize(clock: Arcp::SystemClock.new, enforce_model_use: false)
  @clock = clock
  @enforce_model_use = enforce_model_use
  @leases = {}
  @counters = {}
  @mutex = Mutex.new
end

Instance Method Details

#check!(job_id, capability:) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/arcp/runtime/lease_manager.rb', line 27

def check!(job_id, capability:)
  lease = get(job_id)
  return if lease.nil?

  if lease.expired?(@clock.now)
    raise Arcp::Errors::LeaseExpired.new(
      "lease #{lease.id} expired at #{lease.expires_at}",
      details: { 'lease_id' => lease.id }
    )
  end

  return if lease.capabilities.include?(capability)

  raise Arcp::Errors::PermissionDenied.new(
    "capability #{capability.inspect} not in lease #{lease.id}",
    details: { 'capability' => capability, 'lease_id' => lease.id }
  )
end

#check_model!(job_id, model_id:) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
# File 'lib/arcp/runtime/lease_manager.rb', line 46

def check_model!(job_id, model_id:)
  lease = get(job_id)
  return true if lease.nil? && !@enforce_model_use

  return true if lease&.model_use && Arcp::ModelPattern.match?(lease.model_use, model_id)

  raise Arcp::Errors::PermissionDenied.new(
    "model #{model_id.inspect} not permitted by lease",
    details: { 'model' => model_id, 'lease_id' => lease&.id }
  )
end

#counter(job_id) ⇒ Object



25
# File 'lib/arcp/runtime/lease_manager.rb', line 25

def counter(job_id) = @mutex.synchronize { @counters[job_id] }

#get(job_id) ⇒ Object



24
# File 'lib/arcp/runtime/lease_manager.rb', line 24

def get(job_id) = @mutex.synchronize { @leases[job_id] }

#register(job_id, lease) ⇒ Object



16
17
18
19
20
21
22
# File 'lib/arcp/runtime/lease_manager.rb', line 16

def register(job_id, lease)
  @mutex.synchronize do
    @leases[job_id] = lease
    @counters[job_id] = Arcp::Lease::BudgetCounter.new(initial: lease.budget&.per_currency&.dup || {})
  end
  lease
end

#remaining(job_id) ⇒ Object



75
76
77
78
# File 'lib/arcp/runtime/lease_manager.rb', line 75

def remaining(job_id)
  c = counter(job_id)
  c ? c.snapshot : {}
end

#revoke(job_id) ⇒ Object



80
81
82
83
84
85
# File 'lib/arcp/runtime/lease_manager.rb', line 80

def revoke(job_id)
  @mutex.synchronize do
    @leases.delete(job_id)
    @counters.delete(job_id)
  end
end

#try_spend!(job_id, currency, amount) ⇒ Object

Try to decrement the bound budget. Returns true on success, raises BudgetExhausted if no balance covers the amount. Straight-line —no scheduler-yielding calls between read and write.



61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/arcp/runtime/lease_manager.rb', line 61

def try_spend!(job_id, currency, amount)
  counter = self.counter(job_id)
  return true if counter.nil?
  return true if counter.get(currency).zero? && !counter.remaining.key?(currency)

  unless counter.try_decrement(currency, amount)
    raise Arcp::Errors::BudgetExhausted.new(
      "budget #{currency} exhausted",
      details: { 'currency' => currency, 'remaining' => counter.get(currency).to_s('F') }
    )
  end
  true
end