Class: Apartment::PoolReaper
- Inherits:
-
Object
- Object
- Apartment::PoolReaper
- Defined in:
- lib/apartment/pool_reaper.rb
Overview
Evicts idle and excess tenant pools on a background timer. Complementary to ActiveRecord’s ConnectionPool::Reaper which handles intra-pool connection reaping — this handles inter-pool (tenant) eviction.
Class Method Summary collapse
-
.pool_pinned?(pool) ⇒ Boolean
True when Rails’ transactional-fixture machinery has pinned the pool (ConnectionPool#pin_connection!, Rails 7.1+).
Instance Method Summary collapse
-
#initialize(pool_manager:, interval:, idle_timeout:, max_total: nil, default_tenant: nil, shard_key_prefix: nil, on_evict: nil) ⇒ PoolReaper
constructor
A new instance of PoolReaper.
-
#run_cycle ⇒ Object
Perform one synchronous eviction pass (idle + LRU).
- #running? ⇒ Boolean
- #start ⇒ Object
- #stop ⇒ Object
Constructor Details
#initialize(pool_manager:, interval:, idle_timeout:, max_total: nil, default_tenant: nil, shard_key_prefix: nil, on_evict: nil) ⇒ PoolReaper
Returns a new instance of PoolReaper.
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/apartment/pool_reaper.rb', line 26 def initialize(pool_manager:, interval:, idle_timeout:, max_total: nil, default_tenant: nil, shard_key_prefix: nil, on_evict: nil) raise(ArgumentError, 'interval must be a positive number') unless interval.is_a?(Numeric) && interval.positive? unless idle_timeout.is_a?(Numeric) && idle_timeout.positive? raise(ArgumentError, 'idle_timeout must be a positive number') end if max_total && (!max_total.is_a?(Integer) || max_total < 1) raise(ArgumentError, 'max_total must be a positive integer or nil') end @pool_manager = pool_manager @interval = interval @idle_timeout = idle_timeout @max_total = max_total @default_tenant = default_tenant @shard_key_prefix = shard_key_prefix @on_evict = on_evict @mutex = Mutex.new @timer = nil end |
Class Method Details
.pool_pinned?(pool) ⇒ Boolean
True when Rails’ transactional-fixture machinery has pinned the pool (ConnectionPool#pin_connection!, Rails 7.1+). Evicting or discarding a pinned pool strands the fixture transaction; teardown then errors or marks the DB dirty. ActiveRecord exposes no public predicate, so we read the ivar it sets. TOCTOU caveat applies — see docs/testing.md “Pool lifecycle in tests”.
Exposed as a class method so Apartment.reset_tenant_pools! can reuse the same primitive without instantiating a reaper.
20 21 22 23 24 |
# File 'lib/apartment/pool_reaper.rb', line 20 def self.pool_pinned?(pool) return false unless pool&.instance_variable_defined?(:@pinned_connection) !pool.instance_variable_get(:@pinned_connection).nil? end |
Instance Method Details
#run_cycle ⇒ Object
Perform one synchronous eviction pass (idle + LRU). Returns the total number of pools evicted. Called by the background timer and by CLI ‘pool evict`.
67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/apartment/pool_reaper.rb', line 67 def run_cycle count = 0 count += evict_idle count += evict_lru if @max_total count rescue Apartment::ApartmentError => e warn "[Apartment::PoolReaper] #{e.class}: #{e.}" 0 rescue StandardError => e warn "[Apartment::PoolReaper] Unexpected error: #{e.class}: #{e.}" warn e.backtrace&.first(5)&.join("\n") if e.backtrace 0 end |
#running? ⇒ Boolean
60 61 62 |
# File 'lib/apartment/pool_reaper.rb', line 60 def running? @mutex.synchronize { @timer&.running? || false } end |
#start ⇒ Object
47 48 49 50 51 52 53 54 |
# File 'lib/apartment/pool_reaper.rb', line 47 def start @mutex.synchronize do stop_internal @timer = Concurrent::TimerTask.new(execution_interval: @interval) { reap } @timer.execute end self end |
#stop ⇒ Object
56 57 58 |
# File 'lib/apartment/pool_reaper.rb', line 56 def stop @mutex.synchronize { stop_internal } end |