async-pool 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: df755b3a1ec23182af37b80f498c301e42b0e333c97e663b4f065453c66d36cd
4
+ data.tar.gz: 1af8d6d7b3f67fcaad73527dbf278133813564ff152c0733cdf4e7da88704334
5
+ SHA512:
6
+ metadata.gz: 941097d316256c77a43a700468ad8dbd2549a3c433c1a8393913fbeb72d06886b6f0f59d1689902b3ea923e02a2e90ee3dd9269cb14a6b619160aa025d864a80
7
+ data.tar.gz: a8d87b81f9ec2942d9bb7547b712d239df6fe072f39eb9c6d3d6bc81b16c8a76a79f3ff3fe0746ab7bc8428228fcf391d8a05d7606b13729de878dae80185c96
@@ -0,0 +1,6 @@
1
+ root = true
2
+
3
+ [*]
4
+ indent_style = tab
5
+ indent_size = 2
6
+
@@ -0,0 +1,13 @@
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 ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --warnings
3
+ --require spec_helper
@@ -0,0 +1,20 @@
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 ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,72 @@
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.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new
5
+
6
+ task :default => :spec
@@ -0,0 +1,28 @@
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
@@ -0,0 +1,23 @@
1
+ # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'pool/version'
22
+
23
+ require_relative 'pool/controller'
@@ -0,0 +1,208 @@
1
+ # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'async/logger'
22
+
23
+ require 'async/notification'
24
+ require 'async/semaphore'
25
+
26
+ module Async
27
+ module Pool
28
+ class Controller
29
+ def self.wrap(**options, &block)
30
+ self.new(block, **options)
31
+ end
32
+
33
+ def initialize(constructor, limit: nil)
34
+ @resources = {}
35
+ @available = Async::Notification.new
36
+
37
+ @limit = limit
38
+
39
+ @constructor = constructor
40
+ @guard = Async::Semaphore.new(1)
41
+ end
42
+
43
+ # @attr [Hash<Resource, Integer>] all allocated resources, and their associated usage.
44
+ attr :resources
45
+
46
+ # Whether the pool has any active resources.
47
+ def active?
48
+ !@resources.empty?
49
+ end
50
+
51
+ # Whether there are resources which are currently in use.
52
+ def busy?
53
+ @resources.collect do |_, usage|
54
+ return true if usage > 0
55
+ end
56
+
57
+ return false
58
+ end
59
+
60
+ # Wait until a pool resource has been freed.
61
+ def wait
62
+ @available.wait
63
+ end
64
+
65
+ def empty?
66
+ @resources.empty?
67
+ end
68
+
69
+ def acquire
70
+ resource = wait_for_resource
71
+
72
+ return resource unless block_given?
73
+
74
+ begin
75
+ yield resource
76
+ ensure
77
+ release(resource)
78
+ end
79
+ end
80
+
81
+ # Make the resource resources and let waiting tasks know that there is something resources.
82
+ def release(resource)
83
+ # A resource that is not good should also not be reusable.
84
+ if resource.reusable?
85
+ reuse(resource)
86
+ else
87
+ retire(resource)
88
+ end
89
+ end
90
+
91
+ def close
92
+ @resources.each_key(&:close)
93
+ @resources.clear
94
+ end
95
+
96
+ def to_s
97
+ if @resources.empty?
98
+ "\#<#{self.class}(#{usage_string})>"
99
+ else
100
+ "\#<#{self.class}(#{usage_string}) #{availability_string}>"
101
+ end
102
+ end
103
+
104
+ # Retire (and close) all unused resources. If a block is provided, it should implement the desired functionality for unused resources.
105
+ # @param retain [Integer] the minimum number of resources to retain.
106
+ # @yield resource [Resource] unused resources.
107
+ def prune(retain = 0)
108
+ unused = []
109
+
110
+ @resources.each do |resource, usage|
111
+ unused << resource if usage.zero?
112
+ end
113
+
114
+ unused.each do |resource|
115
+ if block_given?
116
+ yield resource
117
+ else
118
+ retire(resource)
119
+ end
120
+
121
+ break if @resources.size <= retain
122
+ end
123
+
124
+ return unused.size
125
+ end
126
+
127
+ def retire(resource)
128
+ Async.logger.debug(self) {"Retire #{resource}"}
129
+
130
+ @resources.delete(resource)
131
+
132
+ resource.close
133
+
134
+ @available.signal
135
+ end
136
+
137
+ protected
138
+
139
+ def usage_string
140
+ "#{@resources.size}/#{@limit || '∞'}"
141
+ end
142
+
143
+ def availability_string
144
+ @resources.collect do |resource,usage|
145
+ "#{usage}/#{resource.concurrency}#{resource.viable? ? nil : '*'}/#{resource.count}"
146
+ end.join(";")
147
+ end
148
+
149
+ def reuse(resource)
150
+ Async.logger.debug(self) {"Reuse #{resource}"}
151
+
152
+ @resources[resource] -= 1
153
+
154
+ @available.signal
155
+ end
156
+
157
+ def wait_for_resource
158
+ # If we fail to create a resource (below), we will end up waiting for one to become resources.
159
+ until resource = available_resource
160
+ @available.wait
161
+ end
162
+
163
+ Async.logger.debug(self) {"Wait for resource #{resource}"}
164
+
165
+ # if resource.concurrency > 1
166
+ # @available.signal
167
+ # end
168
+
169
+ return resource
170
+ end
171
+
172
+ def create_resource
173
+ # This might return nil, which means creating the resource failed.
174
+ if resource = @constructor.call
175
+ @resources[resource] = 1
176
+ end
177
+
178
+ return resource
179
+ end
180
+
181
+ def available_resource
182
+ # TODO This is a linear search... not ideal, but simple for now.
183
+ @resources.each do |resource, count|
184
+ if count < resource.concurrency
185
+ # We want to use this resource... but is it connected?
186
+ if resource.viable?
187
+ @resources[resource] += 1
188
+
189
+ return resource
190
+ else
191
+ retire(resource)
192
+ end
193
+ end
194
+ end
195
+
196
+ @guard.acquire do
197
+ if @limit.nil? or @resources.size < @limit
198
+ Async.logger.debug(self) {"No resources resources, allocating new one..."}
199
+
200
+ return create_resource
201
+ end
202
+ end
203
+
204
+ return nil
205
+ end
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,70 @@
1
+ # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'async/logger'
22
+
23
+ require 'async/notification'
24
+ require 'async/semaphore'
25
+
26
+ module Async
27
+ module Pool
28
+ # The basic interface required by a pool resource.
29
+ class Resource
30
+ # Constructs a resource.
31
+ def self.call
32
+ self.new
33
+ end
34
+
35
+ def initialize(concurrency = 1)
36
+ @concurrency = concurrency
37
+ @closed = false
38
+ @count = 0
39
+ end
40
+
41
+ # @attr [Integer] The concurrency of this resource, 1 (singleplex) or more (multiplex).
42
+ attr :concurrency
43
+
44
+ # @attr [Integer] The number of times this resource has been used.
45
+ attr :count
46
+
47
+ # Whether this resource can be acquired.
48
+ # @return [Boolean] whether the resource can actually be used.
49
+ def viable?
50
+ !@closed
51
+ end
52
+
53
+ # Whether the resource has been closed by the user.
54
+ # @return [Boolean] whether the resource has been closed or has failed.
55
+ def closed?
56
+ @closed
57
+ end
58
+
59
+ # Close the resource explicitly, e.g. the pool is being closed.
60
+ def close
61
+ @closed = true
62
+ end
63
+
64
+ # Whether this resource can be reused. Used when releasing resources back into the pool.
65
+ def reusable?
66
+ !@closed
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,25 @@
1
+ # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Async
22
+ module Pool
23
+ VERSION = "0.1.0"
24
+ end
25
+ end
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: async-pool
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Samuel Williams
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-12-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: async
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.8'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.8'
27
+ - !ruby/object:Gem::Dependency
28
+ name: async-rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.1'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: covered
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.6'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.6'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description:
98
+ email:
99
+ - samuel.williams@oriontransfer.co.nz
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".editorconfig"
105
+ - ".gitignore"
106
+ - ".rspec"
107
+ - ".travis.yml"
108
+ - Gemfile
109
+ - README.md
110
+ - Rakefile
111
+ - async-pool.gemspec
112
+ - lib/async/pool.rb
113
+ - lib/async/pool/controller.rb
114
+ - lib/async/pool/resource.rb
115
+ - lib/async/pool/version.rb
116
+ homepage: https://github.com/socketry/async-pool
117
+ licenses: []
118
+ metadata: {}
119
+ post_install_message:
120
+ rdoc_options: []
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ requirements: []
134
+ rubygems_version: 3.0.4
135
+ signing_key:
136
+ specification_version: 4
137
+ summary: A Redis client library.
138
+ test_files: []