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
|