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 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