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