Class: Apartment::TenantValidator

Inherits:
Object
  • Object
show all
Defined in:
lib/apartment/tenant_validator.rb

Overview

In-process, memoized validator: answers “is this a real tenant name?”. The positive set is sourced from config.tenants_provider, refreshed on a TTL and — rate-limited, single-flight — on a miss. Lifecycle notifications (create.apartment / drop.apartment) keep the set current between rebuilds; a tenants_provider error makes the validator fail open (allow all names).

Constant Summary collapse

DEFAULT_POSITIVE_TTL_SECONDS =
300
DEFAULT_REBUILD_INTERVAL_SECONDS =
5

Instance Method Summary collapse

Constructor Details

#initialize(positive_ttl: DEFAULT_POSITIVE_TTL_SECONDS, rebuild_interval: DEFAULT_REBUILD_INTERVAL_SECONDS) ⇒ TenantValidator

Returns a new instance of TenantValidator.



16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/apartment/tenant_validator.rb', line 16

def initialize(positive_ttl: DEFAULT_POSITIVE_TTL_SECONDS,
               rebuild_interval: DEFAULT_REBUILD_INTERVAL_SECONDS)
  @positive_ttl = positive_ttl
  @rebuild_interval = rebuild_interval
  @names = Concurrent::Set.new
  @rebuild_mutex = Mutex.new # single-flight guard: one rebuild at a time
  @state_mutex = Mutex.new   # guards the @names swap vs lifecycle deltas
  @pending_deltas = nil      # non-nil Array while a rebuild is in flight
  @built_at = nil
  @last_rebuild_at = nil
  @degraded = false
  @subscribers = subscribe_to_lifecycle
end

Instance Method Details

#call(name) ⇒ Boolean Also known as: valid?

Returns whether ‘name` is a known tenant.

Returns:

  • (Boolean)

    whether ‘name` is a known tenant.



39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/apartment/tenant_validator.rb', line 39

def call(name)
  name = name.to_s
  if @built_at.nil?
    rebuild(blocking: true) # cold start: every caller waits for the first build
  elsif stale?
    rebuild                 # refresh: single-flight, non-blocking
  end
  return true if @degraded
  return true if @names.include?(name)

  rebuild_on_miss
  @degraded || @names.include?(name)
end

#evict(name) ⇒ Object

Remove a name from the positive set immediately. The request-path fail-safe (the elevator’s missing-tenant rescue) calls this when a switch hits a container that no longer exists, so this process stops validating a tenant dropped by another process before its TTL would otherwise heal it. Mid-rebuild evictions are captured as deltas and re-applied after the swap, mirroring drop.apartment handling, so the rebuild cannot resurrect the name.



60
61
62
# File 'lib/apartment/tenant_validator.rb', line 60

def evict(name)
  apply_lifecycle(:remove, name)
end

#shutdownObject

Remove the ActiveSupport::Notifications subscriptions. Call when discarding a validator (Apartment.clear_config) so subscriptions do not accumulate across a process’s lifetime.



33
34
35
36
# File 'lib/apartment/tenant_validator.rb', line 33

def shutdown
  @subscribers.each { |s| ActiveSupport::Notifications.unsubscribe(s) }
  @subscribers = []
end