Class: CountingSemaphore::LocalSemaphore
- Inherits:
-
Object
- Object
- CountingSemaphore::LocalSemaphore
- Includes:
- WithLeaseSupport
- Defined in:
- lib/counting_semaphore/local_semaphore.rb
Constant Summary collapse
- SLEEP_WAIT_SECONDS =
0.25
Instance Attribute Summary collapse
- #capacity ⇒ Integer readonly
Instance Method Summary collapse
-
#acquire(permits = 1) ⇒ CountingSemaphore::Lease
Acquires the given number of permits from this semaphore, blocking until all are available.
-
#available_permits ⇒ Integer
Returns the current number of permits available in this semaphore.
-
#drain_permits ⇒ CountingSemaphore::Lease?
Acquires and returns all permits that are immediately available.
-
#initialize(capacity, logger: CountingSemaphore::NullLogger) ⇒ LocalSemaphore
constructor
Initialize the semaphore with a maximum capacity.
-
#release(lease) ⇒ nil
Releases a previously acquired lease, returning the permits to the semaphore.
-
#try_acquire(permits = 1, timeout: nil) ⇒ CountingSemaphore::Lease?
Acquires the given number of permits from this semaphore, only if all are available at the time of invocation or within the timeout interval.
Methods included from WithLeaseSupport
#currently_leased, #with_lease
Constructor Details
#initialize(capacity, logger: CountingSemaphore::NullLogger) ⇒ LocalSemaphore
Initialize the semaphore with a maximum capacity.
20 21 22 23 24 25 26 27 |
# File 'lib/counting_semaphore/local_semaphore.rb', line 20 def initialize(capacity, logger: CountingSemaphore::NullLogger) raise ArgumentError, "Capacity must be positive, got #{capacity}" unless capacity >= 1 @capacity = capacity.to_i @acquired = 0 @mutex = Mutex.new @condition = ConditionVariable.new @logger = logger end |
Instance Attribute Details
#capacity ⇒ Integer (readonly)
13 14 15 |
# File 'lib/counting_semaphore/local_semaphore.rb', line 13 def capacity @capacity end |
Instance Method Details
#acquire(permits = 1) ⇒ CountingSemaphore::Lease
Acquires the given number of permits from this semaphore, blocking until all are available.
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/counting_semaphore/local_semaphore.rb', line 34 def acquire(permits = 1) permits = permits.to_i raise ArgumentError, "Permits must be at least 1, got #{permits}" if permits < 1 if permits > @capacity raise ArgumentError, "Cannot acquire #{permits} permits as capacity is only #{@capacity}" end loop do acquired = @mutex.synchronize do if (@capacity - @acquired) >= permits @acquired += permits @logger.debug { "Acquired #{permits} permits, now #{@acquired}/#{@capacity}" } true else false end end if acquired lease_id = "local_#{object_id}_#{Time.now.to_f}_#{rand(1000000)}" return CountingSemaphore::Lease.new( semaphore: self, id: lease_id, permits: permits ) end @logger.debug { "Unable to acquire #{permits} permits, #{@acquired}/#{@capacity} in use, waiting" } @mutex.synchronize do @condition.wait(@mutex) end end end |
#available_permits ⇒ Integer
Returns the current number of permits available in this semaphore.
146 147 148 |
# File 'lib/counting_semaphore/local_semaphore.rb', line 146 def available_permits @mutex.synchronize { @capacity - @acquired } end |
#drain_permits ⇒ CountingSemaphore::Lease?
Acquires and returns all permits that are immediately available. Returns a single lease representing all drained permits.
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/counting_semaphore/local_semaphore.rb', line 154 def drain_permits permits = @mutex.synchronize do available = @capacity - @acquired if available > 0 @acquired = @capacity @logger.debug { "Drained #{available} permits" } available else 0 end end if permits > 0 lease_id = "local_#{object_id}_#{Time.now.to_f}_#{rand(1000000)}" CountingSemaphore::Lease.new( semaphore: self, id: lease_id, permits: permits ) end end |
#release(lease) ⇒ nil
Releases a previously acquired lease, returning the permits to the semaphore.
73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/counting_semaphore/local_semaphore.rb', line 73 def release(lease) unless lease.semaphore == self raise ArgumentError, "Lease belongs to a different semaphore" end permits = lease.permits @mutex.synchronize do @acquired -= permits @logger.debug { "Released #{permits} permits (lease: #{lease.id}), now #{@acquired}/#{@capacity}" } @condition.broadcast # Signal waiting threads end nil end |
#try_acquire(permits = 1, timeout: nil) ⇒ CountingSemaphore::Lease?
Acquires the given number of permits from this semaphore, only if all are available at the time of invocation or within the timeout interval.
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/counting_semaphore/local_semaphore.rb', line 95 def try_acquire(permits = 1, timeout: nil) permits = permits.to_i raise ArgumentError, "Permits must be at least 1, got #{permits}" if permits < 1 if permits > @capacity return nil end start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) if timeout loop do # Check timeout if timeout elapsed_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time return nil if elapsed_time >= timeout end acquired = @mutex.synchronize do if (@capacity - @acquired) >= permits @acquired += permits @logger.debug { "Acquired #{permits} permits (try), now #{@acquired}/#{@capacity}" } true else false end end if acquired lease_id = "local_#{object_id}_#{Time.now.to_f}_#{rand(1000000)}" return CountingSemaphore::Lease.new( semaphore: self, id: lease_id, permits: permits ) end # If no timeout, return immediately return nil if timeout.nil? # Wait with remaining timeout remaining_timeout = timeout - (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) return nil if remaining_timeout <= 0 @mutex.synchronize do @condition.wait(@mutex, remaining_timeout) end end end |