async-io 1.21.0 → 1.22.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ef50d94f2e881c86637e32d46f307a88b457537e9a649592d851e768474ffe2a
4
- data.tar.gz: 24aac313f12b07478e080fa5713432b6e270be5d74e21688bb39efed2d4cf296
3
+ metadata.gz: 368fe5eb02962730d8f47e3ab180913aa922085eb554cc2e430b8c15113e8f33
4
+ data.tar.gz: '09c5660537d3a7ce792bdf89674e79d0e40391346788c27808d14edbde1dfb13'
5
5
  SHA512:
6
- metadata.gz: '02688618b5035611cc4753acd581c0691c957ea0daefbdd4c357c52a4a31c12783b44dae77a5654aed36022abccac05a0813d300974dd94be26e97d5b25c47e9'
7
- data.tar.gz: 811063ba4e91b823be5e8037fcf537bb40aa35bbbf7210df5fe21348b1394e579f27a7c0721114da9671c2ad108d0a70413040850d0643f5602cfc7ad5abc44f
6
+ metadata.gz: 27929ee0118185c0dadb8216ce88f5d4296633481215d0c3865396b56e5c9d73f63bd8b6d6f01db5e641a3eb6e78209746b36bf0e91b17cb9e7d8550819d8a37
7
+ data.tar.gz: 35c33f0d712a94d7468c69aaa178139e756a3fcb1e56daf78671a2f347a790b141172ea6a3d993d4640da017d6f12624e1f10124225d0c4a877d2b39351b0b6f
@@ -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, Socket::MSG_PEEK, exception: false)
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, Socket::IPPROTO_TCP
51
- self.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, value ? 1 : 0)
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 Socket::IPPROTO_TCP
65
- self.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY).bool
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(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
152
+ socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
153
153
  end
154
154
 
155
155
  if reuse_port
156
- socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEPORT, 1)
156
+ socket.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1)
157
157
  end
158
158
 
159
159
  if linger
160
- socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_LINGER, linger)
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
@@ -163,7 +163,17 @@ module Async
163
163
  @io.closed?
164
164
  end
165
165
 
166
- # Closes the stream and flushes any unwritten data.
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
 
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Async
22
22
  module IO
23
- VERSION = "1.21.0"
23
+ VERSION = "1.22.0"
24
24
  end
25
25
  end
@@ -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/ ? 200 : 10000}
31
- let(:server_address) {Async::IO::Address.tcp('0.0.0.0', 10102)}
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
- connection_count = 0
37
+ connections = []
36
38
 
37
- connections_complete = task.async do
38
- last_count = 0
39
+ Async::IO::Socket.bind(server_address) do |server|
40
+ server.listen(Socket::SOMAXCONN)
39
41
 
40
- while connection_count < repeats
41
- if connection_count != last_count
42
- puts "#{connection_count}/#{repeats} simultaneous connections."
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
- # This is a synchronous block within the current task:
53
- Async::IO::Socket.accept(server_address) do |client|
54
- connection_count += 1
55
-
56
- # Wait until we've got all the connections:
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
- result = peer.write(data)
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 send/receive 10,000 messages" do
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("localhost", 6788)}
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.21.0
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-25 00:00:00.000000000 Z
11
+ date: 2019-04-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async