async-io 1.20.0 → 1.21.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +2 -0
- data/examples/udp/client.rb +13 -0
- data/examples/udp/server.rb +15 -0
- data/lib/async/io/endpoint.rb +62 -3
- data/lib/async/io/endpoint/each.rb +1 -1
- data/lib/async/io/host_endpoint.rb +21 -5
- data/lib/async/io/shared_endpoint.rb +11 -13
- data/lib/async/io/socket.rb +20 -19
- data/lib/async/io/ssl_endpoint.rb +7 -1
- data/lib/async/io/standard.rb +21 -3
- data/lib/async/io/tcp_socket.rb +2 -2
- data/lib/async/io/unix_endpoint.rb +5 -0
- data/lib/async/io/version.rb +1 -1
- data/spec/async/io/endpoint_spec.rb +5 -0
- data/spec/async/io/standard_spec.rb +2 -2
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ef50d94f2e881c86637e32d46f307a88b457537e9a649592d851e768474ffe2a
|
4
|
+
data.tar.gz: 24aac313f12b07478e080fa5713432b6e270be5d74e21688bb39efed2d4cf296
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '02688618b5035611cc4753acd581c0691c957ea0daefbdd4c357c52a4a31c12783b44dae77a5654aed36022abccac05a0813d300974dd94be26e97d5b25c47e9'
|
7
|
+
data.tar.gz: 811063ba4e91b823be5e8037fcf537bb40aa35bbbf7210df5fe21348b1394e579f27a7c0721114da9671c2ad108d0a70413040850d0643f5602cfc7ad5abc44f
|
data/.yardopts
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'async'
|
4
|
+
require 'async/io'
|
5
|
+
|
6
|
+
endpoint = Async::IO::Endpoint.udp("localhost", 5678)
|
7
|
+
|
8
|
+
Async do |task|
|
9
|
+
endpoint.bind do |socket|
|
10
|
+
while true
|
11
|
+
data, address = socket.recvfrom(1024)
|
12
|
+
socket.send(data.reverse, 0, address)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/async/io/endpoint.rb
CHANGED
@@ -28,29 +28,62 @@ module Async
|
|
28
28
|
# Endpoints represent a way of connecting or binding to an address.
|
29
29
|
class Endpoint
|
30
30
|
def initialize(**options)
|
31
|
-
@options = options
|
31
|
+
@options = options.freeze
|
32
32
|
end
|
33
33
|
|
34
|
-
|
34
|
+
def with(**options)
|
35
|
+
dup = self.dup
|
36
|
+
|
37
|
+
dup.options = @options.merge(options)
|
38
|
+
|
39
|
+
return dup
|
40
|
+
end
|
41
|
+
|
42
|
+
attr_accessor :options
|
35
43
|
|
44
|
+
# @return [String] The hostname of the bound socket.
|
36
45
|
def hostname
|
37
46
|
@options[:hostname]
|
38
47
|
end
|
39
48
|
|
49
|
+
# If `SO_REUSEPORT` is enabled on a socket, the socket can be successfully bound even if there are existing sockets bound to the same address, as long as all prior bound sockets also had `SO_REUSEPORT` set before they were bound.
|
50
|
+
# @return [Boolean, nil] The value for `SO_REUSEPORT`.
|
40
51
|
def reuse_port
|
41
52
|
@options[:reuse_port]
|
42
53
|
end
|
43
54
|
|
55
|
+
# If `SO_REUSEADDR` is enabled on a socket prior to binding it, the socket can be successfully bound unless there is a conflict with another socket bound to exactly the same combination of source address and port. Additionally, when set, binding a socket to the address of an existing socket in `TIME_WAIT` is not an error.
|
56
|
+
# @return [Boolean] The value for `SO_REUSEADDR`.
|
57
|
+
def reuse_address
|
58
|
+
@options[:reuse_address]
|
59
|
+
end
|
60
|
+
|
61
|
+
# Controls SO_LINGER. The amount of time the socket will stay in the `TIME_WAIT` state after being closed.
|
62
|
+
# @return [Integer, nil] The value for SO_LINGER.
|
63
|
+
def linger
|
64
|
+
@options[:linger]
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [Numeric] The default timeout for socket operations.
|
44
68
|
def timeout
|
45
69
|
@options[:timeout]
|
46
70
|
end
|
47
71
|
|
72
|
+
# @return [Address] the address to bind to before connecting.
|
73
|
+
def local_address
|
74
|
+
@options[:local_address]
|
75
|
+
end
|
76
|
+
|
77
|
+
# Endpoints sometimes have multiple paths.
|
78
|
+
# @yield [Endpoint] Enumerate all discrete paths as endpoints.
|
48
79
|
def each
|
49
80
|
return to_enum unless block_given?
|
50
81
|
|
51
82
|
yield self
|
52
83
|
end
|
53
84
|
|
85
|
+
# Accept connections from the specified endpoint.
|
86
|
+
# @param backlog [Integer] the number of connections to listen for.
|
54
87
|
def accept(backlog = Socket::SOMAXCONN, &block)
|
55
88
|
bind do |server|
|
56
89
|
server.listen(backlog)
|
@@ -59,10 +92,36 @@ module Async
|
|
59
92
|
end
|
60
93
|
end
|
61
94
|
|
95
|
+
# Map all endpoints by invoking `#bind`.
|
96
|
+
# @yield the bound wrapper.
|
97
|
+
def bound
|
98
|
+
wrappers = []
|
99
|
+
|
100
|
+
self.each do |endpoint|
|
101
|
+
wrapper = endpoint.bind
|
102
|
+
wrappers << wrapper
|
103
|
+
|
104
|
+
yield wrapper
|
105
|
+
end
|
106
|
+
|
107
|
+
return wrappers
|
108
|
+
ensure
|
109
|
+
wrappers.each(&:close) if $!
|
110
|
+
end
|
111
|
+
|
112
|
+
# Create an Endpoint instance by URI scheme. The host and port of the URI will be passed to the Endpoint factory method, along with any options.
|
113
|
+
#
|
114
|
+
# @param string [String] URI as string. Scheme will decide implementation used.
|
115
|
+
# @param options keyword arguments passed through to {#initialize}
|
116
|
+
#
|
117
|
+
# @see Endpoint.ssl ssl - invoked when parsing a URL with the ssl scheme "ssl://127.0.0.1"
|
118
|
+
# @see Endpoint.tcp tcp - invoked when parsing a URL with the tcp scheme: "tcp://127.0.0.1"
|
119
|
+
# @see Endpoint.udp udp - invoked when parsing a URL with the udp scheme: "udp://127.0.0.1"
|
120
|
+
# @see Endpoint.unix unix - invoked when parsing a URL with the unix scheme: "unix://127.0.0.1"
|
62
121
|
def self.parse(string, **options)
|
63
122
|
uri = URI.parse(string)
|
64
123
|
|
65
|
-
self.
|
124
|
+
self.public_send(uri.scheme, uri.host, uri.port, **options)
|
66
125
|
end
|
67
126
|
end
|
68
127
|
end
|
@@ -43,15 +43,24 @@ module Async
|
|
43
43
|
# @yield [Socket] the socket which is being connected, may be invoked more than once
|
44
44
|
# @return [Socket] the connected socket
|
45
45
|
# @raise if no connection could complete successfully
|
46
|
-
def connect
|
46
|
+
def connect
|
47
47
|
last_error = nil
|
48
48
|
|
49
|
+
task = Task.current
|
50
|
+
|
49
51
|
Addrinfo.foreach(*@specification) do |address|
|
50
52
|
begin
|
51
|
-
|
52
|
-
|
53
|
-
rescue
|
53
|
+
wrapper = Socket.connect(address, **@options, task: task)
|
54
|
+
rescue Errno::ECONNREFUSED, Errno::ENETUNREACH, Errno::EAGAIN
|
54
55
|
last_error = $!
|
56
|
+
else
|
57
|
+
return wrapper unless block_given?
|
58
|
+
|
59
|
+
begin
|
60
|
+
return yield wrapper, task
|
61
|
+
ensure
|
62
|
+
wrapper.close
|
63
|
+
end
|
55
64
|
end
|
56
65
|
end
|
57
66
|
|
@@ -78,13 +87,20 @@ module Async
|
|
78
87
|
end
|
79
88
|
|
80
89
|
class Endpoint
|
81
|
-
# args
|
90
|
+
# @param args nodename, service, family, socktype, protocol, flags. `socktype` will be set to Socket::SOCK_STREAM.
|
91
|
+
# @param options keyword arguments passed on to {HostEndpoint#initialize}
|
92
|
+
#
|
93
|
+
# @return [HostEndpoint]
|
82
94
|
def self.tcp(*args, **options)
|
83
95
|
args[3] = ::Socket::SOCK_STREAM
|
84
96
|
|
85
97
|
HostEndpoint.new(args, **options)
|
86
98
|
end
|
87
99
|
|
100
|
+
# @param args nodename, service, family, socktype, protocol, flags. `socktype` will be set to Socket::SOCK_DGRAM.
|
101
|
+
# @param options keyword arguments passed on to {HostEndpoint#initialize}
|
102
|
+
#
|
103
|
+
# @return [HostEndpoint]
|
88
104
|
def self.udp(*args, **options)
|
89
105
|
args[3] = ::Socket::SOCK_DGRAM
|
90
106
|
|
@@ -22,31 +22,27 @@ require_relative 'endpoint'
|
|
22
22
|
|
23
23
|
module Async
|
24
24
|
module IO
|
25
|
+
# Pre-connect and pre-bind sockets so that it can be used between processes.
|
25
26
|
class SharedEndpoint < Endpoint
|
27
|
+
# Create a new `SharedEndpoint` by binding to the given endpoint.
|
26
28
|
def self.bound(endpoint, backlog = Socket::SOMAXCONN)
|
27
|
-
wrappers =
|
28
|
-
|
29
|
-
endpoint.each do |endpoint|
|
30
|
-
server = endpoint.bind
|
31
|
-
|
29
|
+
wrappers = endpoint.bound do |server|
|
32
30
|
server.listen(backlog)
|
33
|
-
|
34
31
|
server.close_on_exec = false
|
35
32
|
server.reactor = nil
|
36
|
-
|
37
|
-
wrappers << server
|
38
33
|
end
|
39
34
|
|
40
|
-
self.new(endpoint, wrappers)
|
35
|
+
return self.new(endpoint, wrappers)
|
41
36
|
end
|
42
37
|
|
38
|
+
# Create a new `SharedEndpoint` by connecting to the given endpoint.
|
43
39
|
def self.connected(endpoint)
|
44
|
-
|
40
|
+
wrapper = endpoint.connect
|
45
41
|
|
46
|
-
|
47
|
-
|
42
|
+
wrapper.close_on_exec = false
|
43
|
+
wrapper.reactor = nil
|
48
44
|
|
49
|
-
self.new(endpoint, [
|
45
|
+
return self.new(endpoint, [wrapper])
|
50
46
|
end
|
51
47
|
|
52
48
|
def initialize(endpoint, wrappers, **options)
|
@@ -59,8 +55,10 @@ module Async
|
|
59
55
|
attr :endpoint
|
60
56
|
attr :wrappers
|
61
57
|
|
58
|
+
# Close all the internal wrappers.
|
62
59
|
def close
|
63
60
|
@wrappers.each(&:close)
|
61
|
+
@wrappers.clear
|
64
62
|
end
|
65
63
|
|
66
64
|
def bind
|
data/lib/async/io/socket.rb
CHANGED
@@ -108,6 +108,7 @@ module Async
|
|
108
108
|
|
109
109
|
include ::Socket::Constants
|
110
110
|
|
111
|
+
# @raise Errno::EAGAIN the connection failed due to the remote end being overloaded.
|
111
112
|
def connect(*args)
|
112
113
|
begin
|
113
114
|
async_send(:connect_nonblock, *args)
|
@@ -142,9 +143,23 @@ module Async
|
|
142
143
|
alias sysaccept accept
|
143
144
|
|
144
145
|
# Build and wrap the underlying io.
|
145
|
-
|
146
|
+
# @option reuse_port [Boolean] Allow this port to be bound in multiple processes.
|
147
|
+
# @option reuse_address [Boolean] Allow this port to be bound in multiple processes.
|
148
|
+
def self.build(*args, timeout: nil, reuse_address: true, reuse_port: nil, linger: nil, task: Task.current)
|
146
149
|
socket = wrapped_klass.new(*args)
|
147
150
|
|
151
|
+
if reuse_address
|
152
|
+
socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
|
153
|
+
end
|
154
|
+
|
155
|
+
if reuse_port
|
156
|
+
socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEPORT, 1)
|
157
|
+
end
|
158
|
+
|
159
|
+
if linger
|
160
|
+
socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_LINGER, linger)
|
161
|
+
end
|
162
|
+
|
148
163
|
yield socket
|
149
164
|
|
150
165
|
wrapper = self.new(socket, task.reactor)
|
@@ -160,19 +175,14 @@ module Async
|
|
160
175
|
# Establish a connection to a given `remote_address`.
|
161
176
|
# @example
|
162
177
|
# socket = Async::IO::Socket.connect(Async::IO::Address.tcp("8.8.8.8", 53))
|
163
|
-
# @param remote_address [
|
164
|
-
# @
|
165
|
-
|
166
|
-
def self.connect(remote_address, local_address = nil, reuse_port: nil, task: Task.current, **options)
|
178
|
+
# @param remote_address [Address] The remote address to connect to.
|
179
|
+
# @option local_address [Address] The local address to bind to before connecting.
|
180
|
+
def self.connect(remote_address, local_address: nil, task: Task.current, **options)
|
167
181
|
Async.logger.debug(self) {"Connecting to #{remote_address.inspect}"}
|
168
182
|
|
169
183
|
task.annotate "connecting to #{remote_address.inspect}"
|
170
184
|
|
171
185
|
wrapper = build(remote_address.afamily, remote_address.socktype, remote_address.protocol, **options) do |socket|
|
172
|
-
if reuse_port
|
173
|
-
socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
|
174
|
-
end
|
175
|
-
|
176
186
|
if local_address
|
177
187
|
socket.bind(local_address.to_sockaddr)
|
178
188
|
end
|
@@ -200,19 +210,10 @@ module Async
|
|
200
210
|
# socket = Async::IO::Socket.bind(Async::IO::Address.tcp("0.0.0.0", 9090))
|
201
211
|
# @param local_address [Address] The local address to bind to.
|
202
212
|
# @option protocol [Integer] The socket protocol to use.
|
203
|
-
|
204
|
-
def self.bind(local_address, protocol: 0, reuse_port: nil, reuse_address: true, task: Task.current, **options, &block)
|
213
|
+
def self.bind(local_address, protocol: 0, task: Task.current, **options, &block)
|
205
214
|
Async.logger.debug(self) {"Binding to #{local_address.inspect}"}
|
206
215
|
|
207
216
|
wrapper = build(local_address.afamily, local_address.socktype, protocol, **options) do |socket|
|
208
|
-
if reuse_address
|
209
|
-
socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
|
210
|
-
end
|
211
|
-
|
212
|
-
if reuse_port
|
213
|
-
socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEPORT, 1)
|
214
|
-
end
|
215
|
-
|
216
217
|
socket.bind(local_address.to_sockaddr)
|
217
218
|
end
|
218
219
|
|
@@ -90,7 +90,7 @@ module Async
|
|
90
90
|
return to_enum unless block_given?
|
91
91
|
|
92
92
|
@endpoint.each do |endpoint|
|
93
|
-
yield self.class.new(endpoint,
|
93
|
+
yield self.class.new(endpoint, **@options)
|
94
94
|
end
|
95
95
|
end
|
96
96
|
end
|
@@ -99,6 +99,12 @@ module Async
|
|
99
99
|
SecureEndpoint = SSLEndpoint
|
100
100
|
|
101
101
|
class Endpoint
|
102
|
+
# @param args
|
103
|
+
# @param ssl_context [OpenSSL::SSL::SSLContext, nil]
|
104
|
+
# @param hostname [String, nil]
|
105
|
+
# @param options keyword arguments passed through to {Endpoint.tcp}
|
106
|
+
#
|
107
|
+
# @return [SSLEndpoint]
|
102
108
|
def self.ssl(*args, ssl_context: nil, hostname: nil, **options)
|
103
109
|
SSLEndpoint.new(self.tcp(*args, **options), ssl_context: ssl_context, hostname: hostname)
|
104
110
|
end
|
data/lib/async/io/standard.rb
CHANGED
@@ -22,8 +22,26 @@ require_relative 'generic'
|
|
22
22
|
|
23
23
|
module Async
|
24
24
|
module IO
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
class StandardInput < Generic
|
26
|
+
def initialize(io = $stdin)
|
27
|
+
super(io)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class StandardOutput < Generic
|
32
|
+
def initialize(io = $stdout)
|
33
|
+
super(io)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class StandardError < Generic
|
38
|
+
def initialize(io = $stderr)
|
39
|
+
super(io)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
STDIN = StandardInput.new
|
44
|
+
STDOUT = StandardOutput.new
|
45
|
+
STDERR = StandardError.new
|
28
46
|
end
|
29
47
|
end
|
data/lib/async/io/tcp_socket.rb
CHANGED
@@ -54,7 +54,7 @@ module Async
|
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
57
|
-
def initialize(remote_host, remote_port = nil, local_host = nil, local_port =
|
57
|
+
def initialize(remote_host, remote_port = nil, local_host = nil, local_port = 0)
|
58
58
|
if remote_host.is_a? ::TCPSocket
|
59
59
|
super(remote_host)
|
60
60
|
else
|
@@ -62,7 +62,7 @@ module Async
|
|
62
62
|
local_address = Addrinfo.tcp(local_host, local_port) if local_host
|
63
63
|
|
64
64
|
# We do this unusual dance to avoid leaking an "open" socket instance.
|
65
|
-
socket = Socket.connect(remote_address, local_address)
|
65
|
+
socket = Socket.connect(remote_address, local_address: local_address)
|
66
66
|
fd = socket.fcntl(Fcntl::F_DUPFD)
|
67
67
|
Async.logger.debug(self) {"Connected to #{remote_address.inspect}: #{fd}"}
|
68
68
|
socket.close
|
@@ -59,6 +59,11 @@ module Async
|
|
59
59
|
end
|
60
60
|
|
61
61
|
class Endpoint
|
62
|
+
# @param path [String]
|
63
|
+
# @param type Socket type
|
64
|
+
# @param options keyword arguments passed through to {UNIXEndpoint#initialize}
|
65
|
+
#
|
66
|
+
# @return [UNIXEndpoint]
|
62
67
|
def self.unix(path, type = ::Socket::SOCK_STREAM, **options)
|
63
68
|
UNIXEndpoint.new(path, type, **options)
|
64
69
|
end
|
data/lib/async/io/version.rb
CHANGED
@@ -57,6 +57,11 @@ RSpec.describe Async::IO::Endpoint do
|
|
57
57
|
expect(subject.hostname).to be == '0.0.0.0'
|
58
58
|
end
|
59
59
|
|
60
|
+
it "has local address" do
|
61
|
+
address = Async::IO::Address.tcp('127.0.0.1', 8080)
|
62
|
+
expect(subject.with(local_address: address).local_address).to be == address
|
63
|
+
end
|
64
|
+
|
60
65
|
let(:message) {"Hello World!"}
|
61
66
|
|
62
67
|
it "can connect to bound server" do
|
@@ -31,7 +31,7 @@ end
|
|
31
31
|
RSpec.describe Async::IO::STDOUT do
|
32
32
|
include_context Async::RSpec::Reactor
|
33
33
|
|
34
|
-
it "should be able to
|
34
|
+
it "should be able to write" do
|
35
35
|
expect(subject.write("")).to be == 0
|
36
36
|
end
|
37
37
|
end
|
@@ -39,7 +39,7 @@ end
|
|
39
39
|
RSpec.describe Async::IO::STDERR do
|
40
40
|
include_context Async::RSpec::Reactor
|
41
41
|
|
42
|
-
it "should be able to
|
42
|
+
it "should be able to write" do
|
43
43
|
expect(subject.write("")).to be == 0
|
44
44
|
end
|
45
45
|
end
|
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.21.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-
|
11
|
+
date: 2019-04-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: async
|
@@ -119,6 +119,7 @@ files:
|
|
119
119
|
- ".gitignore"
|
120
120
|
- ".rspec"
|
121
121
|
- ".travis.yml"
|
122
|
+
- ".yardopts"
|
122
123
|
- Gemfile
|
123
124
|
- README.md
|
124
125
|
- Rakefile
|
@@ -131,6 +132,8 @@ files:
|
|
131
132
|
- examples/issues/broken_ssl.rb
|
132
133
|
- examples/millions/client.rb
|
133
134
|
- examples/millions/server.rb
|
135
|
+
- examples/udp/client.rb
|
136
|
+
- examples/udp/server.rb
|
134
137
|
- lib/async/io.rb
|
135
138
|
- lib/async/io/address.rb
|
136
139
|
- lib/async/io/address_endpoint.rb
|
@@ -201,7 +204,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
201
204
|
- !ruby/object:Gem::Version
|
202
205
|
version: '0'
|
203
206
|
requirements: []
|
204
|
-
rubygems_version: 3.0.
|
207
|
+
rubygems_version: 3.0.3
|
205
208
|
signing_key:
|
206
209
|
specification_version: 4
|
207
210
|
summary: Provides support for asynchonous TCP, UDP, UNIX and SSL sockets.
|