pax_global_header00006660000000000000000000000064143527626740014532gustar00rootroot0000000000000052 comment=4ad88e91fe32a18b22e493b87271683f80171761 ruby-async-io-1.34.1/000077500000000000000000000000001435276267400143215ustar00rootroot00000000000000ruby-async-io-1.34.1/.editorconfig000066400000000000000000000000641435276267400167760ustar00rootroot00000000000000root = true [*] indent_style = tab indent_size = 2 ruby-async-io-1.34.1/.github/000077500000000000000000000000001435276267400156615ustar00rootroot00000000000000ruby-async-io-1.34.1/.github/workflows/000077500000000000000000000000001435276267400177165ustar00rootroot00000000000000ruby-async-io-1.34.1/.github/workflows/async-head.yml000066400000000000000000000007531435276267400224620ustar00rootroot00000000000000name: 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-io-1.34.1/.github/workflows/async-v1.yml000066400000000000000000000007461435276267400221110ustar00rootroot00000000000000name: 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-io-1.34.1/.github/workflows/development.yml000066400000000000000000000020271435276267400227640ustar00rootroot00000000000000name: Development on: [push, pull_request] 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.6" - "2.7" - "3.0" - "3.1" - "3.2" experimental: [false] env: [""] include: - os: ubuntu ruby: truffleruby experimental: true env: JRUBY_OPTS="--debug -X+O" - os: ubuntu ruby: jruby experimental: true env: 'JRUBY_OPTS="-X+O --debug"' - 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-io-1.34.1/.github/workflows/documentation.yml000066400000000000000000000011651435276267400233150ustar00rootroot00000000000000name: Documentation on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 env: BUNDLE_WITH: maintenance with: ruby-version: 3.0 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-io-1.34.1/.gitignore000066400000000000000000000002041435276267400163050ustar00rootroot00000000000000/.bundle/ /.yardoc /gems.locked /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ # rspec failure tracking .rspec_status .tags ruby-async-io-1.34.1/.rspec000066400000000000000000000000671435276267400154410ustar00rootroot00000000000000--format documentation --warnings --require spec_helperruby-async-io-1.34.1/README.md000066400000000000000000000130141435276267400155770ustar00rootroot00000000000000# Async::IO Async::IO provides builds on [async](https://github.com/socketry/async) and provides asynchronous wrappers for `IO`, `Socket`, and related classes. [![Development Status](https://github.com/socketry/async-io/workflows/Development/badge.svg)](https://github.com/socketry/async-io/actions?workflow=Development) ## Installation Add this line to your application's Gemfile: ``` ruby gem 'async-io' ``` And then execute: $ bundle Or install it yourself as: $ gem install async-io ## Usage Basic echo server (from `spec/async/io/echo_spec.rb`): ``` ruby require 'async/io' def echo_server(endpoint) Async do |task| # This is a synchronous block within the current task: endpoint.accept do |client| # This is an asynchronous block within the current reactor: data = client.read # This produces out-of-order responses. task.sleep(rand * 0.01) client.write(data.reverse) client.close_write end end end def echo_client(endpoint, data) Async do |task| endpoint.connect do |peer| peer.write(data) peer.close_write message = peer.read puts "Sent #{data}, got response: #{message}" end end end Async do endpoint = Async::IO::Endpoint.tcp('0.0.0.0', 9000) server = echo_server(endpoint) 5.times.map do |i| echo_client(endpoint, "Hello World #{i}") end.each(&:wait) server.stop end ``` ### Timeouts Timeouts add a temporal limit to the execution of your code. If the IO doesn't respond in time, it will fail. Timeouts are high level concerns and you generally shouldn't use them except at the very highest level of your program. ``` ruby message = task.with_timeout(5) do begin peer.read rescue Async::TimeoutError nil # The timeout was triggered. end end ``` Any `yield` operation can cause a timeout to trigger. Non-`async` functions might not timeout because they are outside the scope of `async`. #### Wrapper Timeouts Asynchronous operations may block forever. You can assign a per-wrapper operation timeout duration. All asynchronous operations will be bounded by this timeout. ``` ruby peer.timeout = 1 peer.read # If this takes more than 1 second, Async::TimeoutError will be raised. ``` The benefit of this approach is that it applies to all operations. Typically, this would be configured by the user, and set to something pretty high, e.g. 120 seconds. ### Reading Characters This example shows how to read one character at a time as the user presses it on the keyboard, and echos it back out as uppercase: ``` ruby require 'async' require 'async/io/stream' require 'io/console' $stdin.raw! $stdin.echo = false Async do |task| stdin = Async::IO::Stream.new( Async::IO::Generic.new($stdin) ) while character = stdin.read(1) $stdout.write character.upcase end end ``` ### Deferred Buffering `Async::IO::Stream.new(..., deferred:true)` creates a deferred stream which increases latency slightly, but reduces the number of total packets sent. It does this by combining all calls `Stream#flush` within a single iteration of the reactor. This is typically more useful on the client side, but can also be useful on the server side when individual packets have high latency. It should be preferable to send one 100 byte packet than 10x 10 byte packets. Servers typically only deal with one request per iteartion of the reactor so it's less useful. Clients which make multiple requests can benefit significantly e.g. HTTP/2 clients can merge many requests into a single packet. Because HTTP/2 recommends disabling Nagle's algorithm, this is often beneficial. ## 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 ## See Also - [async](https://github.com/socketry/async) — Asynchronous event-driven reactor. - [async-process](https://github.com/socketry/async-process) — Asynchronous process spawning/waiting. - [async-websocket](https://github.com/socketry/async-websocket) — Asynchronous client and server websockets. - [async-dns](https://github.com/socketry/async-dns) — Asynchronous DNS resolver and server. - [async-rspec](https://github.com/socketry/async-rspec) — Shared contexts for running async specs. - [rubydns](https://github.com/ioquatix/rubydns) — A easy to use Ruby DNS server. ## License Released under the MIT license. Copyright, 2017, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-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. ruby-async-io-1.34.1/async-io.gemspec000066400000000000000000000016611435276267400174140ustar00rootroot00000000000000# frozen_string_literal: true require_relative "lib/async/io/version" Gem::Specification.new do |spec| spec.name = "async-io" spec.version = Async::IO::VERSION spec.summary = "Provides support for asynchonous TCP, UDP, UNIX and SSL sockets." spec.authors = ["Samuel Williams", "Aurora", "Olle Jonsson", "Bruno Sutic", "Benoit Daloze", "Cyril Roelandt", "Thibaut Girka", "Jiang Jinyang", "Janko Marohnić"] spec.license = "MIT" spec.homepage = "https://github.com/socketry/async-io" spec.files = Dir.glob('{lib}/**/*', File::FNM_DOTMATCH, base: __dir__) spec.required_ruby_version = ">= 2.5" spec.add_dependency "async" spec.add_development_dependency "async-container", "~> 0.15" spec.add_development_dependency "async-rspec", "~> 1.10" spec.add_development_dependency "bake" spec.add_development_dependency "covered" spec.add_development_dependency "rack-test" spec.add_development_dependency "rspec", "~> 3.6" end ruby-async-io-1.34.1/benchmark/000077500000000000000000000000001435276267400162535ustar00rootroot00000000000000ruby-async-io-1.34.1/benchmark/pipe.rb000077500000000000000000000010201435276267400175310ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true require 'async' require 'async/io' require 'benchmark/ips' def measure(pipe, count) i, o = pipe count.times do o.write("Hello World") i.read(11) end end Benchmark.ips do |benchmark| benchmark.time = 10 benchmark.warmup = 2 benchmark.report("Async::IO.pipe") do |count| Async do |task| measure(::Async::IO.pipe, count) end end benchmark.report("IO.pipe") do |count| Async do |task| measure(::IO.pipe, count) end end benchmark.compare! end ruby-async-io-1.34.1/examples/000077500000000000000000000000001435276267400161375ustar00rootroot00000000000000ruby-async-io-1.34.1/examples/allocations/000077500000000000000000000000001435276267400204475ustar00rootroot00000000000000ruby-async-io-1.34.1/examples/allocations/byteslice.rb000066400000000000000000000014661435276267400227660ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true require_relative 'memory' string = nil measure_memory("Initial allocation") do string = "a" * 5*1024*1024 string.freeze end # => 5.0 MB measure_memory("Byteslice from start to middle") do # Why does this need to allocate memory? Surely it can share the original allocation? x = string.byteslice(0, string.bytesize / 2) end # => 2.5 MB measure_memory("Byteslice from middle to end") do string.byteslice(string.bytesize / 2, string.bytesize) end # => 0.0 MB measure_memory("Slice! from start to middle") do string.dup.slice!(0, string.bytesize / 2) end # => 7.5 MB measure_memory("Byte slice into two halves") do head = string.byteslice(0, string.bytesize / 2) # 2.5 MB remainder = string.byteslice(string.bytesize / 2, string.bytesize) # Shared end # 2.5 MB ruby-async-io-1.34.1/examples/allocations/memory.rb000066400000000000000000000005761435276267400223140ustar00rootroot00000000000000# frozen_string_literal: true def measure_memory(annotation = "Memory allocated") GC.disable start_memory = `ps -p #{Process::pid} -o rss`.split("\n")[1].chomp.to_i yield ensure end_memory = `ps -p #{Process::pid} -o rss`.split("\n")[1].chomp.to_i memory_usage = (end_memory - start_memory).to_f / 1024 puts "#{memory_usage.round(1)} MB: #{annotation}" GC.enable end ruby-async-io-1.34.1/examples/allocations/read_chunks.rb000077500000000000000000000005711435276267400232700ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true require_relative 'memory' require_relative "../../lib/async/io/stream" require "stringio" measure_memory("Stream setup") do @io = StringIO.new("a" * (50*1024*1024)) @stream = Async::IO::Stream.new(@io) end # 50.0 MB measure_memory("Read all chunks") do while chunk = @stream.read_partial chunk.clear end end # 0.5 MB ruby-async-io-1.34.1/examples/chat/000077500000000000000000000000001435276267400170565ustar00rootroot00000000000000ruby-async-io-1.34.1/examples/chat/client.rb000077500000000000000000000024341435276267400206670ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true $LOAD_PATH << File.expand_path("../../lib", __dir__) require 'async' require 'async/notification' require 'async/io/stream' require 'async/io/host_endpoint' require 'async/io/protocol/line' class User < Async::IO::Protocol::Line end endpoint = Async::IO::Endpoint.parse(ARGV.pop || "tcp://localhost:7138") input = Async::IO::Protocol::Line.new( Async::IO::Stream.new( Async::IO::Generic.new($stdin) ) ) Async do |task| socket = endpoint.connect stream = Async::IO::Stream.new(socket) user = User.new(stream) # This is used to track whether either reading from stdin failed or reading from network failed. finished = Async::Notification.new # Read lines from stdin and write to network. terminal = task.async do while line = input.read_line user.write_lines line end rescue EOFError # It's okay, we are disconnecting, because stdin has closed. ensure finished.signal end # Read lines from network and write to stdout. network = task.async do while line = user.read_line puts line end ensure finished.signal end # Wait for any of the above processes to finish: finished.wait ensure # Stop all the nested tasks if we are exiting: network.stop if network terminal.stop if terminal user.close if user end ruby-async-io-1.34.1/examples/chat/server.rb000077500000000000000000000034661435276267400207250ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true $LOAD_PATH << File.expand_path("../../lib", __dir__) require 'set' require 'logger' require 'async' require 'async/io/host_endpoint' require 'async/io/protocol/line' class User < Async::IO::Protocol::Line attr_accessor :name def login! self.write_lines "Tell me your name, traveller:" self.name = self.read_line end def to_s @name || "unknown" end end class Server def initialize @users = Set.new end def broadcast(*message) puts *message @users.each do |user| begin user.write_lines(*message) rescue EOFError # In theory, it's possible this will fail if the remote end has disconnected. Each user has it's own task running `#connected`, and eventually `user.read_line` will fail. When it does, the disconnection logic will be invoked. A better way to do this would be to have a message queue, but for the sake of keeping this example simple, this is by far the better option. end end end def connected(user) user.login! broadcast("#{user} has joined") user.write_lines("currently connected: #{@users.map(&:to_s).join(', ')}") while message = user.read_line broadcast("#{user.name}: #{message}") end rescue EOFError # It's okay, client has disconnected. ensure disconnected(user) end def disconnected(user, reason = "quit") @users.delete(user) broadcast("#{user} has disconnected: #{reason}") end def run(endpoint) Async do |task| endpoint.accept do |peer| stream = Async::IO::Stream.new(peer) user = User.new(stream) @users << user connected(user) end end end end Console.logger.level = Logger::INFO Console.logger.info("Starting server...") server = Server.new endpoint = Async::IO::Endpoint.parse(ARGV.pop || "tcp://localhost:7138") server.run(endpoint) ruby-async-io-1.34.1/examples/defer/000077500000000000000000000000001435276267400172245ustar00rootroot00000000000000ruby-async-io-1.34.1/examples/defer/worker.rb000066400000000000000000000006161435276267400210650ustar00rootroot00000000000000#!/usr/bin/env ruby require 'async' require 'async/io/notification' def defer(*args, &block) Async do notification = Async::IO::Notification.new thread = Thread.new(*args) do yield ensure notification.signal end notification.wait thread.join end end Async do 10.times do defer do puts "I'm going to sleep" sleep 1 puts "I'm going to wake up" end end end ruby-async-io-1.34.1/examples/echo/000077500000000000000000000000001435276267400170555ustar00rootroot00000000000000ruby-async-io-1.34.1/examples/echo/client.rb000077500000000000000000000007051435276267400206650ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../../lib", __dir__) require 'async' require 'async/io/trap' require 'async/io/host_endpoint' require 'async/io/stream' endpoint = Async::IO::Endpoint.tcp('localhost', 4578) Async do |task| endpoint.connect do |peer| stream = Async::IO::Stream.new(peer) while true task.sleep 1 stream.puts "Hello World!" puts stream.gets.inspect end end end ruby-async-io-1.34.1/examples/echo/server.rb000077500000000000000000000024141435276267400207140ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../../lib", __dir__) require 'async' require 'async/io/trap' require 'async/io/host_endpoint' require 'async/io/stream' endpoint = Async::IO::Endpoint.tcp('localhost', 4578) interrupt = Async::IO::Trap.new(:INT) Async do |top| interrupt.install! endpoint.bind do |server, task| Console.logger.info(server) {"Accepting connections on #{server.local_address.inspect}"} task.async do |subtask| interrupt.wait Console.logger.info(server) {"Closing server socket..."} server.close interrupt.default! Console.logger.info(server) {"Waiting for connections to close..."} subtask.sleep(4) Console.logger.info(server) do |buffer| buffer.puts "Stopping all tasks..." task.print_hierarchy(buffer) buffer.puts "", "Reactor Hierarchy" task.reactor.print_hierarchy(buffer) end task.stop end server.listen(128) server.accept_each do |peer| stream = Async::IO::Stream.new(peer) while chunk = stream.read_partial Console.logger.debug(self) {chunk.inspect} stream.write(chunk) stream.flush Console.logger.info(server) do |buffer| task.reactor.print_hierarchy(buffer) end end end end end ruby-async-io-1.34.1/examples/issues/000077500000000000000000000000001435276267400174525ustar00rootroot00000000000000ruby-async-io-1.34.1/examples/issues/broken_ssl.rb000066400000000000000000000006231435276267400221410ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true require 'socket' require 'openssl' server = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM) server.bind(Addrinfo.tcp('127.0.0.1', 4433)) server.listen(128) ssl_server = OpenSSL::SSL::SSLServer.new(server, OpenSSL::SSL::SSLContext.new) puts ssl_server.addr # openssl/ssl.rb:234:in `addr': undefined method `addr' for # (NoMethodError) ruby-async-io-1.34.1/examples/issues/pipes.rb000066400000000000000000000011031435276267400211120ustar00rootroot00000000000000# frozen_string_literal: true # wat.rb require 'async' require_relative '../../lib/async/io' require 'digest/sha1' require 'securerandom' Async.run do |task| r, w = IO.pipe.map { |io| Async::IO.try_convert(io) } task.async do |subtask| s = Digest::SHA1.new l = 0 100.times do bytes = SecureRandom.bytes(4000) s << bytes w << bytes l += bytes.bytesize end w.close p [:write, l, s.hexdigest] end task.async do |subtask| s = Digest::SHA1.new l = 0 while b = r.read(4096) s << b l += b.bytesize end p [:read, l, s.hexdigest] end end ruby-async-io-1.34.1/examples/millions/000077500000000000000000000000001435276267400177655ustar00rootroot00000000000000ruby-async-io-1.34.1/examples/millions/client.rb000077500000000000000000000016661435276267400216040ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true $LOAD_PATH << File.expand_path("../../lib", __dir__) require 'async/reactor' require 'async/io/host_endpoint' require 'async/container' require 'async/container/forked' endpoint = Async::IO::Endpoint.parse(ARGV.pop || "tcp://localhost:7234") CONNECTIONS = 1_000_000 CONCURRENCY = Async::Container.processor_count TASKS = 16 REPEATS = (CONNECTIONS.to_f / (TASKS * CONCURRENCY)).ceil puts "Starting #{CONCURRENCY} processes, running #{TASKS} tasks, making #{REPEATS} connections." puts "Total number of connections: #{CONCURRENCY * TASKS * REPEATS}!" begin container = Async::Container::Forked.new container.run(count: CONCURRENCY) do Async do |task| connections = [] TASKS.times do task.async do REPEATS.times do $stdout.write "." connections << endpoint.connect end end end end end container.wait ensure container.stop if container end ruby-async-io-1.34.1/examples/millions/server.rb000077500000000000000000000014321435276267400216230ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true $LOAD_PATH << File.expand_path("../../lib", __dir__) require 'set' require 'logger' require 'async' require 'async/reactor' require 'async/io/host_endpoint' require 'async/io/protocol/line' class Server def initialize @connections = [] end def run(endpoint) Async do |task| task.async do |subtask| while true subtask.sleep 10 puts "Connection count: #{@connections.size}" end end endpoint.accept do |peer| stream = Async::IO::Stream.new(peer) @connections << stream end end end end Console.logger.level = Logger::INFO Console.logger.info("Starting server...") server = Server.new endpoint = Async::IO::Endpoint.parse(ARGV.pop || "tcp://localhost:7234") server.run(endpoint) ruby-async-io-1.34.1/examples/udp.rb000066400000000000000000000010301435276267400172460ustar00rootroot00000000000000#!/usr/bin/env ruby require 'async' require_relative '../lib/async/io' endpoint = Async::IO::Endpoint.udp("localhost", 5300) Async do |task| endpoint.bind do |socket| # This block executes for both IPv4 and IPv6 UDP sockets: loop do data, address = socket.recvfrom(1024) pp data pp address end end # This will try connecting to all addresses and yield for the first one that successfully connects: endpoint.connect do |socket| loop do task.sleep rand(1..10) socket.send "Hello World!", 0 end end end ruby-async-io-1.34.1/examples/udp/000077500000000000000000000000001435276267400167275ustar00rootroot00000000000000ruby-async-io-1.34.1/examples/udp/client.rb000077500000000000000000000003711435276267400205360ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true require 'async' require 'async/io' endpoint = Async::IO::Endpoint.udp("localhost", 5678) Async do |task| endpoint.connect do |socket| socket.send("Hello World") pp socket.recv(1024) end end ruby-async-io-1.34.1/examples/udp/server.rb000077500000000000000000000004471435276267400205720ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true require 'async' require 'async/io' endpoint = Async::IO::Endpoint.udp("localhost", 5678) Async do |task| endpoint.bind do |socket| while true data, address = socket.recvfrom(1024) socket.send(data.reverse, 0, address) end end end ruby-async-io-1.34.1/gems.rb000066400000000000000000000004101435276267400155740ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' gemspec group :maintenance, optional: true do gem "bake-modernize" gem "bake-gem" gem "utopia-project" end group :test do gem 'benchmark-ips' gem 'ruby-prof', platforms: :mri gem 'http' end ruby-async-io-1.34.1/gems/000077500000000000000000000000001435276267400152545ustar00rootroot00000000000000ruby-async-io-1.34.1/gems/async-head.rb000066400000000000000000000002111435276267400176070ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' gemspec path: "../" gem 'async', git: "https://github.com/socketry/async" ruby-async-io-1.34.1/gems/async-v1.rb000066400000000000000000000001511435276267400172370ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' gemspec path: "../" gem 'async', '~> 1.0' ruby-async-io-1.34.1/lib/000077500000000000000000000000001435276267400150675ustar00rootroot00000000000000ruby-async-io-1.34.1/lib/async/000077500000000000000000000000001435276267400162045ustar00rootroot00000000000000ruby-async-io-1.34.1/lib/async/io.rb000066400000000000000000000027621435276267400171470ustar00rootroot00000000000000# frozen_string_literal: true # 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 'async' require_relative "io/generic" require_relative "io/socket" require_relative "io/version" require_relative "io/endpoint" require_relative "io/endpoint/each" module Async module IO def self.file_descriptor_limit Process.getrlimit(Process::RLIMIT_NOFILE).first end def self.buffer? ::IO.const_defined?(:Buffer) end end end ruby-async-io-1.34.1/lib/async/io/000077500000000000000000000000001435276267400166135ustar00rootroot00000000000000ruby-async-io-1.34.1/lib/async/io/address.rb000066400000000000000000000023241435276267400205660ustar00rootroot00000000000000# frozen_string_literal: true # 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 'socket' module Async module IO Address = Addrinfo end end ruby-async-io-1.34.1/lib/async/io/address_endpoint.rb000066400000000000000000000037721435276267400224760ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, 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 'endpoint' module Async module IO # This class will open and close the socket automatically. class AddressEndpoint < Endpoint def initialize(address, **options) super(**options) @address = address end def to_s "\#<#{self.class} #{@address.inspect}>" end attr :address # Bind a socket to the given address. If a block is given, the socket will be automatically closed when the block exits. # @yield [Socket] the bound socket # @return [Socket] the bound socket def bind(&block) Socket.bind(@address, **@options, &block) end # Connects a socket to the given address. If a block is given, the socket will be automatically closed when the block exits. # @return [Socket] the connected socket def connect(&block) Socket.connect(@address, **@options, &block) end end end end ruby-async-io-1.34.1/lib/async/io/binary_string.rb000066400000000000000000000023701435276267400220140ustar00rootroot00000000000000# frozen_string_literal: true # 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 'buffer' module Async module IO # This is deprecated. BinaryString = Buffer end end ruby-async-io-1.34.1/lib/async/io/buffer.rb000066400000000000000000000027211435276267400204130ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, 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 IO class Buffer < String BINARY = Encoding::BINARY def initialize super force_encoding(BINARY) end def << string if string.encoding == BINARY super(string) else super(string.b) end return self end alias concat << end end end ruby-async-io-1.34.1/lib/async/io/composite_endpoint.rb000066400000000000000000000034141435276267400230440ustar00rootroot00000000000000# frozen_string_literal: true # 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 'endpoint' module Async module IO class CompositeEndpoint < Endpoint def initialize(endpoints, **options) super(**options) @endpoints = endpoints end def each(&block) @endpoints.each(&block) end def connect(&block) error = nil @endpoints.each do |endpoint| begin return endpoint.connect(&block) rescue => error end end raise error end def bind(&block) @endpoints.map(&:bind) end end class Endpoint def self.composite(*endpoints, **options) CompositeEndpoint.new(endpoints, **options) end end end end ruby-async-io-1.34.1/lib/async/io/endpoint.rb000066400000000000000000000106731435276267400207670ustar00rootroot00000000000000# frozen_string_literal: true # 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 'address' require_relative 'socket' require 'uri' module Async module IO # Endpoints represent a way of connecting or binding to an address. class Endpoint def initialize(**options) @options = options.freeze end def with(**options) dup = self.dup dup.options = @options.merge(options) return dup end attr_accessor :options # @return [String] The hostname of the bound socket. def hostname @options[:hostname] end # If `SO_REUSEPORT` is enabled on a socket, the socket can be successfully bound even if there are existing sockets bound to the same address, as long as all prior bound sockets also had `SO_REUSEPORT` set before they were bound. # @return [Boolean, nil] The value for `SO_REUSEPORT`. def reuse_port @options[:reuse_port] end # If `SO_REUSEADDR` is enabled on a socket prior to binding it, the socket can be successfully bound unless there is a conflict with another socket bound to exactly the same combination of source address and port. Additionally, when set, binding a socket to the address of an existing socket in `TIME_WAIT` is not an error. # @return [Boolean] The value for `SO_REUSEADDR`. def reuse_address @options[:reuse_address] end # Controls SO_LINGER. The amount of time the socket will stay in the `TIME_WAIT` state after being closed. # @return [Integer, nil] The value for SO_LINGER. def linger @options[:linger] end # @return [Numeric] The default timeout for socket operations. def timeout @options[:timeout] end # @return [Address] the address to bind to before connecting. def local_address @options[:local_address] end # Endpoints sometimes have multiple paths. # @yield [Endpoint] Enumerate all discrete paths as endpoints. def each return to_enum unless block_given? yield self end # Accept connections from the specified endpoint. # @param backlog [Integer] the number of connections to listen for. def accept(backlog = Socket::SOMAXCONN, &block) bind do |server| server.listen(backlog) server.accept_each(&block) end end # Map all endpoints by invoking `#bind`. # @yield the bound wrapper. def bound wrappers = [] self.each do |endpoint| wrapper = endpoint.bind wrappers << wrapper yield wrapper end return wrappers ensure wrappers.each(&:close) if $! end # Create an Endpoint instance by URI scheme. The host and port of the URI will be passed to the Endpoint factory method, along with any options. # # @param string [String] URI as string. Scheme will decide implementation used. # @param options keyword arguments passed through to {#initialize} # # @see Endpoint.ssl ssl - invoked when parsing a URL with the ssl scheme "ssl://127.0.0.1" # @see Endpoint.tcp tcp - invoked when parsing a URL with the tcp scheme: "tcp://127.0.0.1" # @see Endpoint.udp udp - invoked when parsing a URL with the udp scheme: "udp://127.0.0.1" # @see Endpoint.unix unix - invoked when parsing a URL with the unix scheme: "unix://127.0.0.1" def self.parse(string, **options) uri = URI.parse(string) self.public_send(uri.scheme, uri.host, uri.port, **options) end end end end ruby-async-io-1.34.1/lib/async/io/endpoint/000077500000000000000000000000001435276267400204335ustar00rootroot00000000000000ruby-async-io-1.34.1/lib/async/io/endpoint/each.rb000066400000000000000000000040211435276267400216550ustar00rootroot00000000000000# frozen_string_literal: true # 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 "../host_endpoint" require_relative "../socket_endpoint" require_relative "../ssl_endpoint" module Async module IO class Endpoint def self.try_convert(specification) if specification.is_a? self specification elsif specification.is_a? Array self.send(*specification) elsif specification.is_a? String self.parse(specification) elsif specification.is_a? ::BasicSocket self.socket(specification) elsif specification.is_a? Generic self.new(specification) else raise ArgumentError.new("Not sure how to convert #{specification} to endpoint!") end end # Generate a list of endpoints from an array. def self.each(specifications, &block) return to_enum(:each, specifications) unless block_given? specifications.each do |specification| yield try_convert(specification) end end end end end ruby-async-io-1.34.1/lib/async/io/generic.rb000066400000000000000000000144521435276267400205620ustar00rootroot00000000000000# frozen_string_literal: true # 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 'async/wrapper' require 'forwardable' module Async module IO # The default block size for IO buffers. Defaults to 64KB (typical pipe buffer size). BLOCK_SIZE = ENV.fetch('ASYNC_IO_BLOCK_SIZE', 1024*64).to_i # The maximum read size when appending to IO buffers. Defaults to 8MB. MAXIMUM_READ_SIZE = ENV.fetch('ASYNC_IO_MAXIMUM_READ_SIZE', BLOCK_SIZE * 128).to_i # Convert a Ruby ::IO object to a wrapped instance: def self.try_convert(io, &block) if wrapper_class = Generic::WRAPPERS[io.class] wrapper_class.new(io, &block) else raise ArgumentError.new("Unsure how to wrap #{io.class}!") end end def self.pipe ::IO.pipe.map(&Generic.method(:new)) end # Represents an asynchronous IO within a reactor. class Generic < Wrapper extend Forwardable WRAPPERS = {} class << self # @!macro [attach] wrap_blocking_method # @method $1 # Invokes `$2` on the underlying {io}. If the operation would block, the current task is paused until the operation can succeed, at which point it's resumed and the operation is completed. def wrap_blocking_method(new_name, method_name, invert: true, &block) if block_given? define_method(new_name, &block) else define_method(new_name) do |*args| async_send(method_name, *args) end end if invert # We wrap the original _nonblock method, ignoring options. define_method(method_name) do |*args, exception: false| async_send(method_name, *args) end end end attr :wrapped_klass def wraps(klass, *additional_methods) @wrapped_klass = klass WRAPPERS[klass] = self # These are methods implemented by the wrapped class, that we aren't overriding, that may be of interest: # fallback_methods = klass.instance_methods(false) - instance_methods # puts "Forwarding #{klass} methods #{fallback_methods} to @io" def_delegators :@io, *additional_methods end # Instantiate a wrapped instance of the class, and optionally yield it to a given block, closing it afterwards. def wrap(*args) wrapper = self.new(@wrapped_klass.new(*args)) return wrapper unless block_given? begin yield wrapper ensure wrapper.close end end end wraps ::IO, :external_encoding, :internal_encoding, :autoclose?, :autoclose=, :pid, :stat, :binmode, :flush, :set_encoding, :set_encoding_by_bom, :to_path, :to_io, :to_i, :reopen, :fileno, :fsync, :fdatasync, :sync, :sync=, :tell, :seek, :rewind, :path, :pos, :pos=, :eof, :eof?, :close_on_exec?, :close_on_exec=, :closed?, :close_read, :close_write, :isatty, :tty?, :binmode?, :sysseek, :advise, :ioctl, :fcntl, :nread, :ready?, :pread, :pwrite, :pathconf # Read the specified number of bytes from the input stream. This is fast path. # @example # data = io.sysread(512) wrap_blocking_method :sysread, :read_nonblock alias readpartial read_nonblock # Read `length` bytes of data from the underlying I/O. If length is unspecified, read everything. def read(length = nil, buffer = nil) if buffer buffer.clear else buffer = String.new end if length return String.new(encoding: Encoding::BINARY) if length <= 0 # Fast path: if buffer = self.sysread(length, buffer) # Slow path: while buffer.bytesize < length # Slow path: if chunk = self.sysread(length - buffer.bytesize) buffer << chunk else break end end return buffer else return nil end else buffer = self.sysread(BLOCK_SIZE, buffer) while chunk = self.sysread(BLOCK_SIZE) buffer << chunk end return buffer end end # Write entire buffer to output stream. This is fast path. # @example # io.syswrite("Hello World") wrap_blocking_method :syswrite, :write_nonblock def write(buffer) # Fast path: written = self.syswrite(buffer) remaining = buffer.bytesize - written while remaining > 0 # Slow path: length = self.syswrite(buffer.byteslice(written, remaining)) remaining -= length written += length end return written end def << buffer write(buffer) return self end def dup super.tap do |copy| copy.timeout = self.timeout end end def wait(timeout = self.timeout, mode = :read) case mode when :read wait_readable(timeout) when :write wait_writable(timeout) else wait_any(:rw, timeout) end rescue TimeoutError return nil end def nonblock true end def nonblock= value true end def nonblock? true end def connected? !@io.closed? end attr_accessor :timeout protected def async_send(*arguments, timeout: self.timeout) while true result = @io.__send__(*arguments, exception: false) case result when :wait_readable wait_readable(timeout) when :wait_writable wait_writable(timeout) else return result end end end end end end ruby-async-io-1.34.1/lib/async/io/host_endpoint.rb000066400000000000000000000073741435276267400220300ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, 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 'address_endpoint' module Async module IO class HostEndpoint < Endpoint def initialize(specification, **options) super(**options) @specification = specification end def to_s nodename, service, family, socktype, protocol, flags = @specification "\#<#{self.class} name=#{nodename.inspect} service=#{service.inspect} family=#{family.inspect} type=#{socktype.inspect} protocol=#{protocol.inspect} flags=#{flags.inspect}>" end def address @specification end def hostname @specification.first end # Try to connect to the given host by connecting to each address in sequence until a connection is made. # @yield [Socket] the socket which is being connected, may be invoked more than once # @return [Socket] the connected socket # @raise if no connection could complete successfully def connect last_error = nil task = Task.current Addrinfo.foreach(*@specification) do |address| begin wrapper = Socket.connect(address, **@options, task: task) rescue Errno::ECONNREFUSED, Errno::ENETUNREACH, Errno::EAGAIN last_error = $! else return wrapper unless block_given? begin return yield wrapper, task ensure wrapper.close end end end raise last_error end # Invokes the given block for every address which can be bound to. # @yield [Socket] the bound socket # @return [Array] an array of bound sockets def bind(&block) Addrinfo.foreach(*@specification).map do |address| Socket.bind(address, **@options, &block) end end # @yield [AddressEndpoint] address endpoints by resolving the given host specification def each return to_enum unless block_given? Addrinfo.foreach(*@specification) do |address| yield AddressEndpoint.new(address, **@options) end end end class Endpoint # @param args nodename, service, family, socktype, protocol, flags. `socktype` will be set to Socket::SOCK_STREAM. # @param options keyword arguments passed on to {HostEndpoint#initialize} # # @return [HostEndpoint] def self.tcp(*args, **options) args[3] = ::Socket::SOCK_STREAM HostEndpoint.new(args, **options) end # @param args nodename, service, family, socktype, protocol, flags. `socktype` will be set to Socket::SOCK_DGRAM. # @param options keyword arguments passed on to {HostEndpoint#initialize} # # @return [HostEndpoint] def self.udp(*args, **options) args[3] = ::Socket::SOCK_DGRAM HostEndpoint.new(args, **options) end end end end ruby-async-io-1.34.1/lib/async/io/notification.rb000066400000000000000000000040021435276267400216220ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, 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 'generic' module Async module IO # A cross-reactor/process notification pipe. class Notification def initialize pipe = ::IO.pipe # We could call wait and signal from different reactors/threads/processes, so we don't create wrappers here, because they are not thread safe by design. @input = pipe.first @output = pipe.last end def close @input.close @output.close end # Wait for signal to be called. # @return [Object] def wait wrapper = Async::IO::Generic.new(@input) wrapper.read(1) ensure # Remove the wrapper from the reactor. wrapper.reactor = nil end # Signal to a given task that it should resume operations. # @return [void] def signal wrapper = Async::IO::Generic.new(@output) wrapper.write(".") ensure wrapper.reactor = nil end end end end ruby-async-io-1.34.1/lib/async/io/peer.rb000066400000000000000000000054561435276267400201050ustar00rootroot00000000000000# frozen_string_literal: true # 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 'socket' module Async module IO module Peer include ::Socket::Constants # Is it likely that the socket is still connected? # May return false positive, but won't return false negative. def connected? return false if @io.closed? # If we can wait for the socket to become readable, we know that the socket may still be open. result = to_io.recv_nonblock(1, MSG_PEEK, exception: false) # Either there was some data available, or we can wait to see if there is data avaialble. return !result.empty? || result == :wait_readable rescue Errno::ECONNRESET # This might be thrown by recv_nonblock. return false end def eof !connected? end def eof? !connected? end # Best effort to set *_NODELAY if it makes sense. Swallows errors where possible. def sync=(value) super case self.protocol when 0, IPPROTO_TCP self.setsockopt(IPPROTO_TCP, TCP_NODELAY, value ? 1 : 0) else Console.logger.warn(self) {"Unsure how to sync=#{value} for #{self.protocol}!"} end rescue Errno::EINVAL # On Darwin, sometimes occurs when the connection is not yet fully formed. Empirically, TCP_NODELAY is enabled despite this result. rescue Errno::EOPNOTSUPP # Some platforms may simply not support the operation. # Console.logger.warn(self) {"Unable to set sync=#{value}!"} end def sync case self.protocol when IPPROTO_TCP self.getsockopt(IPPROTO_TCP, TCP_NODELAY).bool else true end && super end def type self.local_address.socktype end def protocol self.local_address.protocol end end end end ruby-async-io-1.34.1/lib/async/io/protocol/000077500000000000000000000000001435276267400204545ustar00rootroot00000000000000ruby-async-io-1.34.1/lib/async/io/protocol/generic.rb000066400000000000000000000026521435276267400224220ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, 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 '../stream' module Async module IO module Protocol class Generic def initialize(stream) @stream = stream end def closed? @stream.closed? end def close @stream.close end attr :stream end end end end ruby-async-io-1.34.1/lib/async/io/protocol/line.rb000077500000000000000000000040561435276267400217400ustar00rootroot00000000000000# frozen_string_literal: true # 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 'generic' module Async module IO module Protocol class Line < Generic def initialize(stream, eol = $/) super(stream) @eol = eol end attr :eol def write_lines(*args) if args.empty? @stream.write(@eol) else args.each do |arg| @stream.write(arg) @stream.write(@eol) end end @stream.flush end def read_line @stream.read_until(@eol) or @stream.eof! end def peek_line @stream.peek do |read_buffer| if index = read_buffer.index(@eol) return read_buffer.slice(0, index) end end raise EOFError end def each_line return to_enum(:each_line) unless block_given? while line = @stream.read_until(@eol) yield line end end def read_lines @stream.read.split(@eol) end end end end end ruby-async-io-1.34.1/lib/async/io/server.rb000066400000000000000000000030361435276267400204500ustar00rootroot00000000000000# frozen_string_literal: true # 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 'async/task' module Async module IO module Server def accept_each(timeout: nil, task: Task.current) task.annotate "accepting connections #{self.local_address.inspect} [fd=#{self.fileno}]" callback = lambda do |io, address| yield io, address, task: task end while true self.accept(timeout: timeout, task: task, &callback) end end end end end ruby-async-io-1.34.1/lib/async/io/shared_endpoint.rb000066400000000000000000000075121435276267400223130ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, 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 'endpoint' require_relative 'composite_endpoint' module Async module IO # Pre-connect and pre-bind sockets so that it can be used between processes. class SharedEndpoint < Endpoint # Create a new `SharedEndpoint` by binding to the given endpoint. def self.bound(endpoint, backlog: Socket::SOMAXCONN, close_on_exec: false) wrappers = endpoint.bound do |server| # This is somewhat optional. We want to have a generic interface as much as possible so that users of this interface can just call it without knowing a lot of internal details. Therefore, we ignore errors here if it's because the underlying socket does not support the operation. begin server.listen(backlog) rescue Errno::EOPNOTSUPP # Ignore. end server.close_on_exec = close_on_exec server.reactor = nil end return self.new(endpoint, wrappers) end # Create a new `SharedEndpoint` by connecting to the given endpoint. def self.connected(endpoint, close_on_exec: false) wrapper = endpoint.connect wrapper.close_on_exec = close_on_exec wrapper.reactor = nil return self.new(endpoint, [wrapper]) end def initialize(endpoint, wrappers, **options) super(**options) @endpoint = endpoint @wrappers = wrappers end attr :endpoint attr :wrappers def local_address_endpoint(**options) endpoints = @wrappers.map do |wrapper| AddressEndpoint.new(wrapper.to_io.local_address) end return CompositeEndpoint.new(endpoints, **options) end def remote_address_endpoint(**options) endpoints = @wrappers.map do |wrapper| AddressEndpoint.new(wrapper.to_io.remote_address) end return CompositeEndpoint.new(endpoints, **options) end # Close all the internal wrappers. def close @wrappers.each(&:close) @wrappers.clear end def bind task = Async::Task.current @wrappers.each do |server| server = server.dup task.async do |task| task.annotate "binding to #{server.inspect}" begin yield server, task ensure server.close end end end end def connect task = Async::Task.current @wrappers.each do |peer| peer = peer.dup task.async do |task| task.annotate "connected to #{peer.inspect} [#{peer.fileno}]" begin yield peer, task ensure peer.close end end end end def accept(backlog = nil, &block) bind do |server| server.accept_each(&block) end end def to_s "\#<#{self.class} #{@wrappers.size} descriptors for #{@endpoint}>" end end end end ruby-async-io-1.34.1/lib/async/io/socket.rb000066400000000000000000000144461435276267400204410ustar00rootroot00000000000000# frozen_string_literal: true # 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 'socket' require 'async/task' require_relative 'peer' require_relative 'server' require_relative 'generic' module Async module IO class BasicSocket < Generic wraps ::BasicSocket, :setsockopt, :connect_address, :close_read, :close_write, :local_address, :remote_address, :do_not_reverse_lookup, :do_not_reverse_lookup=, :shutdown, :getsockopt, :getsockname, :getpeername, :getpeereid wrap_blocking_method :recv, :recv_nonblock wrap_blocking_method :recvmsg, :recvmsg_nonblock wrap_blocking_method :sendmsg, :sendmsg_nonblock wrap_blocking_method :send, :sendmsg_nonblock, invert: false include Peer end class Socket < BasicSocket wraps ::Socket, :bind, :ipv6only!, :listen wrap_blocking_method :recvfrom, :recvfrom_nonblock # @raise Errno::EAGAIN the connection failed due to the remote end being overloaded. def connect(*args) begin async_send(:connect_nonblock, *args) rescue Errno::EISCONN # We are now connected. end end alias connect_nonblock connect # @param timeout [Numeric] the maximum time to wait for accepting a connection, if specified. def accept(timeout: nil, task: Task.current) peer, address = async_send(:accept_nonblock, timeout: timeout) wrapper = Socket.new(peer, task.reactor) wrapper.timeout = self.timeout return wrapper, address unless block_given? task.async do |task| task.annotate "incoming connection #{address.inspect} [fd=#{wrapper.fileno}]" begin yield wrapper, address ensure wrapper.close end end end alias accept_nonblock accept alias sysaccept accept # Build and wrap the underlying io. # @option reuse_port [Boolean] Allow this port to be bound in multiple processes. # @option reuse_address [Boolean] Allow this port to be bound in multiple processes. def self.build(*args, timeout: nil, reuse_address: true, reuse_port: nil, linger: nil, task: Task.current) socket = wrapped_klass.new(*args) if reuse_address socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) end if reuse_port socket.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1) end if linger socket.setsockopt(SOL_SOCKET, SO_LINGER, linger) end yield socket wrapper = self.new(socket, task.reactor) wrapper.timeout = timeout return wrapper rescue Exception socket.close if socket raise end # Establish a connection to a given `remote_address`. # @example # socket = Async::IO::Socket.connect(Async::IO::Address.tcp("8.8.8.8", 53)) # @param remote_address [Address] The remote address to connect to. # @option local_address [Address] The local address to bind to before connecting. def self.connect(remote_address, local_address: nil, task: Task.current, **options) Console.logger.debug(self) {"Connecting to #{remote_address.inspect}"} task.annotate "connecting to #{remote_address.inspect}" wrapper = build(remote_address.afamily, remote_address.socktype, remote_address.protocol, **options) do |socket| if local_address if defined?(IP_BIND_ADDRESS_NO_PORT) # Inform the kernel (Linux 4.2+) to not reserve an ephemeral port when using bind(2) with a port number of 0. The port will later be automatically chosen at connect(2) time, in a way that allows sharing a source port as long as the 4-tuple is unique. socket.setsockopt(SOL_IP, IP_BIND_ADDRESS_NO_PORT, 1) end socket.bind(local_address.to_sockaddr) end end begin wrapper.connect(remote_address.to_sockaddr) task.annotate "connected to #{remote_address.inspect} [fd=#{wrapper.fileno}]" rescue Exception wrapper.close raise end return wrapper unless block_given? begin yield wrapper, task ensure wrapper.close end end # Bind to a local address. # @example # socket = Async::IO::Socket.bind(Async::IO::Address.tcp("0.0.0.0", 9090)) # @param local_address [Address] The local address to bind to. # @option protocol [Integer] The socket protocol to use. def self.bind(local_address, protocol: 0, task: Task.current, **options, &block) Console.logger.debug(self) {"Binding to #{local_address.inspect}"} wrapper = build(local_address.afamily, local_address.socktype, protocol, **options) do |socket| socket.bind(local_address.to_sockaddr) end return wrapper unless block_given? task.async do |task| task.annotate "binding to #{wrapper.local_address.inspect}" begin yield wrapper, task ensure wrapper.close end end end # Bind to a local address and accept connections in a loop. def self.accept(*args, backlog: SOMAXCONN, &block) bind(*args) do |server, task| server.listen(backlog) if backlog server.accept_each(task: task, &block) end end include Server def self.pair(*args) ::Socket.pair(*args).map(&self.method(:new)) end end class IPSocket < BasicSocket wraps ::IPSocket, :addr, :peeraddr wrap_blocking_method :recvfrom, :recvfrom_nonblock end end end ruby-async-io-1.34.1/lib/async/io/socket_endpoint.rb000066400000000000000000000040201435276267400223240ustar00rootroot00000000000000# frozen_string_literal: true # 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 'endpoint' module Async module IO # This class doesn't exert ownership over the specified socket, wraps a native ::IO. class SocketEndpoint < Endpoint def initialize(socket, **options) super(**options) # This socket should already be in the required state. @socket = Async::IO.try_convert(socket) end def to_s "\#<#{self.class} #{@socket.inspect}>" end attr :socket def bind(&block) if block_given? begin yield @socket ensure @socket.reactor = nil end else return @socket end end def connect(&block) if block_given? begin yield @socket ensure @socket.reactor = nil end else return @socket end end end class Endpoint def self.socket(socket, **options) SocketEndpoint.new(socket, **options) end end end end ruby-async-io-1.34.1/lib/async/io/ssl_endpoint.rb000066400000000000000000000063721435276267400216510ustar00rootroot00000000000000# frozen_string_literal: true # 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 'host_endpoint' require_relative 'ssl_socket' module Async module IO class SSLEndpoint < Endpoint def initialize(endpoint, **options) super(**options) @endpoint = endpoint if ssl_context = options[:ssl_context] @context = build_context(ssl_context) else @context = nil end end def to_s "\#<#{self.class} #{@endpoint}>" end def address @endpoint.address end def hostname @options[:hostname] || @endpoint.hostname end attr :endpoint attr :options def params @options[:ssl_params] end def build_context(context = OpenSSL::SSL::SSLContext.new) if params = self.params context.set_params(params) end context.setup context.freeze return context end def context @context ||= build_context end # Connect to the underlying endpoint and establish a SSL connection. # @yield [Socket] the socket which is being connected # @return [Socket] the connected socket def bind if block_given? @endpoint.bind do |server| yield SSLServer.new(server, context) end else return SSLServer.new(@endpoint.bind, context) end end # Connect to the underlying endpoint and establish a SSL connection. # @yield [Socket] the socket which is being connected # @return [Socket] the connected socket def connect(&block) SSLSocket.connect(@endpoint.connect, context, hostname, &block) end def each return to_enum unless block_given? @endpoint.each do |endpoint| yield self.class.new(endpoint, **@options) end end end # Backwards compatibility. SecureEndpoint = SSLEndpoint class Endpoint # @param args # @param ssl_context [OpenSSL::SSL::SSLContext, nil] # @param hostname [String, nil] # @param options keyword arguments passed through to {Endpoint.tcp} # # @return [SSLEndpoint] def self.ssl(*args, ssl_context: nil, hostname: nil, **options) SSLEndpoint.new(self.tcp(*args, **options), ssl_context: ssl_context, hostname: hostname) end end end end ruby-async-io-1.34.1/lib/async/io/ssl_socket.rb000066400000000000000000000105701435276267400213140ustar00rootroot00000000000000# frozen_string_literal: true # 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 'socket' require 'openssl' module Async module IO SSLError = OpenSSL::SSL::SSLError # Asynchronous TCP socket wrapper. class SSLSocket < Generic wraps OpenSSL::SSL::SSLSocket, :alpn_protocol, :cert, :cipher, :client_ca, :context, :export_keying_material, :finished_message, :peer_finished_message, :getsockopt, :hostname, :hostname=, :npn_protocol, :peer_cert, :peer_cert_chain, :pending, :post_connection_check, :setsockopt, :session, :session=, :session_reused?, :ssl_version, :state, :sync_close, :sync_close=, :sysclose, :verify_result, :tmp_key wrap_blocking_method :accept, :accept_nonblock wrap_blocking_method :connect, :connect_nonblock def self.connect(socket, context, hostname = nil, &block) client = self.new(socket, context) # Used for SNI: if hostname client.hostname = hostname end begin client.connect rescue # If the connection fails (e.g. certificates are invalid), the caller never sees the socket, so we close it and raise the exception up the chain. client.close raise end return client unless block_given? begin yield client ensure client.close end end include Peer def initialize(socket, context) if socket.is_a?(self.class.wrapped_klass) super else io = self.class.wrapped_klass.new(socket.to_io, context) super(io, socket.reactor) # We detach the socket from the reactor, otherwise it's possible to add the file descriptor to the selector twice, which is bad. socket.reactor = nil # This ensures that when the internal IO is closed, it also closes the internal socket: io.sync_close = true @timeout = socket.timeout end end def local_address @io.to_io.local_address end def remote_address @io.to_io.remote_address end def close_write self.shutdown(Socket::SHUT_WR) end def close_read self.shutdown(Socket::SHUT_RD) end def shutdown(how) @io.flush @io.to_io.shutdown(how) end end # We reimplement this from scratch because the native implementation doesn't expose the underlying server/context that we need to implement non-blocking accept. class SSLServer extend Forwardable def initialize(server, context) @server = server @context = context end def fileno @server.fileno end def dup self.class.new(@server.dup, @context) end def_delegators :@server, :local_address, :setsockopt, :getsockopt, :close, :close_on_exec=, :reactor=, :timeout, :timeout= attr :server attr :context def listen(*args) @server.listen(*args) end def accept(task: Task.current, **options) peer, address = @server.accept(**options) wrapper = SSLSocket.new(peer, @context) return wrapper, address unless block_given? task.async do task.annotate "accepting secure connection #{address.inspect}" begin # You want to do this in a nested async task or you might suffer from head-of-line blocking. wrapper.accept yield wrapper, address ensure wrapper.close end end end include Server end end end ruby-async-io-1.34.1/lib/async/io/standard.rb000066400000000000000000000030721435276267400207420ustar00rootroot00000000000000# frozen_string_literal: true # 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 'generic' module Async module IO class StandardInput < Generic def initialize(io = $stdin) super(io) end end class StandardOutput < Generic def initialize(io = $stdout) super(io) end end class StandardError < Generic def initialize(io = $stderr) super(io) end end STDIN = StandardInput.new STDOUT = StandardOutput.new STDERR = StandardError.new end end ruby-async-io-1.34.1/lib/async/io/stream.rb000066400000000000000000000202161435276267400204340ustar00rootroot00000000000000# frozen_string_literal: true # 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 'buffer' require_relative 'generic' require 'async/semaphore' module Async module IO class Stream BLOCK_SIZE = IO::BLOCK_SIZE def self.open(path, mode = "r+", **options) stream = self.new(File.open(path, mode), **options) return stream unless block_given? begin yield stream ensure stream.close end end def initialize(io, block_size: BLOCK_SIZE, maximum_read_size: MAXIMUM_READ_SIZE, sync: true, deferred: false) @io = io @eof = false @pending = 0 # This field is ignored, but used to mean, try to buffer packets in a single iteration of the reactor. # @deferred = deferred @writing = Async::Semaphore.new(1) # We don't want Ruby to do any IO buffering. @io.sync = sync @block_size = block_size @maximum_read_size = maximum_read_size @read_buffer = Buffer.new @write_buffer = Buffer.new @drain_buffer = Buffer.new # Used as destination buffer for underlying reads. @input_buffer = Buffer.new end attr :io attr :block_size # Reads `size` bytes from the stream. If size is not specified, read until end of file. def read(size = nil) return String.new(encoding: Encoding::BINARY) if size == 0 if size until @eof or @read_buffer.bytesize >= size # Compute the amount of data we need to read from the underlying stream: read_size = size - @read_buffer.bytesize # Don't read less than @block_size to avoid lots of small reads: fill_read_buffer(read_size > @block_size ? read_size : @block_size) end else until @eof fill_read_buffer end end return consume_read_buffer(size) end # Read at most `size` bytes from the stream. Will avoid reading from the underlying stream if possible. def read_partial(size = nil) return String.new(encoding: Encoding::BINARY) if size == 0 if !@eof and @read_buffer.empty? fill_read_buffer end return consume_read_buffer(size) end def read_exactly(size, exception: EOFError) if buffer = read(size) if buffer.bytesize != size raise exception, "could not read enough data" end return buffer end raise exception, "encountered eof while reading data" end alias readpartial read_partial # Efficiently read data from the stream until encountering pattern. # @param pattern [String] The pattern to match. # @return [String] The contents of the stream up until the pattern, which is consumed but not returned. def read_until(pattern, offset = 0, chomp: true) # We don't want to split on the pattern, so we subtract the size of the pattern. split_offset = pattern.bytesize - 1 until index = @read_buffer.index(pattern, offset) offset = @read_buffer.bytesize - split_offset offset = 0 if offset < 0 return unless fill_read_buffer end @read_buffer.freeze matched = @read_buffer.byteslice(0, index+(chomp ? 0 : pattern.bytesize)) @read_buffer = @read_buffer.byteslice(index+pattern.bytesize, @read_buffer.bytesize) return matched end def peek until yield(@read_buffer) or @eof fill_read_buffer end end def gets(separator = $/, **options) read_until(separator, **options) end # Flushes buffered data to the stream. def flush return if @write_buffer.empty? @writing.acquire do # Flip the write buffer and drain buffer: @write_buffer, @drain_buffer = @drain_buffer, @write_buffer begin @io.write(@drain_buffer) ensure # If the write operation fails, we still need to clear this buffer, and the data is essentially lost. @drain_buffer.clear end end end # Writes `string` to the buffer. When the buffer is full or #sync is true the # buffer is flushed to the underlying `io`. # @param string the string to write to the buffer. # @return the number of bytes appended to the buffer. def write(string) @write_buffer << string if @write_buffer.bytesize >= @block_size flush end return string.bytesize end # Writes `string` to the stream and returns self. def <<(string) write(string) return self end def puts(*arguments, separator: $/) arguments.each do |argument| @write_buffer << argument << separator end flush end def connected? @io.connected? end def closed? @io.closed? end def close_read @io.close_read end def close_write flush ensure @io.close_write end # Best effort to flush any unwritten data, and then close the underling IO. def close return if @io.closed? begin flush rescue # We really can't do anything here unless we want #close to raise exceptions. ensure @io.close end end # Returns true if the stream is at file which means there is no more data to be read. def eof? if !@read_buffer.empty? return false elsif @eof return true else return @io.eof? end end alias eof eof? def eof! @read_buffer.clear @eof = true raise EOFError end private # Fills the buffer from the underlying stream. def fill_read_buffer(size = @block_size) # We impose a limit because the underlying `read` system call can fail if we request too much data in one go. if size > @maximum_read_size size = @maximum_read_size end # This effectively ties the input and output stream together. flush if @read_buffer.empty? if @io.read_nonblock(size, @read_buffer, exception: false) # Console.logger.debug(self, name: "read") {@read_buffer.inspect} return true end else if chunk = @io.read_nonblock(size, @input_buffer, exception: false) @read_buffer << chunk # Console.logger.debug(self, name: "read") {@read_buffer.inspect} return true end end # else for both cases above: @eof = true return false end # Consumes at most `size` bytes from the buffer. # @param size [Integer|nil] The amount of data to consume. If nil, consume entire buffer. def consume_read_buffer(size = nil) # If we are at eof, and the read buffer is empty, we can't consume anything. return nil if @eof && @read_buffer.empty? result = nil if size.nil? or size >= @read_buffer.bytesize # Consume the entire read buffer: result = @read_buffer @read_buffer = Buffer.new else # This approach uses more memory. # result = @read_buffer.slice!(0, size) # We know that we are not going to reuse the original buffer. # But byteslice will generate a hidden copy. So let's freeze it first: @read_buffer.freeze result = @read_buffer.byteslice(0, size) @read_buffer = @read_buffer.byteslice(size, @read_buffer.bytesize) end return result end end end end ruby-async-io-1.34.1/lib/async/io/tcp_socket.rb000066400000000000000000000064451435276267400213070ustar00rootroot00000000000000# frozen_string_literal: true # 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 'socket' require_relative 'stream' require 'fcntl' module Async module IO # Asynchronous TCP socket wrapper. class TCPSocket < IPSocket wraps ::TCPSocket def initialize(remote_host, remote_port = nil, local_host = nil, local_port = nil) if remote_host.is_a? ::TCPSocket super(remote_host) else remote_address = Addrinfo.tcp(remote_host, remote_port) local_address = Addrinfo.tcp(local_host, local_port) if local_host # We do this unusual dance to avoid leaking an "open" socket instance. socket = Socket.connect(remote_address, local_address: local_address) fd = socket.fcntl(Fcntl::F_DUPFD) Console.logger.debug(self) {"Connected to #{remote_address.inspect}: #{fd}"} socket.close super(::TCPSocket.for_fd(fd)) # The equivalent blocking operation. Unfortunately there is no trivial way to make this non-blocking. # super(::TCPSocket.new(remote_host, remote_port, local_host, local_port)) end @stream = Stream.new(self) end class << self alias open new end def close @stream.flush super end include Peer attr :stream # The way this buffering works is pretty atrocious. def_delegators :@stream, :gets, :puts def sysread(size, buffer = nil) data = @stream.read_partial(size) if buffer buffer.replace(data) end return data end end # Asynchronous TCP server wrappper. class TCPServer < TCPSocket wraps ::TCPServer, :listen def initialize(*args) if args.first.is_a? ::TCPServer super(args.first) else # We assume this operation doesn't block (for long): super(::TCPServer.new(*args)) end end def accept(timeout: nil, task: Task.current) peer, address = async_send(:accept_nonblock, timeout: timeout) wrapper = TCPSocket.new(peer) wrapper.timeout = self.timeout return wrapper, address unless block_given? begin yield wrapper, address ensure wrapper.close end end alias accept_nonblock accept alias sysaccept accept include Server end end end ruby-async-io-1.34.1/lib/async/io/threads.rb000066400000000000000000000043061435276267400205750ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2020, 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 'notification' module Async module IO class Threads def initialize(parent: nil) @parent = parent end if Async::Scheduler.supported? def async(parent: (@parent or Task.current)) parent.async do thread = ::Thread.new do yield end thread.join rescue Stop if thread&.alive? thread.raise(Stop) end begin thread.join rescue Stop # Ignore. end end end else def async(parent: (@parent or Task.current)) parent.async do |task| notification = Async::IO::Notification.new thread = ::Thread.new do yield ensure notification.signal end task.annotate "Waiting for thread to finish..." notification.wait thread.value ensure if thread&.alive? thread.raise(Stop) begin thread.join rescue Stop # Ignore. end end notification&.close end end end end end end ruby-async-io-1.34.1/lib/async/io/trap.rb000066400000000000000000000060251435276267400201110ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, 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 'notification' require 'thread' module Async module IO # A cross-reactor/process notification pipe. class Trap def initialize(name) @name = name @notifications = [] @installed = false @mutex = Mutex.new end def to_s "\#<#{self.class} #{@name}>" end # Ignore the trap within the current process. Can be invoked before forking and/or invoking `install!` to assert default behaviour. def ignore! Signal.trap(@name, :IGNORE) end def default! Signal.trap(@name, :DEFAULT) end # Install the trap into the current process. Thread safe. # @return [Boolean] whether the trap was installed or not. If the trap was already installed, returns nil. def install! return if @installed @mutex.synchronize do return if @installed Signal.trap(@name, &self.method(:trigger)) @installed = true end return true end # Wait until the signal occurs. If a block is given, execute in a loop. # @yield [Async::Task] the current task. def wait(task: Task.current, &block) task.annotate("waiting for signal #{@name}") notification = Notification.new @notifications << notification if block_given? while true notification.wait yield task end else notification.wait end ensure if notification notification.close @notifications.delete(notification) end end # Deprecated. alias trap wait # In order to avoid blocking the reactor, specify `transient: true` as an option. def async(parent: Task.current, **options, &block) parent.async(**options) do |task| self.wait(task: task, &block) end end # Signal all waiting tasks that the trap occurred. # @return [void] def trigger(signal_number = nil) @notifications.each(&:signal) end end end end ruby-async-io-1.34.1/lib/async/io/udp_socket.rb000066400000000000000000000034631435276267400213060ustar00rootroot00000000000000# frozen_string_literal: true # 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 'socket' module Async module IO # Asynchronous UDP socket wrapper. class UDPSocket < IPSocket wraps ::UDPSocket, :bind def initialize(family) if family.is_a? ::UDPSocket super(family) else super(::UDPSocket.new(family)) end end # We pass `send` through directly, but in theory it might block. Internally, it uses sendto. def_delegators :@io, :send, :connect # This function is so fucked. Why does `UDPSocket#recvfrom` return the remote address as an array, but `Socket#recfrom` return it as an `Addrinfo`? You should prefer `recvmsg`. wrap_blocking_method :recvfrom, :recvfrom_nonblock end end end ruby-async-io-1.34.1/lib/async/io/unix_endpoint.rb000066400000000000000000000051231435276267400220240ustar00rootroot00000000000000# frozen_string_literal: true # 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 'address_endpoint' module Async module IO # This class doesn't exert ownership over the specified unix socket and ensures exclusive access by using `flock` where possible. class UNIXEndpoint < AddressEndpoint def initialize(path, type, **options) # I wonder if we should implement chdir behaviour in here if path is longer than 104 characters. super(Address.unix(path, type), **options) @path = path end def to_s "\#<#{self.class} #{@path.inspect}>" end attr :path def bound? self.connect do return true end rescue Errno::ECONNREFUSED return false end def bind(&block) Socket.bind(@address, **@options, &block) rescue Errno::EADDRINUSE # If you encounter EADDRINUSE from `bind()`, you can check if the socket is actually accepting connections by attempting to `connect()` to it. If the socket is still bound by an active process, the connection will succeed. Otherwise, it should be safe to `unlink()` the path and try again. if !bound? && File.exist?(@path) File.unlink(@path) retry else raise end end end class Endpoint # @param path [String] # @param type Socket type # @param options keyword arguments passed through to {UNIXEndpoint#initialize} # # @return [UNIXEndpoint] def self.unix(path = "", type = ::Socket::SOCK_STREAM, **options) UNIXEndpoint.new(path, type, **options) end end end end ruby-async-io-1.34.1/lib/async/io/unix_socket.rb000066400000000000000000000034361435276267400215010ustar00rootroot00000000000000# frozen_string_literal: true # 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 'socket' module Async module IO class UNIXSocket < BasicSocket # `send_io`, `recv_io` and `recvfrom` may block but no non-blocking implementation available. wraps ::UNIXSocket, :path, :addr, :peeraddr, :send_io, :recv_io, :recvfrom include Peer end class UNIXServer < UNIXSocket wraps ::UNIXServer, :listen def accept peer = async_send(:accept_nonblock) wrapper = UNIXSocket.new(peer, self.reactor) return wrapper unless block_given? begin yield wrapper ensure wrapper.close end end alias sysaccept accept alias accept_nonblock accept end end end ruby-async-io-1.34.1/lib/async/io/version.rb000066400000000000000000000023021435276267400206220ustar00rootroot00000000000000# frozen_string_literal: true # 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 IO VERSION = "1.34.1" end end ruby-async-io-1.34.1/spec/000077500000000000000000000000001435276267400152535ustar00rootroot00000000000000ruby-async-io-1.34.1/spec/addrinfo.rb000066400000000000000000000004361435276267400173710ustar00rootroot00000000000000# frozen_string_literal: true # This is useful for specs, but I hesitate to monkey patch a core class in the library itself. class Addrinfo def == other self.to_s == other.to_s end def != other self.to_s != other.to_s end def <=> other self.to_s <=> other.to_s end end ruby-async-io-1.34.1/spec/async/000077500000000000000000000000001435276267400163705ustar00rootroot00000000000000ruby-async-io-1.34.1/spec/async/io/000077500000000000000000000000001435276267400167775ustar00rootroot00000000000000ruby-async-io-1.34.1/spec/async/io/buffer_spec.rb000066400000000000000000000033061435276267400216110ustar00rootroot00000000000000# frozen_string_literal: true # 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 'async/io/buffer' RSpec.describe Async::IO::Buffer do include_context Async::RSpec::Memory let!(:string) {"Hello World!".b} subject! {described_class.new} it "should be binary encoding" do expect(subject.encoding).to be Encoding::BINARY end it "should not allocate strings when concatenating" do expect do subject << string end.to limit_allocations.of(String, size: 0, count: 0) end it "can append unicode strings to binary buffer" do 2.times do subject << "Føøbar" end expect(subject).to eq "FøøbarFøøbar".b end end ruby-async-io-1.34.1/spec/async/io/c10k_spec.rb000066400000000000000000000074331435276267400211030ustar00rootroot00000000000000# frozen_string_literal: true # 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 'async/io' require 'benchmark' require 'open3' # require 'ruby-prof' RSpec.describe "c10k echo client/server", if: Process.respond_to?(:fork) do # macOS has a rediculously hard time to do this. # sudo sysctl -w net.inet.ip.portrange.first=10000 # sudo sysctl -w net.inet.ip.portrange.hifirst=10000 # Probably due to the use of select. let(:repeats) do if limit = Async::IO.file_descriptor_limit if limit > 1024*10 10_000 else [1, limit - 100].max end else 10_000 end end let(:server_address) {Async::IO::Address.tcp('0.0.0.0', 10101)} def echo_server(server_address) Async do |task| connections = [] Async::IO::Socket.bind(server_address) do |server| server.listen(Socket::SOMAXCONN) while connections.size < repeats peer, address = server.accept connections << peer end end.wait Console.logger.info("Releasing #{connections.size} connections...") while connection = connections.pop connection.write(".") connection.close end end end def echo_client(server_address, data, responses) Async do |task| begin Async::IO::Socket.connect(server_address) do |peer| responses << peer.read(1) end rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EADDRINUSE Console.logger.warn(data, $!) # If the connection was refused, it means the server probably can't accept connections any faster than it currently is, so we simply retry. retry end end end def fork_server pid = fork do # profile = RubyProf::Profile.new(merge_fibers: true) # profile.start echo_server(server_address) # ensure # result = profile.stop # printer = RubyProf::FlatPrinter.new(result) # printer.print(STDOUT) end yield ensure Process.kill(:KILL, pid) Process.wait(pid) end around(:each) do |example| duration = Benchmark.realtime do example.run end example.reporter.message "Handled #{repeats} connections in #{duration.round(2)}s: #{(repeats/duration).round(2)}req/s" end it "should wait until all clients are connected" do fork_server do # profile = RubyProf::Profile.new(merge_fibers: true) # profile.start Async do |task| responses = [] tasks = repeats.times.map do |i| # puts "Starting client #{i} on #{task}..." if (i % 1000) == 0 echo_client(server_address, "Hello World #{i}", responses) end # task.reactor.print_hierarchy tasks.each(&:wait) expect(responses.size).to be repeats end # ensure # result = profile.stop # printer = RubyProf::FlatPrinter.new(result) # printer.print(STDOUT) end end end ruby-async-io-1.34.1/spec/async/io/echo_spec.rb000066400000000000000000000044261435276267400212620ustar00rootroot00000000000000# frozen_string_literal: true # 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 'async/io' RSpec.describe "echo client/server" do include_context Async::RSpec::Reactor let(:server_address) {Async::IO::Address.tcp('0.0.0.0', 9002)} def echo_server(server_address) Async do |task| # This is a synchronous block within the current task: Async::IO::Socket.accept(server_address) do |client| # This is an asynchronous block within the current reactor: data = client.read(512) # This produces out-of-order responses. task.sleep(rand * 0.01) client.write(data) end end end def echo_client(server_address, data, responses) Async do |task| Async::IO::Socket.connect(server_address) do |peer| result = peer.write(data) peer.close_write message = peer.read(data.bytesize) responses << message end end end let(:repeats) {10} it "should echo several messages" do server = echo_server(server_address) responses = [] tasks = repeats.times.map do |i| echo_client(server_address, "Hello World #{i}", responses) end # task.reactor.print_hierarchy tasks.each(&:wait) server.stop expect(responses.size).to be repeats end end ruby-async-io-1.34.1/spec/async/io/endpoint_spec.rb000066400000000000000000000061301435276267400221560ustar00rootroot00000000000000# frozen_string_literal: true # 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 'async/io/endpoint' require 'async/io/tcp_socket' require 'async/io/socket_endpoint' require 'async/io/ssl_endpoint' RSpec.describe Async::IO::Endpoint do include_context Async::RSpec::Reactor describe Async::IO::Endpoint.ssl('0.0.0.0', 5234, hostname: "lolcathost") do it "should have hostname" do expect(subject.hostname).to be == "lolcathost" end it "shouldn't have a timeout duration" do expect(subject.timeout).to be_nil end end describe Async::IO::Endpoint.tcp('0.0.0.0', 5234, reuse_port: true, timeout: 10) do it "should be a tcp binding" do subject.bind do |server| expect(server.local_address.socktype).to be == ::Socket::SOCK_STREAM end end it "should have a timeout duration" do expect(subject.timeout).to be 10 end it "should print nicely" do expect(subject.to_s).to include('0.0.0.0', '5234') end it "has options" do expect(subject.options[:reuse_port]).to be true end it "has hostname" do expect(subject.hostname).to be == '0.0.0.0' end it "has local address" do address = Async::IO::Address.tcp('127.0.0.1', 8080) expect(subject.with(local_address: address).local_address).to be == address end let(:message) {"Hello World!"} it "can connect to bound server" do server_task = reactor.async do subject.accept do |io| expect(io.timeout).to be == 10 io.write message io.close end end io = subject.connect expect(io.timeout).to be == 10 expect(io.read(message.bytesize)).to be == message io.close server_task.stop end end describe Async::IO::Endpoint.tcp('0.0.0.0', 0) do it "should be a tcp binding" do subject.bind do |server| expect(server.local_address.ip_port).to be > 10000 end end end describe Async::IO::SocketEndpoint.new(TCPServer.new('0.0.0.0', 1234)) do it "should bind to given socket" do subject.bind do |server| expect(server).to be == subject.socket end end end end ruby-async-io-1.34.1/spec/async/io/generic_examples.rb000066400000000000000000000057541435276267400226510ustar00rootroot00000000000000# frozen_string_literal: true # 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. RSpec.shared_examples Async::IO::Generic do |ignore_methods| let(:instance_methods) {described_class.wrapped_klass.public_instance_methods(false) - (ignore_methods || [])} let(:wrapped_instance_methods) {described_class.public_instance_methods} it "should wrap a class" do expect(described_class.wrapped_klass).to_not be_nil end it "should wrap underlying instance methods" do expect(wrapped_instance_methods.sort).to include(*instance_methods.sort) end # This needs to be reviewed in more detail. # # let(:singleton_methods) {described_class.wrapped_klass.singleton_methods(false)} # let(:wrapped_singleton_methods) {described_class.singleton_methods(false)} # # it "should wrap underlying class methods" do # singleton_methods.each do |method| # expect(wrapped_singleton_methods).to include(method) # end # end end RSpec.shared_examples Async::IO do let(:data) {"Hello World!"} it "should read data" do io.write(data) expect(subject.read(data.bytesize)).to be == data end it "should read less than available data" do io.write(data) expect(subject.read(1)).to be == data[0] end it "should read all available data" do io.write(data) io.close_write expect(subject.read(data.bytesize * 2)).to be == data end it "should read all available data" do io.write(data) io.close_write expect(subject.read).to be == data end context "has the right encoding" do it "with a normal read" do io.write(data) expect(subject.read(1).encoding).to be == Encoding::BINARY end it "with a zero-length read" do expect(subject.read(0).encoding).to be == Encoding::BINARY end end context "are not frozen" do it "with a normal read" do io.write(data) expect(subject.read(1).frozen?).to be == false end it "with a zero-length read" do expect(subject.read(0).frozen?).to be == false end end end ruby-async-io-1.34.1/spec/async/io/generic_spec.rb000066400000000000000000000067101435276267400217560ustar00rootroot00000000000000# frozen_string_literal: true # 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 'async/io' require 'async/clock' require_relative 'generic_examples' RSpec.describe Async::IO::Generic do include_context Async::RSpec::Reactor CONSOLE_METHODS = [:beep, :cooked, :cooked!, :cursor, :cursor=, :echo=, :echo?,:getch, :getpass, :goto, :iflush, :ioflush, :noecho, :oflush,:pressed?, :raw, :raw!, :winsize, :winsize=] # On TruffleRuby, IO#encode_with needs to be defined for YAML.dump as a public method, allow it ignore = [:encode_with, :check_winsize_changed, :clear_screen, :console_mode, :console_mode=, :cursor_down, :cursor_left, :cursor_right, :cursor_up, :erase_line, :erase_screen, :goto_column, :scroll_backward, :scroll_forward, :wait_priority] it_should_behave_like Async::IO::Generic, [ :bytes, :chars, :codepoints, :each, :each_byte, :each_char, :each_codepoint, :each_line, :getbyte, :getc, :gets, :lineno, :lineno=, :lines, :print, :printf, :putc, :puts, :readbyte, :readchar, :readline, :readlines, :ungetbyte, :ungetc ] + CONSOLE_METHODS + ignore let(:message) {"Hello World!"} let(:pipe) {IO.pipe} let(:input) {Async::IO::Generic.new(pipe.first)} let(:output) {Async::IO::Generic.new(pipe.last)} it "should send and receive data within the same reactor" do received = nil output_task = reactor.async do received = input.read(1024) input.close end reactor.async do output.write(message) output.close end output_task.wait expect(received).to be == message end describe '#wait' do let(:wait_duration) {0.1} it "can wait for :read and :write" do reader = reactor.async do |task| duration = Async::Clock.measure do input.wait(1, :read) end expect(duration).to be >= wait_duration expect(input.read(1024)).to be == message input.close end writer = reactor.async do |task| duration = Async::Clock.measure do output.wait(1, :write) end task.sleep(wait_duration) output.write(message) output.close end [reader, writer].each(&:wait) end it "can return nil when timeout is exceeded" do reader = reactor.async do |task| duration = Async::Clock.measure do expect(input.wait(wait_duration, :read)).to be_nil end expect(duration).to be >= wait_duration input.close end [reader].each(&:wait) output.close end end end ruby-async-io-1.34.1/spec/async/io/notification_spec.rb000066400000000000000000000031111435276267400230200ustar00rootroot00000000000000# frozen_string_literal: true # 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 'async/io/notification' RSpec.describe Async::IO::Notification do include_context Async::RSpec::Reactor it "should wait for notification" do waiting_task = reactor.async do subject.wait end expect(waiting_task.status).to be :running signalling_task = reactor.async do subject.signal end signalling_task.wait waiting_task.wait expect(waiting_task.status).to be :complete subject.close end end ruby-async-io-1.34.1/spec/async/io/protocol/000077500000000000000000000000001435276267400206405ustar00rootroot00000000000000ruby-async-io-1.34.1/spec/async/io/protocol/line_spec.rb000077500000000000000000000047631435276267400231430ustar00rootroot00000000000000# frozen_string_literal: true # 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 'async/io/protocol/line' require 'async/io/socket' RSpec.describe Async::IO::Protocol::Line do include_context Async::RSpec::Reactor let(:pipe) {@pipe = Async::IO::Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM)} let(:remote) {pipe.first} subject {described_class.new(Async::IO::Stream.new(pipe.last, deferred: true), "\n")} after(:each) {defined?(@pipe) && @pipe&.each(&:close)} context "default line ending" do subject {described_class.new(nil)} it "should have default eol terminator" do expect(subject.eol).to_not be_nil end end describe '#write_lines' do it "should write line" do subject.write_lines "Hello World" subject.close expect(remote.read).to be == "Hello World\n" end end describe '#read_line' do before(:each) do remote.write "Hello World\n" remote.close end it "should read one line" do expect(subject.read_line).to be == "Hello World" end it "should be binary encoding" do expect(subject.read_line.encoding).to be == Encoding::BINARY end end describe '#read_lines' do before(:each) do remote.write "Hello\nWorld\n" remote.close end it "should read multiple lines" do expect(subject.read_lines).to be == ["Hello", "World"] end it "should be binary encoding" do expect(subject.read_lines.first.encoding).to be == Encoding::BINARY end end end ruby-async-io-1.34.1/spec/async/io/shared_endpoint/000077500000000000000000000000001435276267400221455ustar00rootroot00000000000000ruby-async-io-1.34.1/spec/async/io/shared_endpoint/server_spec.rb000066400000000000000000000047551435276267400250250ustar00rootroot00000000000000# frozen_string_literal: true # 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 'async/io/ssl_socket' require 'async/rspec/ssl' require 'async/io/host_endpoint' require 'async/io/shared_endpoint' require 'async/container' RSpec.shared_examples_for Async::IO::SharedEndpoint do |container_class| include_context Async::RSpec::SSL::VerifiedContexts include_context Async::RSpec::SSL::ValidCertificate let!(:endpoint) {Async::IO::Endpoint.tcp("127.0.0.1", 6781, reuse_port: true)} let!(:server_endpoint) {Async::IO::SSLEndpoint.new(endpoint, ssl_context: server_context)} let!(:client_endpoint) {Async::IO::SSLEndpoint.new(endpoint, ssl_context: client_context)} let!(:bound_endpoint) do Async do Async::IO::SharedEndpoint.bound(server_endpoint) end.wait end let(:container) {container_class.new} it "can use bound endpoint in container" do container.async do bound_endpoint.accept do |peer| peer.write "Hello World" peer.close end end Async do client_endpoint.connect do |peer| expect(peer.read(11)).to eq "Hello World" end end container.stop bound_endpoint.close end end RSpec.describe Async::Container::Forked, if: Process.respond_to?(:fork) do it_behaves_like Async::IO::SharedEndpoint, described_class end RSpec.describe Async::Container::Threaded, if: (RUBY_PLATFORM !~ /darwin/ && RUBY_ENGINE != "jruby") do it_behaves_like Async::IO::SharedEndpoint, described_class end ruby-async-io-1.34.1/spec/async/io/shared_endpoint_spec.rb000066400000000000000000000051751435276267400235140ustar00rootroot00000000000000# frozen_string_literal: true # 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 'async/io/host_endpoint' require 'async/io/shared_endpoint' RSpec.describe Async::IO::SharedEndpoint do include_context Async::RSpec::Reactor describe '#bound' do let(:endpoint) {Async::IO::Endpoint.udp("localhost", 5123, timeout: 10)} it "can bind to shared endpoint" do bound_endpoint = described_class.bound(endpoint) expect(bound_endpoint.wrappers).to_not be_empty wrapper = bound_endpoint.wrappers.first expect(wrapper).to be_a Async::IO::Socket expect(wrapper.timeout).to be == endpoint.timeout expect(wrapper).to_not be_close_on_exec bound_endpoint.close end it "can specify close_on_exec" do bound_endpoint = described_class.bound(endpoint, close_on_exec: true) expect(bound_endpoint.wrappers).to_not be_empty wrapper = bound_endpoint.wrappers.first expect(wrapper).to be_close_on_exec bound_endpoint.close end end describe '#connected' do let(:endpoint) {Async::IO::Endpoint.tcp("localhost", 5124, timeout: 10)} it "can connect to shared endpoint" do server_task = reactor.async do endpoint.accept do |io| io.close end end connected_endpoint = described_class.connected(endpoint) expect(connected_endpoint.wrappers).to_not be_empty wrapper = connected_endpoint.wrappers.first expect(wrapper).to be_a Async::IO::Socket expect(wrapper.timeout).to be == endpoint.timeout expect(wrapper).to_not be_close_on_exec connected_endpoint.close server_task.stop end end end ruby-async-io-1.34.1/spec/async/io/socket/000077500000000000000000000000001435276267400202675ustar00rootroot00000000000000ruby-async-io-1.34.1/spec/async/io/socket/tcp_spec.rb000066400000000000000000000055151435276267400224220ustar00rootroot00000000000000# frozen_string_literal: true # 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 'async/io/tcp_socket' require 'async/io/address' RSpec.describe Async::IO::Socket do include_context Async::RSpec::Reactor # Shared port for localhost network tests. let(:server_address) {Async::IO::Address.tcp("127.0.0.1", 6788)} let(:local_address) {Async::IO::Address.tcp("127.0.0.1", 0)} let(:data) {"The quick brown fox jumped over the lazy dog."} let!(:server_task) do # Accept a single incoming connection and then finish. Async::IO::Socket.bind(server_address) do |server| server.listen(10) server.accept do |peer, address| data = peer.read(512) peer.write(data) end end end describe 'basic tcp server' do it "should start server and send data" do Async::IO::Socket.connect(server_address) do |client| client.write(data) client.close_write expect(client.read(512)).to be == data end end end describe 'non-blocking tcp connect' do it "can specify local address" do Async::IO::Socket.connect(server_address, local_address: local_address) do |client| client.write(data) client.close_write expect(client.read(512)).to be == data end end it "should start server and send data" do Async::IO::Socket.connect(server_address) do |client| client.write(data) client.close_write expect(client.read(512)).to be == data end end it "can connect socket and read/write in a different task" do socket = Async::IO::Socket.connect(server_address) expect(socket).to_not be_nil expect(socket).to be_kind_of Async::Wrapper reactor.async do socket.write(data) socket.close_write expect(socket.read(512)).to be == data end.wait socket.close end end end ruby-async-io-1.34.1/spec/async/io/socket/udp_spec.rb000066400000000000000000000041331435276267400224170ustar00rootroot00000000000000# frozen_string_literal: true # 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 'async/io/udp_socket' RSpec.describe Async::IO::Socket do include_context Async::RSpec::Reactor # Shared port for localhost network tests. let!(:server_address) {Async::IO::Address.udp("127.0.0.1", 6778)} let(:data) {"The quick brown fox jumped over the lazy dog."} let!(:server_task) do reactor.async do Async::IO::Socket.bind(server_address) do |server| packet, address = server.recvfrom(512) server.send(packet, 0, address) end end end describe 'basic udp server' do it "should echo data back to peer" do Async::IO::Socket.connect(server_address) do |client| client.send(data) response = client.recv(512) expect(response).to be == data end end it "should use unconnected socket" do Async::IO::UDPSocket.wrap(server_address.afamily) do |client| client.send(data, 0, server_address) response, address = client.recvfrom(512) expect(response).to be == data end end end end ruby-async-io-1.34.1/spec/async/io/socket_spec.rb000066400000000000000000000077311435276267400216360ustar00rootroot00000000000000# frozen_string_literal: true # 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 'async/io/socket' require 'async/io/address' require_relative 'generic_examples' RSpec.describe Async::IO::BasicSocket do it_should_behave_like Async::IO::Generic end RSpec.describe Async::IO::Socket do include_context Async::RSpec::Reactor it_should_behave_like Async::IO::Generic describe '#connect' do let(:address) {Async::IO::Address.tcp('127.0.0.1', 12345)} it "should fail to connect if no listening server" do expect do Async::IO::Socket.connect(address) end.to raise_exception(Errno::ECONNREFUSED) end it "should close the socket when interrupted by a timeout" do wrapper = double() expect(Async::IO::Socket).to receive(:build).and_return(wrapper) expect(wrapper).to receive(:connect).and_raise Async::TimeoutError expect(wrapper).to receive(:close) expect do Async::IO::Socket.connect(address) end.to raise_exception(Async::TimeoutError) end end describe '#bind' do it "should fail to bind to port < 1024" do address = Async::IO::Address.tcp('127.0.0.1', 1) expect do Async::IO::Socket.bind(address) end.to raise_exception(Errno::EACCES) end it "can bind to port 0" do address = Async::IO::Address.tcp('127.0.0.1', 0) Async::IO::Socket.bind(address) do |socket| expect(socket.local_address.ip_port).to be > 10000 expect(Async::Task.current.annotation).to include("#{socket.local_address.ip_port}") end end end describe '#sync' do it "should set TCP_NODELAY" do address = Async::IO::Address.tcp('127.0.0.1', 0) socket = Async::IO::Socket.wrap(::Socket::AF_INET, ::Socket::SOCK_STREAM, ::Socket::IPPROTO_TCP) socket.sync = true expect(socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY).bool).to be true socket.close end end describe '#timeout' do subject{described_class.pair(:UNIX, :STREAM, 0)} it "should timeout while waiting to receive data" do s1, s2 = *subject s2.timeout = 1 expect{s2.recv(32)}.to raise_exception(Async::TimeoutError, "execution expired") s1.close s2.close end end describe '.pair' do subject{described_class.pair(:UNIX, :STREAM, 0)} it "should be able to send and recv" do s1, s2 = *subject s1.send "Hello World", 0 s1.close expect(s2.recv(32)).to be == "Hello World" s2.close end it "should be connected" do s1, s2 = *subject expect(s1).to be_connected s1.close expect(s2).to_not be_connected s2.close end end context '.pipe' do let(:sockets) do @sockets = described_class.pair(Socket::AF_UNIX, Socket::SOCK_STREAM) end after do @sockets&.each(&:close) end let(:io) {sockets.first} subject {sockets.last} it_should_behave_like Async::IO end end RSpec.describe Async::IO::IPSocket do it_should_behave_like Async::IO::Generic, [:inspect] end ruby-async-io-1.34.1/spec/async/io/ssl_server_spec.rb000066400000000000000000000107631435276267400225340ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2018, 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/io/ssl_socket' require 'async/io/ssl_endpoint' require 'async/rspec/ssl' require 'async/queue' require_relative 'generic_examples' RSpec.describe Async::IO::SSLServer do include_context Async::RSpec::Reactor context 'single host' do include_context Async::RSpec::SSL::VerifiedContexts include_context Async::RSpec::SSL::ValidCertificate let(:endpoint) {Async::IO::Endpoint.tcp("127.0.0.1", 6780, reuse_port: true)} let(:server_endpoint) {Async::IO::SSLEndpoint.new(endpoint, ssl_context: server_context)} let(:client_endpoint) {Async::IO::SSLEndpoint.new(endpoint, ssl_context: client_context)} let(:data) {"What one programmer can do in one month, two programmers can do in two months."} it "can see through to address" do expect(server_endpoint.address).to be == endpoint.address end it 'can accept_each connections' do ready = Async::Queue.new # Accept a single incoming connection and then finish. server_task = reactor.async do |task| server_endpoint.bind do |server| server.listen(10) ready.enqueue(true) server.accept_each do |peer, address| data = peer.read(512) peer.write(data) end end end reactor.async do |task| ready.dequeue client_endpoint.connect do |client| client.write(data) client.close_write expect(client.read(512)).to be == data end server_task.stop end end end context 'multiple hosts' do let(:hosts) {['test.com', 'example.com']} include_context Async::RSpec::SSL::HostCertificates let(:endpoint) {Async::IO::Endpoint.tcp("127.0.0.1", 6782, reuse_port: true)} let(:server_endpoint) {Async::IO::SSLEndpoint.new(endpoint, ssl_context: server_context)} let(:valid_client_endpoint) {Async::IO::SSLEndpoint.new(endpoint, hostname: 'example.com', ssl_context: client_context)} let(:invalid_client_endpoint) {Async::IO::SSLEndpoint.new(endpoint, hostname: 'fleeb.com', ssl_context: client_context)} let(:data) {"What one programmer can do in one month, two programmers can do in two months."} before do certificates end it 'can select correct host' do ready = Async::Queue.new # Accept a single incoming connection and then finish. server_task = reactor.async do |task| server_endpoint.bind do |server| server.listen(10) ready.enqueue(true) server.accept_each do |peer, address| expect(peer.hostname).to be == 'example.com' data = peer.read(512) peer.write(data) end end end reactor.async do ready.dequeue valid_client_endpoint.connect do |client| client.write(data) client.close_write expect(client.read(512)).to be == data end server_task.stop end end it 'it fails with invalid host' do ready = Async::Queue.new # Accept a single incoming connection and then finish. server_task = reactor.async do |task| server_endpoint.bind do |server| server.listen(10) ready.enqueue(true) server.accept_each do |peer, address| peer.close end end end reactor.async do ready.dequeue expect do invalid_client_endpoint.connect do |client| end end.to raise_exception(OpenSSL::SSL::SSLError, /handshake failure/) server_task.stop end.wait end end end ruby-async-io-1.34.1/spec/async/io/ssl_socket_spec.rb000066400000000000000000000060711435276267400225130ustar00rootroot00000000000000# frozen_string_literal: true # 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 'async/io/ssl_endpoint' require 'async/queue' require 'async/rspec/ssl' require_relative 'generic_examples' RSpec.describe Async::IO::SSLSocket do it_should_behave_like Async::IO::Generic describe "#connect" do include_context Async::RSpec::Reactor include_context Async::RSpec::SSL::VerifiedContexts # Shared port for localhost network tests. let!(:endpoint) {Async::IO::Endpoint.tcp("127.0.0.1", 6779, reuse_port: true, timeout: 10)} let!(:server_endpoint) {Async::IO::SSLEndpoint.new(endpoint, ssl_context: server_context, timeout: 20)} let!(:client_endpoint) {Async::IO::SSLEndpoint.new(endpoint, ssl_context: client_context, timeout: 20)} let(:data) {"The quick brown fox jumped over the lazy dog."} let!(:server_task) do ready = Async::Queue.new # Accept a single incoming connection and then finish. reactor.async do |task| server_endpoint.bind do |server| ready.enqueue(server) server.listen(10) begin server.accept do |peer, address| expect(peer.timeout).to be == 10 data = peer.read(512) peer.write(data) end rescue OpenSSL::SSL::SSLError # ignore. end end end ready.dequeue end context "with a trusted certificate" do include_context Async::RSpec::SSL::ValidCertificate it "should start server and send data" do reactor.async do client_endpoint.connect do |client| # expect(client).to be_connected expect(client.timeout).to be == 10 client.write(data) client.close_write expect(client.read(512)).to be == data end end end end context "with an untrusted certificate" do include_context Async::RSpec::SSL::InvalidCertificate it "should fail to connect" do reactor.async do expect do client_endpoint.connect end.to raise_exception(OpenSSL::SSL::SSLError) end.wait end end end end ruby-async-io-1.34.1/spec/async/io/standard_spec.rb000066400000000000000000000032011435276267400221320ustar00rootroot00000000000000# frozen_string_literal: true # 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 'async/io/standard' RSpec.describe Async::IO::STDIN do include_context Async::RSpec::Reactor it "should be able to read" do expect(subject.read(0)).to be == "" end end RSpec.describe Async::IO::STDOUT do include_context Async::RSpec::Reactor it "should be able to write" do expect(subject.write("")).to be == 0 end end RSpec.describe Async::IO::STDERR do include_context Async::RSpec::Reactor it "should be able to write" do expect(subject.write("")).to be == 0 end endruby-async-io-1.34.1/spec/async/io/stream_context.rb000066400000000000000000000025261435276267400223700ustar00rootroot00000000000000# frozen_string_literal: true # 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/rspec/buffer' require 'async/io/stream' RSpec.shared_context Async::IO::Stream do include_context Async::RSpec::Buffer subject {described_class.new(buffer)} let(:io) {subject.io} end ruby-async-io-1.34.1/spec/async/io/stream_spec.rb000066400000000000000000000222201435276267400216270ustar00rootroot00000000000000# frozen_string_literal: true # 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 'async/io' require 'async/io/socket' require 'async/clock' require_relative 'generic_examples' require_relative 'stream_context' RSpec.describe Async::IO::Stream do # This constant is part of the public interface, but was renamed to `Async::IO::BLOCK_SIZE`. describe "::BLOCK_SIZE" do it "should exist and be reasonable" do expect(Async::IO::Stream::BLOCK_SIZE).to be_between(1024, 1024*128) end end context "socket I/O" do let(:sockets) do @sockets = Async::IO::Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM) end after do @sockets&.each(&:close) end let(:io) {sockets.first} subject {described_class.new(sockets.last)} it_should_behave_like Async::IO describe '#drain_write_buffer' do include_context Async::RSpec::Reactor let(:output) {described_class.new(sockets.last)} subject {described_class.new(sockets.first)} let(:buffer_size) {1024*6} it "can interleave calls to flush" do tasks = 2.times.map do |i| reactor.async do buffer = i.to_s * buffer_size 128.times do output.write(buffer) output.flush end end end reactor.async do tasks.each(&:wait) output.close end Async::Task.current.sleep(1) while buffer = subject.read(buffer_size) expect(buffer).to be == (buffer[0] * buffer_size) end end it "handles write failures" do subject.close task = reactor.async do output.write("Hello World") output.flush end expect do task.wait end.to raise_error(Errno::EPIPE) write_buffer = output.instance_variable_get(:@write_buffer) drain_buffer = output.instance_variable_get(:@drain_buffer) expect(write_buffer).to be_empty expect(drain_buffer).to be_empty end end describe '#close_read' do subject {described_class.new(sockets.last)} it "can close the reading end of the stream" do expect(subject.io).to receive(:close_read).and_call_original subject.close_read # Ruby <= 2.4 raises an exception even with exception: false # expect(stream.read).to be_nil end it "can close the writing end of the stream" do expect(subject.io).to receive(:close_write).and_call_original subject.write("Oh yes!") subject.close_write expect do subject.write("Oh no!") subject.flush end.to raise_error(IOError, /not opened for writing/) end end describe '#read_exactly' do it "can read several bytes" do io.write("hello\nworld\n") expect(subject.read_exactly(4)).to be == 'hell' end it "can raise exception if io is eof" do io.close expect do subject.read_exactly(4) end.to raise_error(EOFError) end end end context "performance (BLOCK_SIZE: #{Async::IO::BLOCK_SIZE} MAXIMUM_READ_SIZE: #{Async::IO::MAXIMUM_READ_SIZE})" do include_context Async::RSpec::Reactor let!(:stream) {described_class.open("/dev/zero")} after {stream.close} it "can read data quickly" do |example| data = nil duration = Async::Clock.measure do data = stream.read(1024**3) end size = data.bytesize / 1024**2 rate = size / duration example.reporter.message "Read #{size.round(2)}MB of data at #{rate.round(2)}MB/s." expect(rate).to be > 128 end end context "buffered I/O" do include_context Async::IO::Stream include_context Async::RSpec::Memory include_context Async::RSpec::Reactor describe '#read' do it "can read zero length" do result = subject.read(0) expect(result).to be == "" expect(result.encoding).to be == Encoding::BINARY end it "should read everything" do io.write "Hello World" io.seek(0) expect(subject.io).to receive(:read_nonblock).and_call_original.twice expect(subject.read).to be == "Hello World" expect(subject).to be_eof end it "should read only the amount requested" do io.write "Hello World" io.seek(0) expect(subject.io).to receive(:read_nonblock).and_call_original.once expect(subject.read_partial(4)).to be == "Hell" expect(subject).to_not be_eof expect(subject.read_partial(20)).to be == "o World" expect(subject).to be_eof end context "with large content", if: !Async::IO.buffer? do it "allocates expected amount of bytes" do io.write("." * 16*1024) io.seek(0) buffer = nil expect do # The read buffer is already allocated, and it will be resized to fit the incoming data. It will be swapped with an empty buffer. buffer = subject.read(16*1024) end.to limit_allocations.of(String, count: 1, size: 0) expect(buffer.size).to be == 16*1024 end end end describe '#read_until' do it "can read a line" do io.write("hello\nworld\n") io.seek(0) expect(subject.read_until("\n")).to be == 'hello' expect(subject.read_until("\n")).to be == 'world' expect(subject.read_until("\n")).to be_nil end context "with 1-byte block size" do subject! {Async::IO::Stream.new(buffer, block_size: 1)} it "can read a line with a multi-byte pattern" do io.write("hello\r\nworld\r\n") io.seek(0) expect(subject.read_until("\r\n")).to be == 'hello' expect(subject.read_until("\r\n")).to be == 'world' expect(subject.read_until("\r\n")).to be_nil end end context "with large content", if: !Async::IO.buffer? do it "allocates expected amount of bytes" do subject expect do subject.read_until("b") end.to limit_allocations.of(String, size: 0, count: 1) end end end describe '#flush' do it "should not call write if write buffer is empty" do expect(subject.io).to_not receive(:write) subject.flush end it "should flush underlying data when it exceeds block size" do expect(subject.io).to receive(:write).and_call_original.once subject.block_size.times do subject.write("!") end end end describe '#read_partial' do before(:each) do string = "Hello World!" io.write(string * (1 + (Async::IO::BLOCK_SIZE / string.bytesize))) io.seek(0) end it "should avoid calling read" do expect(subject.io).to receive(:read_nonblock).and_call_original.once expect(subject.read_partial(12)).to be == "Hello World!" end context "with large content", if: !Async::IO.buffer? do it "allocates only the amount required" do expect do subject.read(4*1024) end.to limit_allocations.of(String, count: 2, size: 4*1024+1) end it "allocates exact number of bytes being read" do expect do subject.read_partial(subject.block_size * 2) end.to limit_allocations.of(String, count: 1, size: 0) end it "allocates expected amount of bytes" do buffer = nil expect do buffer = subject.read_partial end.to limit_allocations.of(String, count: 1) expect(buffer.size).to be == subject.block_size end end context "has the right encoding" do it "with a normal partial_read" do expect(subject.read_partial(1).encoding).to be == Encoding::BINARY end it "with a zero-length partial_read" do expect(subject.read_partial(0).encoding).to be == Encoding::BINARY end end end describe '#write' do it "should read one line" do expect(subject.io).to receive(:write).and_call_original.once subject.write "Hello World\n" subject.flush io.seek(0) expect(subject.read).to be == "Hello World\n" end end describe '#eof' do it "should terminate subject" do expect do subject.eof! end.to raise_exception(EOFError) expect(subject).to be_eof end end describe '#close' do it 'can be closed even if underlying io is closed' do io.close expect(subject.io).to be_closed # Put some data in the write buffer subject.write "." expect do subject.close end.to_not raise_exception end end end end ruby-async-io-1.34.1/spec/async/io/tcp_socket_spec.rb000066400000000000000000000047201435276267400224770ustar00rootroot00000000000000# frozen_string_literal: true # 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, reactor 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/io/tcp_socket' require_relative 'generic_examples' RSpec.describe Async::IO::TCPSocket do include_context Async::RSpec::Reactor it_should_behave_like Async::IO::Generic # Shared port for localhost network tests. let(:server_address) {Async::IO::Address.tcp("localhost", 6788)} let(:data) {"The quick brown fox jumped over the lazy dog."} describe Async::IO::TCPServer do it_should_behave_like Async::IO::Generic end describe Async::IO::TCPServer do let!(:server_task) do reactor.async do |task| server = Async::IO::TCPServer.new("localhost", 6788) peer, address = server.accept data = peer.gets peer.puts(data) peer.flush peer.close server.close end end let(:client) {Async::IO::TCPSocket.new("localhost", 6788)} it "can read into output buffer" do client.puts("Hello World") client.flush buffer = String.new # 20 is bigger than echo response... data = client.read(20, buffer) expect(buffer).to_not be_empty expect(buffer).to be == data client.close server_task.wait end it "should start server and send data" do # Accept a single incoming connection and then finish. client.puts(data) client.flush expect(client.gets).to be == data client.close server_task.wait end end end ruby-async-io-1.34.1/spec/async/io/threads_spec.rb000066400000000000000000000035551435276267400220000ustar00rootroot00000000000000# frozen_string_literal: true # Copyright, 2020, 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/io/threads' RSpec.describe Async::IO::Threads do include_context Async::RSpec::Reactor describe '#async' do it "can schedule work on a different thread" do thread = subject.async do Thread.current end.wait expect(thread).to be_kind_of Thread expect(thread).to_not be Thread.current end it "can kill thread when stopping task" do sleeping = Async::IO::Notification.new thread = nil task = subject.async do thread = Thread.current sleeping.signal sleep end sleeping.wait task.stop 10.times do pp thread sleep(0.1) break unless thread.status end expect(thread.status).to be_nil ensure sleeping.close end end end ruby-async-io-1.34.1/spec/async/io/trap_spec.rb000066400000000000000000000033531435276267400213100ustar00rootroot00000000000000# frozen_string_literal: true # 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 'async/io/trap' RSpec.describe Async::IO::Trap do include_context Async::RSpec::Reactor subject {described_class.new(:USR2)} it "can ignore signal" do subject.ignore! Process.kill(:USR2, Process.pid) end it "should wait for signal" do trapped = false waiting_task = reactor.async do subject.wait do trapped = true break end end subject.trigger waiting_task.wait expect(trapped).to be_truthy end it "should create transient task" do task = subject.async(transient: true) do # Trapped. end expect(task).to be_transient end end ruby-async-io-1.34.1/spec/async/io/udp_socket_spec.rb000066400000000000000000000036161435276267400225040ustar00rootroot00000000000000# frozen_string_literal: true # 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 'async/io/udp_socket' require_relative 'generic_examples' RSpec.describe Async::IO::UDPSocket do include_context Async::RSpec::Reactor it_should_behave_like Async::IO::Generic let(:data) {"The quick brown fox jumped over the lazy dog."} it "should echo data back to peer" do reactor.async do server = Async::IO::UDPSocket.new(Socket::AF_INET) server.bind("127.0.0.1", 6778) packet, address = server.recvfrom(512) server.send(packet, 0, address[3], address[1]) server.close end reactor.async do client = Async::IO::UDPSocket.new(Socket::AF_INET) client.connect("127.0.0.1", 6778) client.send(data, 0) response = client.recv(512) client.close expect(response).to be == data end.wait end end ruby-async-io-1.34.1/spec/async/io/unix_endpoint_spec.rb000066400000000000000000000053011435276267400232200ustar00rootroot00000000000000# frozen_string_literal: true # 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 'async/io/unix_endpoint' require 'async/io/stream' require 'fileutils' RSpec.describe Async::IO::UNIXEndpoint do include_context Async::RSpec::Reactor let(:data) {"The quick brown fox jumped over the lazy dog."} let(:path) {File.join(__dir__, "unix-socket")} subject {described_class.unix(path)} before(:each) do FileUtils.rm_f path end after do FileUtils.rm_f path end it "should echo data back to peer" do server_task = reactor.async do subject.accept do |peer| peer.send(peer.recv(512)) end end subject.connect do |client| client.send(data) response = client.recv(512) expect(response).to be == data end server_task.stop end it "should fails to bind if there is an existing binding" do condition = Async::Condition.new reactor.async do condition.wait expect do subject.bind end.to raise_error(Errno::EADDRINUSE) end server_task = reactor.async do subject.bind do |server| server.listen(1) condition.signal end end server_task.stop end context "using buffered stream" do it "can use stream to read and write data" do server_task = reactor.async do |task| subject.accept do |peer| stream = Async::IO::Stream.new(peer) stream.write(stream.read) stream.close end end reactor.async do subject.connect do |client| stream = Async::IO::Stream.new(client) stream.write(data) stream.close_write expect(stream.read).to be == data end end.wait server_task.stop end end end ruby-async-io-1.34.1/spec/async/io/unix_socket_spec.rb000066400000000000000000000037041435276267400226750ustar00rootroot00000000000000# frozen_string_literal: true # 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 'async/io/unix_socket' require_relative 'generic_examples' RSpec.describe Async::IO::UNIXSocket do include_context Async::RSpec::Reactor it_should_behave_like Async::IO::Generic let(:path) {File.join(__dir__, "unix-socket")} let(:data) {"The quick brown fox jumped over the lazy dog."} before(:each) do FileUtils.rm_f path end after do FileUtils.rm_f path end it "should echo data back to peer" do reactor.async do Async::IO::UNIXServer.wrap(path) do |server| server.accept do |peer| peer.send(peer.recv(512)) end end end Async::IO::UNIXSocket.wrap(path) do |client| client.send(data) response = client.recv(512) expect(response).to be == data end end end RSpec.describe Async::IO::UNIXServer do it_should_behave_like Async::IO::Generic end ruby-async-io-1.34.1/spec/spec_helper.rb000066400000000000000000000005461435276267400200760ustar00rootroot00000000000000# frozen_string_literal: true require 'covered/rspec' require "async/rspec" require_relative 'addrinfo' RSpec.configure do |config| # 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 Signal.trap(:INT) { raise Interrupt }