Class: Async::Pool::Controller

Inherits:
Object
  • Object
show all
Defined in:
lib/async/pool/controller.rb

Overview

A resource pool controller.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(constructor, limit: nil, concurrency: 1, policy: nil, tags: nil) ⇒ Controller

Create a new resource pool.



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/async/pool/controller.rb', line 33

def initialize(constructor, limit: nil, concurrency: 1, policy: nil, tags: nil)
	@constructor = constructor
	@limit = limit
	
	# This semaphore is used to limit the number of concurrent tasks which are creating new resources.
	@guard = Async::Semaphore.new(concurrency)
	
	@policy = policy
	@gardener = nil
	
	@tags = tags
	
	# All allocated resources. Each resource is tracked by identity, irrespective of any value equality it may define:
	@resources = {}.compare_by_identity
	
	# Resources which may be available to be acquired. Resources are compared by identity and acquired in insertion order. Adding an existing resource preserves its position; a fully utilized resource is removed and reinserted at the end when capacity becomes available again.
	# This set may contain false positives, or resources which were okay but have since entered a state which is unusable.
	@available = Set.new.compare_by_identity
	
	# Used to signal when a resource has been released:
	@mutex = Thread::Mutex.new
	@condition = Thread::ConditionVariable.new
end

Instance Attribute Details

#all allocated resources, and their associated usage.(allocatedresources) ⇒ Object (readonly)



101
# File 'lib/async/pool/controller.rb', line 101

attr :resources

#constructorObject (readonly)

Returns the value of attribute constructor.



58
59
60
# File 'lib/async/pool/controller.rb', line 58

def constructor
  @constructor
end

#limitObject

Returns the value of attribute limit.



61
62
63
# File 'lib/async/pool/controller.rb', line 61

def limit
  @limit
end

#policyObject

Returns the value of attribute policy.



98
99
100
# File 'lib/async/pool/controller.rb', line 98

def policy
  @policy
end

#resourcesObject (readonly)

Returns the value of attribute resources.



101
102
103
# File 'lib/async/pool/controller.rb', line 101

def resources
  @resources
end

#tagsObject

Returns the value of attribute tags.



104
105
106
# File 'lib/async/pool/controller.rb', line 104

def tags
  @tags
end

#The constructor used to create new resources.(constructorusedtocreatenewresources.) ⇒ Object (readonly)



58
# File 'lib/async/pool/controller.rb', line 58

attr :constructor

#The maximum number of concurrent tasks that can be creating a new resource.(maximumnumberofconcurrenttasksthatcanbecreatinganewresource.) ⇒ Object (readonly)



88
89
90
# File 'lib/async/pool/controller.rb', line 88

def concurrency
	@guard.limit
end

#The maximum number of resources that this pool can have at any given time.(maximumnumberofresourcesthatthispoolcanhaveatanygiventime.) ⇒ Object (readonly)



61
# File 'lib/async/pool/controller.rb', line 61

attr_accessor :limit

#The name of the pool.(nameofthepool.) ⇒ Object (readonly)



104
# File 'lib/async/pool/controller.rb', line 104

attr_accessor :tags

Class Method Details

.wrap(**options, &block) ⇒ Object

Create a new resource pool, using the given block to create new resources.



23
24
25
# File 'lib/async/pool/controller.rb', line 23

def self.wrap(**options, &block)
	self.new(block, **options)
end

Instance Method Details

#acquireObject

Acquire a resource from the pool. If a block is provided, the resource will be released after the block has been executed.



156
157
158
159
160
161
162
163
164
165
166
# File 'lib/async/pool/controller.rb', line 156

def acquire
	resource = wait_for_resource
	
	return resource unless block_given?
	
	begin
		yield resource
	ensure
		release(resource)
	end
end

#active?Boolean

Whether the pool has any active resources.

Returns:

  • (Boolean)


112
113
114
# File 'lib/async/pool/controller.rb', line 112

def active?
	!@resources.empty?
end

#as_jsonObject

Generate a JSON representation of the pool.



73
74
75
76
77
78
79
80
# File 'lib/async/pool/controller.rb', line 73

def as_json(...)
	{
		limit: @limit,
		concurrency: @guard.limit,
		usage: @resources.size,
		availability_summary: self.availability_summary,
	}
end

#available?Boolean

Whether there are available resources, i.e. whether #acquire can reuse an existing resource.

Returns:

  • (Boolean)


126
127
128
# File 'lib/async/pool/controller.rb', line 126

def available?
	@available.any?
end

#busy?Boolean

Whether there are resources which are currently in use.

Returns:

  • (Boolean)


117
118
119
120
121
122
123
# File 'lib/async/pool/controller.rb', line 117

def busy?
	@resources.collect do |_, usage|
		return true if usage > 0
	end
	
	return false
end

#closeObject

Drain the pool, clear all resources, and stop the gardener.



208
209
210
211
212
213
# File 'lib/async/pool/controller.rb', line 208

def close
	self.drain
	
	@available.clear
	@gardener&.stop
end

#concurrencyObject



88
89
90
# File 'lib/async/pool/controller.rb', line 88

def concurrency
	@guard.limit
end

#concurrency=(value) ⇒ Object

Set the maximum number of concurrent tasks that can be creating a new resource.



93
94
95
# File 'lib/async/pool/controller.rb', line 93

def concurrency= value
	@guard.limit = value
end

#drainObject

Drain the pool, closing all resources.



198
199
200
201
202
203
204
205
# File 'lib/async/pool/controller.rb', line 198

def drain
	Console.debug(self, "Draining pool...", size: @resources.size)
	
	# Enumerate all existing resources and retire them:
	while resource = acquire_existing_resource
		retire(resource)
	end
end

#empty?Boolean

Whether the pool is empty.

Returns:

  • (Boolean)


151
152
153
# File 'lib/async/pool/controller.rb', line 151

def empty?
	@resources.empty?
end

#prune(retain = 0) ⇒ Object

Retire (and close) all unused resources. If a block is provided, it should implement the desired functionality for unused resources.



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/async/pool/controller.rb', line 218

def prune(retain = 0)
	unused = []
	
	# This code must not context switch:
	@resources.each do |resource, usage|
		if usage.zero?
			unused << resource
		end
	end
	
	# It's okay for this to context switch:
	unused.each do |resource|
		if block_given?
			yield resource
		else
			retire(resource)
		end
		
		break if @resources.size <= retain
	end
	
	# Update availability list:
	@available.clear
	@resources.each do |resource, usage|
		if usage < resource.concurrency and resource.reusable?
			@available << resource
		end
	end
	
	return unused.size
end

#release(resource) ⇒ Object

Make the resource resources and let waiting tasks know that there is something resources.



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/async/pool/controller.rb', line 169

def release(resource)
	unless usage = decrement_usage(resource)
		return false
	end
	
	if resource.reusable?
		@available.add(resource)
	else
		# The resource must not be acquired again, but it cannot be retired until all existing users have released it:
		@available.delete(resource)
		
		if usage.zero?
			retire(resource)
			resource = nil
			return true
		end
	end
	
	@mutex.synchronize{@condition.broadcast}
	
	# @policy.released(self, resource)
	
	resource = nil
	return true
ensure
	retire(resource) if resource
end

#retire(resource) ⇒ Object

Retire a specific resource.



251
252
253
254
255
256
257
258
259
260
261
# File 'lib/async/pool/controller.rb', line 251

def retire(resource)
	Console.debug(self){"Retire #{resource}"}
	
	return false unless @resources.delete(resource)
	
	resource.close
	
	@mutex.synchronize{@condition.broadcast}
	
	return true
end

#sizeObject

The number of resources in the pool.



107
108
109
# File 'lib/async/pool/controller.rb', line 107

def size
	@resources.size
end

#The pool policy.=(poolpolicy. = (value)) ⇒ Object



98
# File 'lib/async/pool/controller.rb', line 98

attr_accessor :policy

#to_jsonObject

Generate a JSON representation of the pool.



83
84
85
# File 'lib/async/pool/controller.rb', line 83

def to_json(...)
	as_json.to_json(...)
end

#to_sObject

Generate a human-readable representation of the pool.



64
65
66
67
68
69
70
# File 'lib/async/pool/controller.rb', line 64

def to_s
	if @resources.empty?
		"\#<#{self.class}(#{usage_string})>"
	else
		"\#<#{self.class}(#{usage_string}) #{availability_summary.join(';')}>"
	end
end

#waitObject

Deprecated.

Use #wait_until_free instead.

Wait until a pool resource has been freed.



132
133
134
135
136
# File 'lib/async/pool/controller.rb', line 132

def wait
	@mutex.synchronize do
		@condition.wait(@mutex)
	end
end

#wait_until_freeObject

Wait until the pool is not busy (no resources in use).



139
140
141
142
143
144
145
146
147
148
# File 'lib/async/pool/controller.rb', line 139

def wait_until_free
	@mutex.synchronize do
		if busy?
			yield self if block_given?
			
			# Wait until the pool is not busy:
			@condition.wait(@mutex) while busy?
		end
	end
end