pax_global_header00006660000000000000000000000064140742347100014514gustar00rootroot0000000000000052 comment=746fd4b07254df02ced54fe529dfb89ef84b732c ruby-async-pool-0.3.8/000077500000000000000000000000001407423471000145675ustar00rootroot00000000000000ruby-async-pool-0.3.8/.editorconfig000066400000000000000000000000641407423471000172440ustar00rootroot00000000000000root = true [*] indent_style = tab indent_size = 2 ruby-async-pool-0.3.8/.github/000077500000000000000000000000001407423471000161275ustar00rootroot00000000000000ruby-async-pool-0.3.8/.github/workflows/000077500000000000000000000000001407423471000201645ustar00rootroot00000000000000ruby-async-pool-0.3.8/.github/workflows/async-head.yml000066400000000000000000000007531407423471000227300ustar00rootroot00000000000000name: Async head on: [push, pull_request] jobs: test: runs-on: ${{matrix.os}}-latest strategy: matrix: os: - ubuntu ruby: - head env: BUNDLE_GEMFILE: gems/async-head.rb steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} bundler-cache: true - name: Run tests timeout-minutes: 5 run: bundle exec rspec ruby-async-pool-0.3.8/.github/workflows/async-v1.yml000066400000000000000000000007461407423471000223570ustar00rootroot00000000000000name: Async v1 on: [push, pull_request] jobs: test: runs-on: ${{matrix.os}}-latest strategy: matrix: os: - ubuntu ruby: - 2.7 env: BUNDLE_GEMFILE: gems/async-v1.rb steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} bundler-cache: true - name: Run tests timeout-minutes: 5 run: bundle exec rspec ruby-async-pool-0.3.8/.github/workflows/development.yml000066400000000000000000000015561407423471000232400ustar00rootroot00000000000000name: Development on: [push, pull_request] jobs: test: runs-on: ${{matrix.os}}-latest continue-on-error: ${{matrix.experimental}} strategy: matrix: os: - ubuntu - macos ruby: - "2.6" - "2.7" - "3.0" experimental: [false] env: [""] include: - os: ubuntu ruby: truffleruby experimental: true - os: ubuntu ruby: jruby experimental: true - os: ubuntu ruby: head experimental: true steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} bundler-cache: true - name: Run tests timeout-minutes: 5 run: ${{matrix.env}} bundle exec rspec ruby-async-pool-0.3.8/.github/workflows/documentation.yml000066400000000000000000000011671407423471000235650ustar00rootroot00000000000000name: Documentation on: push: branches: - master jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 env: BUNDLE_WITH: maintenance with: ruby-version: 2.7 bundler-cache: true - name: Installing packages run: sudo apt-get install wget - name: Generate documentation timeout-minutes: 5 run: bundle exec bake utopia:project:static - name: Deploy documentation uses: JamesIves/github-pages-deploy-action@4.0.0 with: branch: docs folder: docs ruby-async-pool-0.3.8/.gitignore000066400000000000000000000002121407423471000165520ustar00rootroot00000000000000/.bundle/ /.yardoc /gems.locked /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ # rspec failure tracking .rspec_status .covered.db ruby-async-pool-0.3.8/.rspec000066400000000000000000000000671407423471000157070ustar00rootroot00000000000000--format documentation --warnings --require spec_helperruby-async-pool-0.3.8/README.md000066400000000000000000000046131407423471000160520ustar00rootroot00000000000000# Async::Pool Provides support for connection pooling both singleplex and multiplex resources. [![Development Status](https://github.com/socketry/async-pool/workflows/Development/badge.svg)](https://github.com/socketry/async-pool/actions?workflow=Development) ## Installation Add this line to your application's Gemfile: ``` ruby gem 'async-pool' ``` And then execute: $ bundle Or install it yourself as: $ gem install async-pool ## Usage `Async::Pool::Controller` provides support for both singleplex (one stream at a time) and multiplex resources (multiple streams at a time). `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. ``` ruby pool = Async::Pool::Controller.new(Async::Pool::Resource) pool.acquire do |resource| # resource is implicitly released when exiting the block. end resource = pool.acquire # Return the resource back to the pool: pool.release(resource) ``` ## Contributing 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request ## License Released under the MIT license. Copyright, 2019, by [Samuel G. D. Williams](http://www.codeotaku.com). Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ruby-async-pool-0.3.8/async-pool.gemspec000066400000000000000000000015731407423471000202260ustar00rootroot00000000000000 require_relative "lib/async/pool/version" Gem::Specification.new do |spec| spec.name = "async-pool" spec.version = Async::Pool::VERSION spec.summary = "A singleplex and multiplex resource pool for implementing robust clients." spec.authors = ["Samuel Williams"] spec.license = "MIT" spec.homepage = "https://github.com/socketry/async-pool" spec.metadata = { "funding_uri" => "https://github.com/sponsors/ioquatix/", } spec.files = Dir.glob('{lib}/**/*', File::FNM_DOTMATCH, base: __dir__) spec.required_ruby_version = ">= 2.5" spec.add_dependency "async", ">= 1.25" spec.add_development_dependency "async-rspec", "~> 1.1" spec.add_development_dependency "bake-bundler" spec.add_development_dependency "bake-modernize" spec.add_development_dependency "bundler" spec.add_development_dependency "covered" spec.add_development_dependency "rspec", "~> 3.6" end ruby-async-pool-0.3.8/examples/000077500000000000000000000000001407423471000164055ustar00rootroot00000000000000ruby-async-pool-0.3.8/examples/overhead/000077500000000000000000000000001407423471000202025ustar00rootroot00000000000000ruby-async-pool-0.3.8/examples/overhead/connect.rb000077500000000000000000000007201407423471000221620ustar00rootroot00000000000000#!/usr/bin/env ruby require 'async' require 'async/pool/controller' require 'async/pool/resource' class MyResource < Async::Pool::Resource def self.call Async::Task.current.sleep(1) self.new end end Async do progress = Console.logger.progress("Pool Usage", 10*10) pool = Async::Pool::Controller.new(MyResource) 10.times do Async do 10.times do resource = pool.acquire pool.release(resource) progress.increment end end end end ruby-async-pool-0.3.8/examples/overhead/gems.rb000066400000000000000000000001341407423471000214600ustar00rootroot00000000000000source 'https://rubygems.org' # gem "async-pool", "0.3.3" gem "async-pool", path: "../../" ruby-async-pool-0.3.8/gems.rb000066400000000000000000000001101407423471000160370ustar00rootroot00000000000000source 'https://rubygems.org' gemspec # gem "async", path: "../async" ruby-async-pool-0.3.8/gems/000077500000000000000000000000001407423471000155225ustar00rootroot00000000000000ruby-async-pool-0.3.8/gems/async-head.rb000066400000000000000000000002111407423471000200550ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' gemspec path: "../" gem 'async', git: "https://github.com/socketry/async" ruby-async-pool-0.3.8/gems/async-v1.rb000066400000000000000000000001511407423471000175050ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' gemspec path: "../" gem 'async', '~> 1.0' ruby-async-pool-0.3.8/lib/000077500000000000000000000000001407423471000153355ustar00rootroot00000000000000ruby-async-pool-0.3.8/lib/async/000077500000000000000000000000001407423471000164525ustar00rootroot00000000000000ruby-async-pool-0.3.8/lib/async/pool.rb000066400000000000000000000022611407423471000177510ustar00rootroot00000000000000# Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative 'pool/version' require_relative 'pool/controller' ruby-async-pool-0.3.8/lib/async/pool/000077500000000000000000000000001407423471000174235ustar00rootroot00000000000000ruby-async-pool-0.3.8/lib/async/pool/controller.rb000066400000000000000000000171231407423471000221370ustar00rootroot00000000000000# Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'console/logger' require 'async' require 'async/notification' require 'async/semaphore' module Async module Pool class Controller def self.wrap(**options, &block) self.new(block, **options) end def initialize(constructor, limit: nil) # All available resources: @resources = {} # Resources which may be available to be acquired: # This list may contain false positives, or resources which were okay but have since entered a state which is unusuable. @available = [] @notification = Async::Notification.new @limit = limit @constructor = constructor @guard = Async::Semaphore.new(1) @gardener = nil end # @attribute [Hash(Resource, Integer)] all allocated resources, and their associated usage. attr :resources def size @resources.size end # Whether the pool has any active resources. def active? !@resources.empty? end # Whether there are resources which are currently in use. def busy? @resources.collect do |_, usage| return true if usage > 0 end return false end # Whether there are available resources, i.e. whether {#acquire} can reuse an existing resource. def available? @available.any? end # Wait until a pool resource has been freed. def wait @notification.wait end def empty? @resources.empty? end def acquire resource = wait_for_resource return resource unless block_given? begin yield resource ensure release(resource) end end # Make the resource resources and let waiting tasks know that there is something resources. def release(resource) reused = false # A resource that is not good should also not be reusable. if resource.reusable? reused = reuse(resource) end ensure retire(resource) unless reused end def close @available.clear @resources.each_key(&:close) @resources.clear @gardener&.stop end def to_s if @resources.empty? "\#<#{self.class}(#{usage_string})>" else "\#<#{self.class}(#{usage_string}) #{availability_string}>" end end # Retire (and close) all unused resources. If a block is provided, it should implement the desired functionality for unused resources. # @param retain [Integer] the minimum number of resources to retain. # @yield resource [Resource] unused resources. def prune(retain = 0) unused = [] @resources.each do |resource, usage| if usage.zero? unused << resource end end 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 def retire(resource) Console.logger.debug(self) {"Retire #{resource}"} @resources.delete(resource) resource.close @notification.signal end protected def start_gardener return if @gardener Async(transient: true, annotation: "#{self.class} Gardener") do |task| @gardener = task Task.yield ensure @gardener = nil self.close end end def usage_string "#{@resources.size}/#{@limit || '∞'}" end def availability_string @resources.collect do |resource,usage| "#{usage}/#{resource.concurrency}#{resource.viable? ? nil : '*'}/#{resource.count}" end.join(";") end def usage @resources.count{|resource, usage| usage > 0} end def free @resources.count{|resource, usage| usage == 0} end # @returns [Boolean] Whether the number of available resources is excessive and we should retire some. def overflowing? if @resources.any? (self.free.to_f / @resources.size) > 0.5 end end def reuse(resource) Console.logger.debug(self) {"Reuse #{resource}"} usage = @resources[resource] if usage.zero? raise "Trying to reuse unacquired resource: #{resource}!" end # We retire resources when adding to the @available list would overflow our pool: if usage == 1 if overflowing? return retire(resource) end end # If the resource was fully utilized, it now becomes available: if usage == resource.concurrency @available.push(resource) end @resources[resource] = usage - 1 @notification.signal return true end def wait_for_resource # If we fail to create a resource (below), we will end up waiting for one to become resources. until resource = available_resource @notification.wait end Console.logger.debug(self) {"Wait for resource -> #{resource}"} # if resource.concurrency > 1 # @notification.signal # end return resource end # @returns [Object] A new resource in a "used" state. def create_resource self.start_gardener # This might return nil, which means creating the resource failed. if resource = @constructor.call @resources[resource] = 1 # Make the resource available if it can be used multiple times: if resource.concurrency > 1 @available.push(resource) end end return resource end # @returns [Object] An existing resource in a "used" state. def available_resource resource = nil @guard.acquire do resource = get_resource end return resource rescue Exception reuse(resource) if resource raise end private def get_resource while resource = @available.last if usage = @resources[resource] and usage < resource.concurrency if resource.viable? usage = (@resources[resource] += 1) if usage == resource.concurrency # The resource is used up to it's limit: @available.pop end return resource else retire(resource) @available.pop end else # The resource has been removed already, so skip it and remove it from the availability list. @available.pop end end if @limit.nil? or @resources.size < @limit Console.logger.debug(self) {"No available resources, allocating new one..."} return create_resource end end end end end ruby-async-pool-0.3.8/lib/async/pool/resource.rb000066400000000000000000000043341407423471000216030ustar00rootroot00000000000000# Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'console/logger' require 'async/notification' require 'async/semaphore' module Async module Pool # The basic interface required by a pool resource. class Resource # Constructs a resource. def self.call self.new end def initialize(concurrency = 1) @concurrency = concurrency @closed = false @count = 0 end # @attr [Integer] The concurrency of this resource, 1 (singleplex) or more (multiplex). attr :concurrency # @attr [Integer] The number of times this resource has been used. attr :count # Whether this resource can be acquired. # @return [Boolean] whether the resource can actually be used. def viable? !@closed end # Whether the resource has been closed by the user. # @return [Boolean] whether the resource has been closed or has failed. def closed? @closed end # Close the resource explicitly, e.g. the pool is being closed. def close @closed = true end # Whether this resource can be reused. Used when releasing resources back into the pool. def reusable? !@closed end end end end ruby-async-pool-0.3.8/lib/async/pool/version.rb000066400000000000000000000022441407423471000214370ustar00rootroot00000000000000# Copyright, 2017, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. module Async module Pool VERSION = "0.3.8" end end ruby-async-pool-0.3.8/spec/000077500000000000000000000000001407423471000155215ustar00rootroot00000000000000ruby-async-pool-0.3.8/spec/async/000077500000000000000000000000001407423471000166365ustar00rootroot00000000000000ruby-async-pool-0.3.8/spec/async/pool/000077500000000000000000000000001407423471000176075ustar00rootroot00000000000000ruby-async-pool-0.3.8/spec/async/pool/controller_helper.rb000066400000000000000000000036101407423471000236560ustar00rootroot00000000000000# Copyright, 2019, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'async/pool/controller' require 'async/pool/resource' class Async::Pool::Controller attr :available end class NonblockingResource < Async::Pool::Resource # Whether this resource can be acquired. # @return [Boolean] whether the resource can actually be used. def viable? Async::Task.current.yield !@closed end # Whether the resource has been closed by the user. # @return [Boolean] whether the resource has been closed or has failed. def closed? Async::Task.current.yield @closed end # Close the resource explicitly, e.g. the pool is being closed. def close Async::Task.current.yield @closed = true end # Whether this resource can be reused. Used when releasing resources back into the pool. def reusable? Async::Task.current.yield !@closed end end ruby-async-pool-0.3.8/spec/async/pool/controller_spec.rb000066400000000000000000000127361407423471000233420ustar00rootroot00000000000000# Copyright, 2019, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative 'controller_helper' RSpec.describe Async::Pool::Controller, timeout: 1 do include_context Async::RSpec::Reactor subject {described_class.new(Async::Pool::Resource)} describe '#acquire' do it "can allocate resources" do object = subject.acquire expect(object).to_not be_nil expect(subject).to be_busy subject.release(object) expect(subject).to_not be_busy end end describe '#release' do it "will reuse resources" do object = subject.acquire expect(object).to receive(:reusable?).and_return(true) subject.release(object) expect(subject).to be_active end it "will retire unusable resources" do object = subject.acquire expect(object).to receive(:reusable?).and_return(false) subject.release(object) expect(subject).to_not be_active end it "will fail when releasing an unacquired resource" do object = subject.acquire allow(object).to receive(:reusable?).and_return(true) subject.release(object) expect do subject.release(object) end.to raise_exception(/unacquired resource/) end it "will overflow after freeing 50% of resources" do objects = 10.times.map do subject.acquire.tap do |object| allow(object).to receive(:reusable?).and_return(true) end end 10.times do subject.release(objects.pop) end expect(subject.available.size).to be == 6 end end describe '#prune' do it "can prune unused resources" do subject.acquire{} expect(subject).to be_active subject.prune expect(subject).to_not be_active end end describe '#close' do it "will no longer be active" do object = subject.acquire expect(object).to receive(:reusable?).and_return(true) subject.release(object) subject.close expect(subject).to_not be_active end it "should clear list of available resources" do object = subject.acquire expect(object).to receive(:reusable?).and_return(true) subject.release(object) expect(subject.available).to_not be_empty subject.close expect(subject.available).to be_empty end end describe '#to_s' do it "can inspect empty pool" do expect(subject.to_s).to match("0/∞") end end context "with limit" do subject {described_class.new(Async::Pool::Resource, limit: 1)} describe '#to_s' do it "can inspect empty pool" do expect(subject.to_s).to match("0/1") end end describe '#acquire' do it "will limit allocations" do state = nil inner = nil outer = subject.acquire reactor.async do state = :waiting inner = subject.acquire state = :acquired subject.release(inner) end expect(state).to be :waiting subject.release(outer) reactor.yield expect(state).to be :acquired expect(outer).to be inner end end end context "with non-blocking connect" do subject do described_class.wrap do # Simulate a non-blocking connection: Async::Task.current.sleep(0.1) Async::Pool::Resource.new end end describe '#acquire' do it "can reuse resources" do 3.times do subject.acquire{} end expect(subject.size).to be == 1 end end end context "robustness", timeout: 10 do subject {described_class.new(NonblockingResource)} def failures(repeats: 500, time_scale: 0.001, &block) count = 0 backtraces = Set.new Sync do |task| while count < repeats begin task.with_timeout(rand * time_scale, &block) rescue Async::TimeoutError => error backtraces << error.backtrace.first(10) count += 1 else if count.zero? time_scale /= 2 end end end end # pp backtraces end it "releases resources" do failures do begin resource = subject.acquire ensure subject.release(resource) if resource end end expect(subject).to_not be_busy end end end RSpec.describe Async::Pool::Controller, timeout: 1 do subject {described_class.new(Async::Pool::Resource)} describe '#close' do it "closes all resources when going out of scope" do Async do object = subject.acquire expect(object).to_not be_nil subject.release(object) # There is some resource which is still open: expect(subject.resources).to_not be_empty end expect(subject.resources).to be_empty end end end ruby-async-pool-0.3.8/spec/async/pool/multiplex_spec.rb000066400000000000000000000046711407423471000232010ustar00rootroot00000000000000# Copyright, 2019, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative 'controller_helper' RSpec.describe Async::Pool::Controller, timeout: 1 do include_context Async::RSpec::Reactor let(:constructor) {lambda{Async::Pool::Resource.new(2)}} subject {described_class.new(constructor)} describe '#available' do it "is initially empty" do expect(subject.available).to be_empty end it "will put object in available list after one use" do object = subject.acquire allow(object).to receive(:reusable?).and_return(true) subject.release(object) expect(subject).to be_active expect(subject.available).to be == [object] end it "can acquire and release the same object up to the concurrency limit" do object1 = subject.acquire allow(object1).to receive(:reusable?).and_return(true) object2 = subject.acquire expect(object2).to be_equal(object1) expect(subject.available).to be_empty subject.release(object1) expect(subject.available).to be == [object1] subject.release(object2) expect(subject.available).to be == [object1] end end describe '#prune' do it "removes the item from the availabilty list when it is retired" do object = subject.acquire allow(object).to receive(:reusable?).and_return(false) subject.release(object) subject.prune expect(subject.available).to be == [] end end end ruby-async-pool-0.3.8/spec/spec_helper.rb000066400000000000000000000005161407423471000203410ustar00rootroot00000000000000 require 'covered/rspec' require 'async/rspec' # Console.logger.level = Logger::DEBUG RSpec.configure do |config| config.disable_monkey_patching # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = ".rspec_status" config.expect_with :rspec do |c| c.syntax = :expect end end