async-io 1.21.0 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/async/io/socket.rb +15 -10
- data/lib/async/io/stream.rb +11 -1
- data/lib/async/io/version.rb +1 -1
- data/spec/async/io/c10k_spec.rb +34 -34
- data/spec/async/io/socket/tcp_spec.rb +12 -1
- data/spec/async/io/stream_spec.rb +23 -0
- metadata +2 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 368fe5eb02962730d8f47e3ab180913aa922085eb554cc2e430b8c15113e8f33
         | 
| 4 | 
            +
              data.tar.gz: '09c5660537d3a7ce792bdf89674e79d0e40391346788c27808d14edbde1dfb13'
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 27929ee0118185c0dadb8216ce88f5d4296633481215d0c3865396b56e5c9d73f63bd8b6d6f01db5e641a3eb6e78209746b36bf0e91b17cb9e7d8550819d8a37
         | 
| 7 | 
            +
              data.tar.gz: 35c33f0d712a94d7468c69aaa178139e756a3fcb1e56daf78671a2f347a790b141172ea6a3d993d4640da017d6f12624e1f10124225d0c4a877d2b39351b0b6f
         | 
    
        data/lib/async/io/socket.rb
    CHANGED
    
    | @@ -26,13 +26,15 @@ require_relative 'generic' | |
| 26 26 | 
             
            module Async
         | 
| 27 27 | 
             
            	module IO
         | 
| 28 28 | 
             
            		module Peer
         | 
| 29 | 
            +
            			include ::Socket::Constants
         | 
| 30 | 
            +
            			
         | 
| 29 31 | 
             
            			# Is it likely that the socket is still connected?
         | 
| 30 32 | 
             
            			# May return false positive, but won't return false negative.
         | 
| 31 33 | 
             
            			def connected?
         | 
| 32 34 | 
             
            				return false if @io.closed?
         | 
| 33 35 |  | 
| 34 36 | 
             
            				# If we can wait for the socket to become readable, we know that the socket may still be open.
         | 
| 35 | 
            -
            				result = to_io.recv_nonblock(1,  | 
| 37 | 
            +
            				result = to_io.recv_nonblock(1, MSG_PEEK, exception: false)
         | 
| 36 38 |  | 
| 37 39 | 
             
            				# Either there was some data available, or we can wait to see if there is data avaialble.
         | 
| 38 40 | 
             
            				return !result.empty? || result == :wait_readable
         | 
| @@ -47,8 +49,8 @@ module Async | |
| 47 49 | 
             
            				super
         | 
| 48 50 |  | 
| 49 51 | 
             
            				case self.protocol
         | 
| 50 | 
            -
            				when 0,  | 
| 51 | 
            -
            					self.setsockopt( | 
| 52 | 
            +
            				when 0, IPPROTO_TCP
         | 
| 53 | 
            +
            					self.setsockopt(IPPROTO_TCP, TCP_NODELAY, value ? 1 : 0)
         | 
| 52 54 | 
             
            				else
         | 
| 53 55 | 
             
            					Async.logger.warn(self) {"Unsure how to sync=#{value} for #{self.protocol}!"}
         | 
| 54 56 | 
             
            				end
         | 
| @@ -61,8 +63,8 @@ module Async | |
| 61 63 |  | 
| 62 64 | 
             
            			def sync
         | 
| 63 65 | 
             
            				case self.protocol
         | 
| 64 | 
            -
            				when  | 
| 65 | 
            -
            					self.getsockopt( | 
| 66 | 
            +
            				when IPPROTO_TCP
         | 
| 67 | 
            +
            					self.getsockopt(IPPROTO_TCP, TCP_NODELAY).bool
         | 
| 66 68 | 
             
            				else
         | 
| 67 69 | 
             
            					true
         | 
| 68 70 | 
             
            				end && super
         | 
| @@ -106,8 +108,6 @@ module Async | |
| 106 108 |  | 
| 107 109 | 
             
            			wrap_blocking_method :recvfrom, :recvfrom_nonblock
         | 
| 108 110 |  | 
| 109 | 
            -
            			include ::Socket::Constants
         | 
| 110 | 
            -
            			
         | 
| 111 111 | 
             
            			# @raise Errno::EAGAIN the connection failed due to the remote end being overloaded.
         | 
| 112 112 | 
             
            			def connect(*args)
         | 
| 113 113 | 
             
            				begin
         | 
| @@ -149,15 +149,15 @@ module Async | |
| 149 149 | 
             
            				socket = wrapped_klass.new(*args)
         | 
| 150 150 |  | 
| 151 151 | 
             
            				if reuse_address
         | 
| 152 | 
            -
            					socket.setsockopt( | 
| 152 | 
            +
            					socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
         | 
| 153 153 | 
             
            				end
         | 
| 154 154 |  | 
| 155 155 | 
             
            				if reuse_port
         | 
| 156 | 
            -
            					socket.setsockopt( | 
| 156 | 
            +
            					socket.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1)
         | 
| 157 157 | 
             
            				end
         | 
| 158 158 |  | 
| 159 159 | 
             
            				if linger
         | 
| 160 | 
            -
            					socket.setsockopt( | 
| 160 | 
            +
            					socket.setsockopt(SOL_SOCKET, SO_LINGER, linger)
         | 
| 161 161 | 
             
            				end
         | 
| 162 162 |  | 
| 163 163 | 
             
            				yield socket
         | 
| @@ -184,6 +184,11 @@ module Async | |
| 184 184 |  | 
| 185 185 | 
             
            				wrapper = build(remote_address.afamily, remote_address.socktype, remote_address.protocol, **options) do |socket|
         | 
| 186 186 | 
             
            					if local_address
         | 
| 187 | 
            +
            						if defined?(IP_BIND_ADDRESS_NO_PORT)
         | 
| 188 | 
            +
            							# 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.
         | 
| 189 | 
            +
            							socket.setsockopt(SOL_IP, IP_BIND_ADDRESS_NO_PORT, 1)
         | 
| 190 | 
            +
            						end
         | 
| 191 | 
            +
            						
         | 
| 187 192 | 
             
            						socket.bind(local_address.to_sockaddr)
         | 
| 188 193 | 
             
            					end
         | 
| 189 194 | 
             
            				end
         | 
    
        data/lib/async/io/stream.rb
    CHANGED
    
    | @@ -163,7 +163,17 @@ module Async | |
| 163 163 | 
             
            				@io.closed?
         | 
| 164 164 | 
             
            			end
         | 
| 165 165 |  | 
| 166 | 
            -
            			 | 
| 166 | 
            +
            			def close_read
         | 
| 167 | 
            +
            				@io.close_read
         | 
| 168 | 
            +
            			end
         | 
| 169 | 
            +
            			
         | 
| 170 | 
            +
            			def close_write
         | 
| 171 | 
            +
            				flush
         | 
| 172 | 
            +
            			ensure
         | 
| 173 | 
            +
            				@io.close_write
         | 
| 174 | 
            +
            			end
         | 
| 175 | 
            +
            			
         | 
| 176 | 
            +
            			# Best effort to flush any unwritten data, and then close the underling IO.
         | 
| 167 177 | 
             
            			def close
         | 
| 168 178 | 
             
            				return if @io.closed?
         | 
| 169 179 |  | 
    
        data/lib/async/io/version.rb
    CHANGED
    
    
    
        data/spec/async/io/c10k_spec.rb
    CHANGED
    
    | @@ -21,59 +21,44 @@ | |
| 21 21 | 
             
            require 'async/io'
         | 
| 22 22 | 
             
            require 'benchmark'
         | 
| 23 23 |  | 
| 24 | 
            +
            require 'ruby-prof'
         | 
| 25 | 
            +
             | 
| 24 26 | 
             
            RSpec.describe "echo client/server" do
         | 
| 25 27 | 
             
            	# macOS has a rediculously hard time to do this.
         | 
| 26 28 | 
             
            	# sudo sysctl -w net.inet.ip.portrange.first=10000
         | 
| 27 29 | 
             
            	# sudo sysctl -w net.inet.ip.portrange.hifirst=10000
         | 
| 28 30 | 
             
            	# Probably due to the use of select.
         | 
| 29 31 |  | 
| 30 | 
            -
            	let(:repeats) {RUBY_PLATFORM =~ /darwin/ ?  | 
| 31 | 
            -
            	let(:server_address) {Async::IO::Address.tcp('0.0.0.0',  | 
| 32 | 
            +
            	let(:repeats) {RUBY_PLATFORM =~ /darwin/ ? 1000 : 10000}
         | 
| 33 | 
            +
            	let(:server_address) {Async::IO::Address.tcp('0.0.0.0', 10101)}
         | 
| 32 34 |  | 
| 33 35 | 
             
            	def echo_server(server_address)
         | 
| 34 36 | 
             
            		Async do |task|
         | 
| 35 | 
            -
            			 | 
| 37 | 
            +
            			connections = []
         | 
| 36 38 |  | 
| 37 | 
            -
            			 | 
| 38 | 
            -
            				 | 
| 39 | 
            +
            			Async::IO::Socket.bind(server_address) do |server|
         | 
| 40 | 
            +
            				server.listen(Socket::SOMAXCONN)
         | 
| 39 41 |  | 
| 40 | 
            -
            				while  | 
| 41 | 
            -
            					 | 
| 42 | 
            -
             | 
| 43 | 
            -
            						last_count = connection_count
         | 
| 44 | 
            -
            					end
         | 
| 45 | 
            -
            					
         | 
| 46 | 
            -
            					task.sleep(1.0)
         | 
| 42 | 
            +
            				while connections.count < repeats
         | 
| 43 | 
            +
            					peer, address = server.accept
         | 
| 44 | 
            +
            					connections << peer
         | 
| 47 45 | 
             
            				end
         | 
| 48 | 
            -
             | 
| 49 | 
            -
            				puts "Releasing all connections..."
         | 
| 50 | 
            -
            			end
         | 
| 46 | 
            +
            			end.wait
         | 
| 51 47 |  | 
| 52 | 
            -
            			 | 
| 53 | 
            -
            			 | 
| 54 | 
            -
             | 
| 55 | 
            -
            				
         | 
| 56 | 
            -
            				 | 
| 57 | 
            -
            				connections_complete.wait
         | 
| 58 | 
            -
            				
         | 
| 59 | 
            -
            				# This is an asynchronous block within the current reactor:
         | 
| 60 | 
            -
            				data = client.read(512)
         | 
| 61 | 
            -
            				client.write(data)
         | 
| 48 | 
            +
            			puts "Releasing #{connections.count} connections..."
         | 
| 49 | 
            +
            			
         | 
| 50 | 
            +
            			while connection = connections.pop
         | 
| 51 | 
            +
            				connection.write(".")
         | 
| 52 | 
            +
            				connection.close
         | 
| 62 53 | 
             
            			end
         | 
| 63 54 | 
             
            		end
         | 
| 64 | 
            -
            	ensure
         | 
| 65 | 
            -
            		puts "echo_server: #{$!.inspect}"
         | 
| 66 55 | 
             
            	end
         | 
| 67 56 |  | 
| 68 57 | 
             
            	def echo_client(server_address, data, responses)
         | 
| 69 58 | 
             
            		Async do |task|
         | 
| 70 59 | 
             
            			begin
         | 
| 71 60 | 
             
            				Async::IO::Socket.connect(server_address) do |peer|
         | 
| 72 | 
            -
            					 | 
| 73 | 
            -
            					
         | 
| 74 | 
            -
            					message = peer.read(512)
         | 
| 75 | 
            -
            					
         | 
| 76 | 
            -
            					responses << message
         | 
| 61 | 
            +
            					responses << peer.read(1)
         | 
| 77 62 | 
             
            				end
         | 
| 78 63 | 
             
            			rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EADDRINUSE
         | 
| 79 64 | 
             
            				puts "#{data}: #{$!}..."
         | 
| @@ -85,9 +70,16 @@ RSpec.describe "echo client/server" do | |
| 85 70 |  | 
| 86 71 | 
             
            	def fork_server
         | 
| 87 72 | 
             
            		pid = fork do
         | 
| 73 | 
            +
            			# profile = RubyProf::Profile.new(merge_fibers: true)
         | 
| 74 | 
            +
            			# profile.start
         | 
| 75 | 
            +
            			
         | 
| 88 76 | 
             
            			echo_server(server_address)
         | 
| 77 | 
            +
            		# ensure
         | 
| 78 | 
            +
            		# 	result = profile.stop
         | 
| 79 | 
            +
            		# 	printer = RubyProf::FlatPrinter.new(result)
         | 
| 80 | 
            +
            		# 	printer.print(STDOUT)
         | 
| 89 81 | 
             
            		end
         | 
| 90 | 
            -
             | 
| 82 | 
            +
             | 
| 91 83 | 
             
            		yield
         | 
| 92 84 | 
             
            	ensure
         | 
| 93 85 | 
             
            		Process.kill(:KILL, pid)
         | 
| @@ -102,8 +94,11 @@ RSpec.describe "echo client/server" do | |
| 102 94 | 
             
            		example.reporter.message "Handled #{repeats} connections in #{duration.round(2)}s: #{(repeats/duration).round(2)}req/s"
         | 
| 103 95 | 
             
            	end
         | 
| 104 96 |  | 
| 105 | 
            -
            	it "should  | 
| 97 | 
            +
            	it "should wait until all clients are connected" do
         | 
| 106 98 | 
             
            		fork_server do
         | 
| 99 | 
            +
            			# profile = RubyProf::Profile.new(merge_fibers: true)
         | 
| 100 | 
            +
            			# profile.start
         | 
| 101 | 
            +
            			
         | 
| 107 102 | 
             
            			Async do |task|
         | 
| 108 103 | 
             
            				responses = []
         | 
| 109 104 |  | 
| @@ -119,6 +114,11 @@ RSpec.describe "echo client/server" do | |
| 119 114 |  | 
| 120 115 | 
             
            				expect(responses.count).to be repeats
         | 
| 121 116 | 
             
            			end
         | 
| 117 | 
            +
            			
         | 
| 118 | 
            +
            		# ensure
         | 
| 119 | 
            +
            		# 	result = profile.stop
         | 
| 120 | 
            +
            		# 	printer = RubyProf::FlatPrinter.new(result)
         | 
| 121 | 
            +
            		# 	printer.print(STDOUT)
         | 
| 122 122 | 
             
            		end
         | 
| 123 123 | 
             
            	end
         | 
| 124 124 | 
             
            end
         | 
| @@ -24,7 +24,8 @@ RSpec.describe Async::IO::Socket do | |
| 24 24 | 
             
            	include_context Async::RSpec::Reactor
         | 
| 25 25 |  | 
| 26 26 | 
             
            	# Shared port for localhost network tests.
         | 
| 27 | 
            -
            	let(:server_address) {Async::IO::Address.tcp(" | 
| 27 | 
            +
            	let(:server_address) {Async::IO::Address.tcp("127.0.0.1", 6788)}
         | 
| 28 | 
            +
            	let(:local_address) {Async::IO::Address.tcp("127.0.0.1", 0)}
         | 
| 28 29 | 
             
            	let(:data) {"The quick brown fox jumped over the lazy dog."}
         | 
| 29 30 |  | 
| 30 31 | 
             
            	let!(:server_task) do
         | 
| @@ -54,6 +55,16 @@ RSpec.describe Async::IO::Socket do | |
| 54 55 | 
             
            	end
         | 
| 55 56 |  | 
| 56 57 | 
             
            	describe 'non-blocking tcp connect' do
         | 
| 58 | 
            +
            		it "can specify local address" do
         | 
| 59 | 
            +
            			reactor.async do |task|
         | 
| 60 | 
            +
            				Async::IO::Socket.connect(server_address, local_address: local_address) do |client|
         | 
| 61 | 
            +
            					client.write(data)
         | 
| 62 | 
            +
            					
         | 
| 63 | 
            +
            					expect(client.read(512)).to be == data
         | 
| 64 | 
            +
            				end
         | 
| 65 | 
            +
            			end
         | 
| 66 | 
            +
            		end
         | 
| 67 | 
            +
            		
         | 
| 57 68 | 
             
            		it "should start server and send data" do
         | 
| 58 69 | 
             
            			reactor.async do |task|
         | 
| 59 70 | 
             
            				Async::IO::Socket.connect(server_address) do |client|
         | 
| @@ -29,6 +29,29 @@ RSpec.describe Async::IO::Stream do | |
| 29 29 | 
             
            	let!(:stream) {Async::IO::Stream.new(buffer)}
         | 
| 30 30 | 
             
            	let(:io) {stream.io}
         | 
| 31 31 |  | 
| 32 | 
            +
            	describe '#close_read' do
         | 
| 33 | 
            +
            		let(:sockets) {@sockets = Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM)}
         | 
| 34 | 
            +
            		let!(:stream) {Async::IO::Stream.new(sockets.last)}
         | 
| 35 | 
            +
            		after(:each) {@sockets&.each(&:close)}
         | 
| 36 | 
            +
            		
         | 
| 37 | 
            +
            		it "can close the reading end of the stream" do
         | 
| 38 | 
            +
            			stream.close_read
         | 
| 39 | 
            +
            			
         | 
| 40 | 
            +
            			expect do
         | 
| 41 | 
            +
            				stream.read
         | 
| 42 | 
            +
            			end.to raise_error(IOError, /not opened for reading/)
         | 
| 43 | 
            +
            		end
         | 
| 44 | 
            +
            		
         | 
| 45 | 
            +
            		it "can close the writing end of the stream" do
         | 
| 46 | 
            +
            			stream.close_write
         | 
| 47 | 
            +
            			
         | 
| 48 | 
            +
            			expect do
         | 
| 49 | 
            +
            				stream.write("Oh no!")
         | 
| 50 | 
            +
            				stream.flush
         | 
| 51 | 
            +
            			end.to raise_error(IOError, /not opened for writing/)
         | 
| 52 | 
            +
            		end
         | 
| 53 | 
            +
            	end
         | 
| 54 | 
            +
            	
         | 
| 32 55 | 
             
            	describe '#read' do
         | 
| 33 56 | 
             
            		it "should read everything" do
         | 
| 34 57 | 
             
            			io.write "Hello World"
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: async-io
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 1. | 
| 4 | 
            +
              version: 1.22.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Samuel Williams
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2019-04- | 
| 11 | 
            +
            date: 2019-04-30 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: async
         |