pax_global_header00006660000000000000000000000064143025423510014511gustar00rootroot0000000000000052 comment=d940024cd7a0e8150d55832572a94e81da8f81d0 ruby-async-pool-0.3.12/000077500000000000000000000000001430254235100146375ustar00rootroot00000000000000ruby-async-pool-0.3.12/.editorconfig000066400000000000000000000000641430254235100173140ustar00rootroot00000000000000root = true [*] indent_style = tab indent_size = 2 ruby-async-pool-0.3.12/.github/000077500000000000000000000000001430254235100161775ustar00rootroot00000000000000ruby-async-pool-0.3.12/.github/workflows/000077500000000000000000000000001430254235100202345ustar00rootroot00000000000000ruby-async-pool-0.3.12/.github/workflows/coverage.yaml000066400000000000000000000021201430254235100227060ustar00rootroot00000000000000name: Coverage on: [push, pull_request] permissions: contents: read env: CONSOLE_OUTPUT: XTerm COVERAGE: PartialSummary jobs: test: name: ${{matrix.ruby}} on ${{matrix.os}} runs-on: ${{matrix.os}}-latest strategy: matrix: os: - ubuntu - macos ruby: - "3.1" steps: - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} bundler-cache: true - name: Run tests timeout-minutes: 5 run: bundle exec bake test - uses: actions/upload-artifact@v2 with: name: coverage-${{matrix.os}}-${{matrix.ruby}} path: .covered.db validate: needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: ruby-version: "3.1" bundler-cache: true - uses: actions/download-artifact@v3 - name: Validate coverage timeout-minutes: 5 run: bundle exec bake covered:validate --paths */.covered.db \; ruby-async-pool-0.3.12/.github/workflows/documentation.yaml000066400000000000000000000023171430254235100237740ustar00rootroot00000000000000name: Documentation on: push: branches: - main # Allows you to run this workflow manually from the Actions tab: workflow_dispatch: # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages: permissions: contents: read pages: write id-token: write # Allow one concurrent deployment: concurrency: group: "pages" cancel-in-progress: true env: CONSOLE_OUTPUT: XTerm BUNDLE_WITH: maintenance jobs: generate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: ruby-version: "3.1" 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 --force no - name: Upload documentation artifact uses: actions/upload-pages-artifact@v1 with: path: docs deploy: runs-on: ubuntu-latest environment: name: github-pages url: ${{steps.deployment.outputs.page_url}} needs: generate steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v1 ruby-async-pool-0.3.12/.github/workflows/test-async-head.yaml000066400000000000000000000007571430254235100241220ustar00rootroot00000000000000name: 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 bake test ruby-async-pool-0.3.12/.github/workflows/test-async-v1.yaml000066400000000000000000000007521430254235100235420ustar00rootroot00000000000000name: 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 bake test ruby-async-pool-0.3.12/.github/workflows/test-external.yaml000066400000000000000000000011461430254235100237210ustar00rootroot00000000000000name: Test External on: [push, pull_request] permissions: contents: read env: CONSOLE_OUTPUT: XTerm jobs: test: name: ${{matrix.ruby}} on ${{matrix.os}} runs-on: ${{matrix.os}}-latest strategy: matrix: os: - ubuntu - macos ruby: - "2.7" - "3.0" - "3.1" steps: - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} bundler-cache: true - name: Run tests timeout-minutes: 10 run: bundle exec bake test:external ruby-async-pool-0.3.12/.github/workflows/test.yaml000066400000000000000000000016641430254235100221060ustar00rootroot00000000000000name: Test on: [push, pull_request] permissions: contents: read env: CONSOLE_OUTPUT: XTerm jobs: test: name: ${{matrix.ruby}} on ${{matrix.os}} runs-on: ${{matrix.os}}-latest continue-on-error: ${{matrix.experimental}} strategy: matrix: os: - ubuntu - macos ruby: - "2.7" - "3.0" - "3.1" experimental: [false] include: - os: ubuntu ruby: truffleruby experimental: true - os: ubuntu ruby: jruby experimental: true - os: ubuntu ruby: head experimental: true steps: - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} bundler-cache: true - name: Run tests timeout-minutes: 10 run: bundle exec bake test ruby-async-pool-0.3.12/.gitignore000066400000000000000000000000641430254235100166270ustar00rootroot00000000000000/.bundle/ /pkg/ /gems.locked /.covered.db /external ruby-async-pool-0.3.12/async-pool.gemspec000066400000000000000000000020471430254235100202730ustar00rootroot00000000000000# frozen_string_literal: true 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", "Olle Jonsson", "Simon Perepelitsa"] spec.license = "MIT" spec.cert_chain = ['release.cert'] spec.signing_key = File.expand_path('~/.gem/release.pem') spec.homepage = "https://github.com/socketry/async-pool" spec.metadata = { "funding_uri" => "https://github.com/sponsors/ioquatix/", } spec.files = Dir.glob(['{lib}/**/*', '*.md'], File::FNM_DOTMATCH, base: __dir__) spec.required_ruby_version = ">= 2.5" spec.add_dependency "async", ">= 1.25" spec.add_development_dependency "bake-test" spec.add_development_dependency "bake-test-external" spec.add_development_dependency "bundler" spec.add_development_dependency "covered" spec.add_development_dependency "sus", "~> 0.12" spec.add_development_dependency "sus-fixtures-async" end ruby-async-pool-0.3.12/config/000077500000000000000000000000001430254235100161045ustar00rootroot00000000000000ruby-async-pool-0.3.12/config/external.yaml000066400000000000000000000001271430254235100206120ustar00rootroot00000000000000async-http: url: https://github.com/socketry/async-http command: bundle exec rspec ruby-async-pool-0.3.12/config/sus.rb000066400000000000000000000002241430254235100172410ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2022, by Samuel Williams. require 'covered/sus' include Covered::Sus ruby-async-pool-0.3.12/examples/000077500000000000000000000000001430254235100164555ustar00rootroot00000000000000ruby-async-pool-0.3.12/examples/memory/000077500000000000000000000000001430254235100177655ustar00rootroot00000000000000ruby-async-pool-0.3.12/examples/memory/gems.rb000066400000000000000000000003071430254235100212450ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2022, by Samuel Williams. source 'https://rubygems.org' gem "async" gem "async-redis" gem "async-pool", path: "../../" ruby-async-pool-0.3.12/examples/memory/leak.rb000077500000000000000000000006371430254235100212370ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true # Released under the MIT License. # Copyright, 2022, by Samuel Williams. require "async" require "async/redis" Async do |task| c = Async::Redis::Client.new 10_000.times do |i| c.ping "foo#{i}" end available = c.instance_variable_get("@pool").instance_variable_get("@available") puts "available size #{available.size}, unique #{available.uniq.size}" end ruby-async-pool-0.3.12/examples/overhead/000077500000000000000000000000001430254235100202525ustar00rootroot00000000000000ruby-async-pool-0.3.12/examples/overhead/connect.rb000077500000000000000000000011161430254235100222320ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2022, by Samuel Williams. 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, concurrency: 10) 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.12/examples/overhead/gems.rb000066400000000000000000000003611430254235100215320ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2022, by Samuel Williams. source 'https://rubygems.org' # Takes about ~10s # gem "async-pool", "0.3.3" # Takes about ~30s gem "async-pool", path: "../../" ruby-async-pool-0.3.12/fixtures/000077500000000000000000000000001430254235100165105ustar00rootroot00000000000000ruby-async-pool-0.3.12/fixtures/nonblocking_resource.rb000066400000000000000000000015631430254235100232540ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2022, by Samuel Williams. 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 super 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 super end # Close the resource explicitly, e.g. the pool is being closed. def close Async::Task.current.yield super end # Whether this resource can be reused. Used when releasing resources back into the pool. def reusable? Async::Task.current.yield super end end ruby-async-pool-0.3.12/gems.rb000066400000000000000000000004301430254235100161140ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2020-2022, by Samuel Williams. source 'https://rubygems.org' gemspec group :maintenance, optional: true do gem "bake-gem" gem "bake-modernize" gem "utopia-project" gem "bake-github-pages" end ruby-async-pool-0.3.12/gems/000077500000000000000000000000001430254235100155725ustar00rootroot00000000000000ruby-async-pool-0.3.12/gems/async-head.rb000066400000000000000000000003301430254235100201270ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2022, by Samuel Williams. source 'https://rubygems.org' gemspec path: "../" gem 'async', git: "https://github.com/socketry/async" ruby-async-pool-0.3.12/gems/async-v1.rb000066400000000000000000000002701430254235100175570ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2021-2022, by Samuel Williams. source 'https://rubygems.org' gemspec path: "../" gem 'async', '~> 1.0' ruby-async-pool-0.3.12/lib/000077500000000000000000000000001430254235100154055ustar00rootroot00000000000000ruby-async-pool-0.3.12/lib/async/000077500000000000000000000000001430254235100165225ustar00rootroot00000000000000ruby-async-pool-0.3.12/lib/async/pool.rb000066400000000000000000000002611430254235100200170ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2019-2022, by Samuel Williams. require_relative 'pool/version' require_relative 'pool/controller' ruby-async-pool-0.3.12/lib/async/pool/000077500000000000000000000000001430254235100174735ustar00rootroot00000000000000ruby-async-pool-0.3.12/lib/async/pool/controller.rb000066400000000000000000000153441430254235100222120ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2019-2022, by Samuel Williams. # Copyright, 2020, by Simon Perepelitsa. 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, concurrency: 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 # Set the concurrency to be the same as the limit for maximum performance: if limit concurrency ||= limit else concurrency ||= 1 end @guard = Async::Semaphore.new(concurrency) @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) processed = false # A resource that is not good should also not be reusable. if resource.reusable? processed = reuse(resource) end ensure retire(resource) unless processed end def close @available.clear while pair = @resources.shift resource, usage = pair if usage > 0 Console.logger.warn(self, resource: resource, usage: usage) {"Closing resource while still in use!"} end resource.close end @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 = [] # 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 def retire(resource) Console.logger.debug(self) {"Retire #{resource}"} @resources.delete(resource) resource.close @notification.signal return true 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 def reuse(resource) Console.logger.debug(self) {"Reuse #{resource}"} usage = @resources[resource] if usage.zero? raise "Trying to reuse unacquired resource: #{resource}!" 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.12/lib/async/pool/resource.rb000066400000000000000000000023351430254235100216520ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2019-2022, by Samuel Williams. 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.12/lib/async/pool/version.rb000066400000000000000000000002461430254235100215070ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2019-2022, by Samuel Williams. module Async module Pool VERSION = "0.3.12" end end ruby-async-pool-0.3.12/license.md000066400000000000000000000022101430254235100165760ustar00rootroot00000000000000# MIT License Copyright, 2019-2022, by Samuel Williams. Copyright, 2020, by Simon Perepelitsa. Copyright, 2021, by Olle Jonsson. 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.12/readme.md000066400000000000000000000025121430254235100164160ustar00rootroot00000000000000# Async::Pool Provides support for connection pooling both singleplex and multiplex resources. [![Development Status](https://github.com/socketry/async-pool/workflows/Test/badge.svg)](https://github.com/socketry/async-pool/actions?workflow=Test) ## Installation Add this line to your application's Gemfile: ``` ruby gem 'async-pool' ``` And then execute: ``` bash $ bundle ``` Or install it yourself as: ``` bash $ 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 We welcome contributions to this project. 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. ruby-async-pool-0.3.12/release.cert000066400000000000000000000033141430254235100171370ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIE2DCCA0CgAwIBAgIBATANBgkqhkiG9w0BAQsFADBhMRgwFgYDVQQDDA9zYW11 ZWwud2lsbGlhbXMxHTAbBgoJkiaJk/IsZAEZFg1vcmlvbnRyYW5zZmVyMRIwEAYK CZImiZPyLGQBGRYCY28xEjAQBgoJkiaJk/IsZAEZFgJuejAeFw0yMjA4MDYwNDUz MjRaFw0zMjA4MDMwNDUzMjRaMGExGDAWBgNVBAMMD3NhbXVlbC53aWxsaWFtczEd MBsGCgmSJomT8ixkARkWDW9yaW9udHJhbnNmZXIxEjAQBgoJkiaJk/IsZAEZFgJj bzESMBAGCgmSJomT8ixkARkWAm56MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB igKCAYEAomvSopQXQ24+9DBB6I6jxRI2auu3VVb4nOjmmHq7XWM4u3HL+pni63X2 9qZdoq9xt7H+RPbwL28LDpDNflYQXoOhoVhQ37Pjn9YDjl8/4/9xa9+NUpl9XDIW sGkaOY0eqsQm1pEWkHJr3zn/fxoKPZPfaJOglovdxf7dgsHz67Xgd/ka+Wo1YqoE e5AUKRwUuvaUaumAKgPH+4E4oiLXI4T1Ff5Q7xxv6yXvHuYtlMHhYfgNn8iiW8WN XibYXPNP7NtieSQqwR/xM6IRSoyXKuS+ZNGDPUUGk8RoiV/xvVN4LrVm9upSc0ss RZ6qwOQmXCo/lLcDUxJAgG95cPw//sI00tZan75VgsGzSWAOdjQpFM0l4dxvKwHn tUeT3ZsAgt0JnGqNm2Bkz81kG4A2hSyFZTFA8vZGhp+hz+8Q573tAR89y9YJBdYM zp0FM4zwMNEUwgfRzv1tEVVUEXmoFCyhzonUUw4nE4CFu/sE3ffhjKcXcY//qiSW xm4erY3XAgMBAAGjgZowgZcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O BBYEFO9t7XWuFf2SKLmuijgqR4sGDlRsMC4GA1UdEQQnMCWBI3NhbXVlbC53aWxs aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MC4GA1UdEgQnMCWBI3NhbXVlbC53aWxs aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MA0GCSqGSIb3DQEBCwUAA4IBgQB5sxkE cBsSYwK6fYpM+hA5B5yZY2+L0Z+27jF1pWGgbhPH8/FjjBLVn+VFok3CDpRqwXCl xCO40JEkKdznNy2avOMra6PFiQyOE74kCtv7P+Fdc+FhgqI5lMon6tt9rNeXmnW/ c1NaMRdxy999hmRGzUSFjozcCwxpy/LwabxtdXwXgSay4mQ32EDjqR1TixS1+smp 8C/NCWgpIfzpHGJsjvmH2wAfKtTTqB9CVKLCWEnCHyCaRVuKkrKjqhYCdmMBqCws JkxfQWC+jBVeG9ZtPhQgZpfhvh+6hMhraUYRQ6XGyvBqEUe+yo6DKIT3MtGE2+CP eX9i9ZWBydWb8/rvmwmX2kkcBbX0hZS1rcR593hGc61JR6lvkGYQ2MYskBveyaxt Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8 voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg= -----END CERTIFICATE----- ruby-async-pool-0.3.12/test/000077500000000000000000000000001430254235100156165ustar00rootroot00000000000000ruby-async-pool-0.3.12/test/async/000077500000000000000000000000001430254235100167335ustar00rootroot00000000000000ruby-async-pool-0.3.12/test/async/pool.rb000066400000000000000000000003661430254235100202360ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2022, by Samuel Williams. require 'async/pool' describe Async::Pool do it "has a version number" do expect(Async::Pool::VERSION).to be =~ /\d+\.\d+\.\d+/ end end ruby-async-pool-0.3.12/test/async/pool/000077500000000000000000000000001430254235100177045ustar00rootroot00000000000000ruby-async-pool-0.3.12/test/async/pool/controller.rb000066400000000000000000000105561430254235100224230ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2022, by Samuel Williams. require 'nonblocking_resource' require 'sus/fixtures/async/reactor_context' describe Async::Pool::Controller do include Sus::Fixtures::Async::ReactorContext let(:pool) {subject.new(Async::Pool::Resource)} with '#acquire' do it "can allocate resources" do object = pool.acquire expect(object).not.to be_nil expect(pool).to be(:busy?) pool.release(object) expect(pool).not.to be(:busy?) end end with '#release' do it "will reuse resources" do object = pool.acquire expect(object).to receive(:reusable?).and_return(true) pool.release(object) expect(pool).to be(:active?) end it "will retire unusable resources" do object = pool.acquire expect(object).to receive(:reusable?).and_return(false) pool.release(object) expect(pool).not.to be(:active?) end it "will fail when releasing an unacquired resource" do object = pool.acquire mock(object) do |mock| mock.replace(:reusable?) {true} end pool.release(object) expect do pool.release(object) end.to raise_exception(RuntimeError, message: /unacquired resource/) end end with '#prune' do it "can prune unused resources" do pool.acquire{} expect(pool).to be(:active?) pool.prune expect(pool).not.to be(:active?) end end with '#close' do it "will no longer be active" do object = pool.acquire expect(object).to receive(:reusable?).and_return(true) pool.release(object) pool.close expect(pool).not.to be(:active?) end it "should clear list of available resources" do object = pool.acquire expect(object).to receive(:reusable?).and_return(true) pool.release(object) expect(pool.available).not.to be(:empty?) pool.close expect(pool.available).to be(:empty?) end it "can acquire resource during close" do object = pool.acquire mock(object) do |mock| mock.replace(:close) do pool.acquire{} end end pool.release(object) pool.close expect(pool).not.to be(:active?) end end with '#to_s' do it "can inspect empty pool" do expect(pool.to_s).to be(:match?, "0/∞") end end with 'a small limit' do let(:pool) {subject.new(Async::Pool::Resource, limit: 1)} with '#to_s' do it "can inspect empty pool" do expect(pool.to_s).to be(:match?, "0/1") end end with '#acquire' do it "will limit allocations" do state = nil inner = nil outer = pool.acquire reactor.async do state = :waiting inner = pool.acquire state = :acquired pool.release(inner) end expect(state).to be == :waiting pool.release(outer) reactor.yield expect(state).to be == :acquired expect(outer).to be == inner end end end with "with non-blocking connect" do let(:pool) do subject.wrap do # Simulate a non-blocking connection: Async::Task.current.sleep(0.1) Async::Pool::Resource.new end end with '#acquire' do it "can reuse resources" do 3.times do pool.acquire{} end expect(pool.size).to be == 1 end end end with 'a busy connection pool' do let(:pool) {subject.new(NonblockingResource)} let(:timeout) {60} 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 "robustly releases resources" do failures do begin resource = pool.acquire ensure pool.release(resource) if resource end end expect(pool).not.to be(:busy?) end end end describe Async::Pool::Controller do let(:pool) {subject.new(Async::Pool::Resource)} with '#close' do it "closes all resources when going out of scope" do Async do object = pool.acquire expect(object).not.to be_nil pool.release(object) # There is some resource which is still open: expect(pool.resources).not.to be(:empty?) end expect(pool.resources).to be(:empty?) end end end ruby-async-pool-0.3.12/test/async/pool/multiplex.rb000066400000000000000000000027101430254235100222540ustar00rootroot00000000000000# frozen_string_literal: true # Released under the MIT License. # Copyright, 2022, by Samuel Williams. require 'nonblocking_resource' require 'sus/fixtures/async/reactor_context' describe Async::Pool::Controller do include Sus::Fixtures::Async::ReactorContext let(:constructor) {lambda{Async::Pool::Resource.new(2)}} let(:pool) {subject.new(constructor)} with '#available' do it "is initially empty" do expect(pool.available).to be(:empty?) end it "will put object in available list after one use" do object = pool.acquire mock(object) do |mock| mock.replace(:reusable?) {true} end pool.release(object) expect(pool).to be(:active?) expect(pool.available).to be == [object] end it "can acquire and release the same object up to the concurrency limit" do object1 = pool.acquire mock(object1) do |mock| mock.replace(:reusable?) {true} end object2 = pool.acquire expect(object2).to be(:equal?, object1) expect(pool.available).to be(:empty?) pool.release(object1) expect(pool.available).to be == [object1] pool.release(object2) expect(pool.available).to be == [object1] end end with '#prune' do it "removes the item from the availabilty list when it is retired" do object = pool.acquire mock(object) do |mock| mock.replace(:reusable?) {false} end pool.release(object) pool.prune expect(pool.available).to be == [] end end end