async-pool 0.2.0 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6ad379b03305a3175cb279cdb733802af5d18c8dd4286aa405af0266326ded2e
4
- data.tar.gz: a14e523cbf72c0be0153c5281a66e4b15b97bbf46f3ebc1f4f2903b642d84d32
3
+ metadata.gz: 53496ca8f02b057c94d8cef37e92f7d14bb3d7062267ea53f363d5ca3c2ee7d4
4
+ data.tar.gz: b59bb3a498bb16b1306eea4b6bcb765efa60a479701f546a3c2b6785ff5203e1
5
5
  SHA512:
6
- metadata.gz: e6977d59abe105dc69cf481500307f1105d5fb3aaecf44804c71202892fa59869b23672b059d396ae1d538d9859a372a4ddb3d21d75a000f172f0ec6a83df9b6
7
- data.tar.gz: df123b18dd484354fd59e356f0957e2b95965cf28889592191b92b6f2280cbd96f5d035c3229734382e5d306882b3acfc96d614dff60cbb48f546871b7914f26
6
+ metadata.gz: f53063512c044675725eed75b72346644a36c99704bf2f1559692420f7176b88063cb79200d4a24f19f8cd081be224e307a811d91dc8a94923cccb87cf850cc4
7
+ data.tar.gz: 366355082c92722989dd76607f611fd81f520844b8733e15872db4164a146bc2e257e40f316bc87c173a49b65d465e07cccdf459fea242b9c8445841c13f7580
@@ -20,6 +20,7 @@
20
20
 
21
21
  require 'async/logger'
22
22
 
23
+ require 'async'
23
24
  require 'async/notification'
24
25
  require 'async/semaphore'
25
26
 
@@ -31,18 +32,24 @@ module Async
31
32
  end
32
33
 
33
34
  def initialize(constructor, limit: nil)
35
+ # All available resources:
34
36
  @resources = {}
35
37
 
38
+ # Resources which may be available to be acquired:
39
+ # This list may contain false positives, or resources which were okay but have since entered a state which is unusuable.
36
40
  @available = []
41
+
37
42
  @notification = Async::Notification.new
38
43
 
39
44
  @limit = limit
40
45
 
41
46
  @constructor = constructor
42
47
  @guard = Async::Semaphore.new(1)
48
+
49
+ @gardener = nil
43
50
  end
44
51
 
45
- # @attr [Hash<Resource, Integer>] all allocated resources, and their associated usage.
52
+ # @attribute [Hash(Resource, Integer)] all allocated resources, and their associated usage.
46
53
  attr :resources
47
54
 
48
55
  def size
@@ -63,6 +70,11 @@ module Async
63
70
  return false
64
71
  end
65
72
 
73
+ # Whether there are available resources, i.e. whether {#acquire} can reuse an existing resource.
74
+ def available?
75
+ @available.any?
76
+ end
77
+
66
78
  # Wait until a pool resource has been freed.
67
79
  def wait
68
80
  @notification.wait
@@ -97,6 +109,8 @@ module Async
97
109
  def close
98
110
  @resources.each_key(&:close)
99
111
  @resources.clear
112
+
113
+ @gardener&.stop
100
114
  end
101
115
 
102
116
  def to_s
@@ -114,7 +128,9 @@ module Async
114
128
  unused = []
115
129
 
116
130
  @resources.each do |resource, usage|
117
- unused << resource if usage.zero?
131
+ if usage.zero?
132
+ unused << resource
133
+ end
118
134
  end
119
135
 
120
136
  unused.each do |resource|
@@ -127,6 +143,14 @@ module Async
127
143
  break if @resources.size <= retain
128
144
  end
129
145
 
146
+ # Update availability list:
147
+ @available.clear
148
+ @resources.each do |resource, usage|
149
+ if usage < resource.concurrency and resource.reusable?
150
+ @available << resource
151
+ end
152
+ end
153
+
130
154
  return unused.size
131
155
  end
132
156
 
@@ -142,6 +166,19 @@ module Async
142
166
 
143
167
  protected
144
168
 
169
+ def start_gardener
170
+ return if @gardener
171
+
172
+ Async(transient: true) do |task|
173
+ @gardener = task
174
+
175
+ Task.yield
176
+ ensure
177
+ @gardener = nil
178
+ self.close
179
+ end
180
+ end
181
+
145
182
  def usage_string
146
183
  "#{@resources.size}/#{@limit || '∞'}"
147
184
  end
@@ -152,11 +189,42 @@ module Async
152
189
  end.join(";")
153
190
  end
154
191
 
192
+ def usage
193
+ @resources.count{|resource, usage| usage > 0}
194
+ end
195
+
196
+ def free
197
+ @resources.count{|resource, usage| usage == 0}
198
+ end
199
+
200
+ # @returns [Boolean] Whether the number of available resources is excessive and we should retire some.
201
+ def overflowing?
202
+ if @resources.any?
203
+ (self.free.to_f / @resources.size) > 0.5
204
+ end
205
+ end
206
+
155
207
  def reuse(resource)
156
208
  Async.logger.debug(self) {"Reuse #{resource}"}
209
+ usage = @resources[resource]
210
+
211
+ if usage.zero?
212
+ raise "Trying to reuse unacquired resource: #{resource}!"
213
+ end
157
214
 
158
- @resources[resource] -= 1
159
- @available.push(resource)
215
+ # We retire resources when adding to the @available list would overflow our pool:
216
+ if usage == 1
217
+ if overflowing?
218
+ return retire(resource)
219
+ end
220
+ end
221
+
222
+ # If the resource was fully utilized, it now becomes available:
223
+ if usage == resource.concurrency
224
+ @available.push(resource)
225
+ end
226
+
227
+ @resources[resource] = usage - 1
160
228
 
161
229
  @notification.signal
162
230
  end
@@ -176,23 +244,35 @@ module Async
176
244
  return resource
177
245
  end
178
246
 
247
+ # @returns [Object] A new resource in a "used" state.
179
248
  def create_resource
249
+ self.start_gardener
250
+
180
251
  # This might return nil, which means creating the resource failed.
181
252
  if resource = @constructor.call
182
253
  @resources[resource] = 1
183
254
 
184
- @available.push(resource) if resource.concurrency > 1
255
+ # Make the resource available if it can be used multiple times:
256
+ if resource.concurrency > 1
257
+ @available.push(resource)
258
+ end
185
259
  end
186
260
 
187
261
  return resource
188
262
  end
189
263
 
264
+ # @returns [Object] An existing resource in a "used" state.
190
265
  def available_resource
191
266
  @guard.acquire do
192
267
  while resource = @available.last
193
268
  if usage = @resources[resource] and usage < resource.concurrency
194
269
  if resource.viable?
195
- @resources[resource] += 1
270
+ usage = (@resources[resource] += 1)
271
+
272
+ if usage == resource.concurrency
273
+ # The resource is used up to it's limit:
274
+ @available.pop
275
+ end
196
276
 
197
277
  return resource
198
278
  else
@@ -200,12 +280,13 @@ module Async
200
280
  @available.pop
201
281
  end
202
282
  else
283
+ # The resource has been removed already, so skip it and remove it from the availability list.
203
284
  @available.pop
204
285
  end
205
286
  end
206
287
 
207
288
  if @limit.nil? or @resources.size < @limit
208
- Async.logger.debug(self) {"No resources resources, allocating new one..."}
289
+ Async.logger.debug(self) {"No available resources, allocating new one..."}
209
290
 
210
291
  return create_resource
211
292
  end
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Async
22
22
  module Pool
23
- VERSION = "0.2.0"
23
+ VERSION = "0.3.4"
24
24
  end
25
25
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-pool
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-12-22 00:00:00.000000000 Z
11
+ date: 2021-02-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.8'
19
+ version: '1.25'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.8'
26
+ version: '1.25'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: async-rspec
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -39,7 +39,7 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.1'
41
41
  - !ruby/object:Gem::Dependency
42
- name: covered
42
+ name: bake-bundler
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
@@ -53,7 +53,7 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: bundler
56
+ name: bake-modernize
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
@@ -67,21 +67,21 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: rspec
70
+ name: bundler
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - "~>"
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
- version: '3.6'
75
+ version: '0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - "~>"
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
- version: '3.6'
82
+ version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
- name: rake
84
+ name: covered
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - ">="
@@ -94,29 +94,36 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
- description:
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.6'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.6'
111
+ description:
98
112
  email:
99
- - samuel.williams@oriontransfer.co.nz
100
113
  executables: []
101
114
  extensions: []
102
115
  extra_rdoc_files: []
103
116
  files:
104
- - ".editorconfig"
105
- - ".gitignore"
106
- - ".rspec"
107
- - ".travis.yml"
108
- - Gemfile
109
- - README.md
110
- - Rakefile
111
- - async-pool.gemspec
112
117
  - lib/async/pool.rb
113
118
  - lib/async/pool/controller.rb
114
119
  - lib/async/pool/resource.rb
115
120
  - lib/async/pool/version.rb
116
121
  homepage: https://github.com/socketry/async-pool
117
- licenses: []
118
- metadata: {}
119
- post_install_message:
122
+ licenses:
123
+ - MIT
124
+ metadata:
125
+ funding_uri: https://github.com/sponsors/ioquatix/
126
+ post_install_message:
120
127
  rdoc_options: []
121
128
  require_paths:
122
129
  - lib
@@ -124,15 +131,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
124
131
  requirements:
125
132
  - - ">="
126
133
  - !ruby/object:Gem::Version
127
- version: '0'
134
+ version: '2.5'
128
135
  required_rubygems_version: !ruby/object:Gem::Requirement
129
136
  requirements:
130
137
  - - ">="
131
138
  - !ruby/object:Gem::Version
132
139
  version: '0'
133
140
  requirements: []
134
- rubygems_version: 3.0.4
135
- signing_key:
141
+ rubygems_version: 3.1.2
142
+ signing_key:
136
143
  specification_version: 4
137
- summary: A Redis client library.
144
+ summary: A singleplex and multiplex resource pool for implementing robust clients.
138
145
  test_files: []
data/.editorconfig DELETED
@@ -1,6 +0,0 @@
1
- root = true
2
-
3
- [*]
4
- indent_style = tab
5
- indent_size = 2
6
-
data/.gitignore DELETED
@@ -1,13 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
10
-
11
- # rspec failure tracking
12
- .rspec_status
13
- .covered.db
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --warnings
3
- --require spec_helper
data/.travis.yml DELETED
@@ -1,20 +0,0 @@
1
- language: ruby
2
- dist: xenial
3
- cache: bundler
4
-
5
- matrix:
6
- include:
7
- - rvm: 2.3
8
- - rvm: 2.4
9
- - rvm: 2.5
10
- - rvm: 2.6
11
- - rvm: 2.6
12
- env: COVERAGE=BriefSummary,Coveralls
13
- - rvm: ruby-head
14
- - rvm: truffleruby
15
- - rvm: jruby-head
16
- env: JRUBY_OPTS="--debug -X+O"
17
- allow_failures:
18
- - rvm: ruby-head
19
- - rvm: truffleruby
20
- - rvm: jruby-head
data/Gemfile DELETED
@@ -1,3 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- gemspec
data/README.md DELETED
@@ -1,72 +0,0 @@
1
- # Async::Pool
2
-
3
- Provides support for connection pooling both singleplex and multiplex resources.
4
-
5
- [![Build Status](https://travis-ci.com/socketry/async-pool.svg)](https://travis-ci.com/socketry/async-pool)
6
-
7
- ## Installation
8
-
9
- Add this line to your application's Gemfile:
10
-
11
- ```ruby
12
- gem 'async-pool'
13
- ```
14
-
15
- And then execute:
16
-
17
- $ bundle
18
-
19
- Or install it yourself as:
20
-
21
- $ gem install async-pool
22
-
23
- ## Usage
24
-
25
- `Async::Pool::Controller` provides support for both singleplex (one stream at a time) and multiplex resources (multiple streams at a time).
26
-
27
- `Async::Pool::Resource` is provided as an interface and to document how to use the pools. However, you wouldn't need to use this in practice and just implement the appropriate interface on your own objects.
28
-
29
- ```ruby
30
- pool = Async::Pool::Controller.new(Async::Pool::Resource)
31
-
32
- pool.acquire do |resource|
33
- # resource is implicitly released when exiting the block.
34
- end
35
-
36
- resource = pool.acquire
37
-
38
- # Return the resource back to the pool:
39
- pool.release(resource)
40
- ```
41
-
42
- ## Contributing
43
-
44
- 1. Fork it
45
- 2. Create your feature branch (`git checkout -b my-new-feature`)
46
- 3. Commit your changes (`git commit -am 'Add some feature'`)
47
- 4. Push to the branch (`git push origin my-new-feature`)
48
- 5. Create new Pull Request
49
-
50
- ## License
51
-
52
- Released under the MIT license.
53
-
54
- Copyright, 2019, by [Samuel G. D. Williams](http://www.codeotaku.com).
55
-
56
- Permission is hereby granted, free of charge, to any person obtaining a copy
57
- of this software and associated documentation files (the "Software"), to deal
58
- in the Software without restriction, including without limitation the rights
59
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
60
- copies of the Software, and to permit persons to whom the Software is
61
- furnished to do so, subject to the following conditions:
62
-
63
- The above copyright notice and this permission notice shall be included in
64
- all copies or substantial portions of the Software.
65
-
66
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
67
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
68
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
69
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
70
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
71
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
72
- THE SOFTWARE.
data/Rakefile DELETED
@@ -1,6 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
3
-
4
- RSpec::Core::RakeTask.new
5
-
6
- task :default => :spec
data/async-pool.gemspec DELETED
@@ -1,28 +0,0 @@
1
-
2
- require_relative 'lib/async/pool/version'
3
-
4
- Gem::Specification.new do |spec|
5
- spec.name = "async-pool"
6
- spec.version = Async::Pool::VERSION
7
- spec.authors = ["Samuel Williams"]
8
- spec.email = ["samuel.williams@oriontransfer.co.nz"]
9
-
10
- spec.summary = "A Redis client library."
11
- spec.homepage = "https://github.com/socketry/async-pool"
12
-
13
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
14
- f.match(%r{^(test|spec|features)/})
15
- end
16
-
17
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
- spec.require_paths = ["lib"]
19
-
20
- spec.add_dependency("async", "~> 1.8")
21
-
22
- spec.add_development_dependency "async-rspec", "~> 1.1"
23
-
24
- spec.add_development_dependency "covered"
25
- spec.add_development_dependency "bundler"
26
- spec.add_development_dependency "rspec", "~> 3.6"
27
- spec.add_development_dependency "rake"
28
- end