async-io 1.4.0 → 1.5.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: 8b5507d72c8c7e07851a347dc15eacfbb51f06a03a50ef438af12bb889b28a8c
4
- data.tar.gz: b53e91190155fad15b9a8097ec5243a8af5fc81623f4acb2e68f5d1591752868
3
+ metadata.gz: 96b8fa83be7fa357a0aa41a783fb890389940717f110a67df2bcac406c0283ad
4
+ data.tar.gz: cf5fde9ff0c2b09f84b00cb2732606c4fd622f6819024fe2a1c10a48fa6564bf
5
5
  SHA512:
6
- metadata.gz: dbbf5038f8a3d9bed87b8d51a81a34b2c454cb79a80405a560c857d1cfab1ec49e8ca75363eec37f0e71ff3dc2c177b7394b8562a2b7a965c01e9a67af0134a9
7
- data.tar.gz: 2885f7b39a0bed3e4091900768499d3c0837a82b19e7cd5fb7d8770655a6f678283b4d49af2f195666b4fb6ffd003c093167f3d6ec7ce2f37ecbae436469a596
6
+ metadata.gz: 1efa54e086e3f3753a224bb1e7e4cb6a5fa59d15f570af9c1d2f3969eef5dae5698c657c7976ead21070ddfb6b9a87c5fe88f10c6625bd469cf422e05a1c2247
7
+ data.tar.gz: 3fc8f33825173f68d076ac6246e5a570db472613dfec9c3f2f30edd6f0d6719dd303138b802300a2d155c4cf423550a7286596aa26112d2415f05d7c881ef8d5
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'socket'
4
+ require 'openssl'
5
+
6
+ server = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM)
7
+ server.bind(Addrinfo.tcp('127.0.0.1', 4433))
8
+ server.listen(128)
9
+
10
+ ssl_server = OpenSSL::SSL::SSLServer.new(server, OpenSSL::SSL::SSLContext.new)
11
+
12
+ puts ssl_server.addr
13
+
14
+ # openssl/ssl.rb:234:in `addr': undefined method `addr' for #<Socket:fd 8> (NoMethodError)
@@ -18,45 +18,10 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require_relative '../socket'
21
+ require 'socket'
22
22
 
23
23
  module Async
24
24
  module IO
25
- module Wrap
26
- module TCPServer
27
- def self.new(*args)
28
- return ::TCPServer.new(*args) unless Task.current?
29
-
30
- case args.size
31
- when 2
32
- local_address = Async::IO::Address.tcp(*args)
33
- when 1
34
- local_address = Async::IO::Address.tcp("0.0.0.0", *args)
35
- else
36
- raise ArgumentError, "TCPServer.new([hostname], port)"
37
- end
38
-
39
- return Async::IO::Socket.bind(local_address)
40
- end
41
- end
42
-
43
- module TCPSocket
44
- def self.new(remote_host, remote_port, local_host=nil, local_port=nil)
45
- return ::TCPSocket.new(remote_host, remote_port, local_host, local_port) unless Task.current?
46
-
47
- remote_address = Async::IO::Address.tcp(remote_host, remote_port)
48
-
49
- if local_host && local_port
50
- local_address = Async::IO::Address.tcp(local_host, local_port)
51
- end
52
-
53
- return Async::IO::Socket.connect(remote_address, local_address)
54
- end
55
-
56
- def self.open(*args)
57
- self.new(*args)
58
- end
59
- end
60
- end
25
+ Address = Addrinfo
61
26
  end
62
27
  end
@@ -18,109 +18,92 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
+ require_relative 'address'
21
22
  require_relative 'socket'
22
23
  require 'uri'
23
24
 
24
25
  module Async
25
26
  module IO
26
- Address = Addrinfo
27
-
28
- class Endpoint < Struct.new(:specification, :options)
29
- include Comparable
30
-
31
- class << self
32
- def parse(string, **options)
33
- uri = URI.parse(string)
34
- self.send(uri.scheme, uri.host, uri.port, **options)
35
- end
36
-
37
- # args: nodename, service, family, socktype, protocol, flags
38
- def tcp(*args, **options)
39
- args[3] = ::Socket::SOCK_STREAM
40
-
41
- HostEndpoint.new(args, **options)
42
- end
27
+ class Endpoint
28
+ def self.parse(string, **options)
29
+ uri = URI.parse(string)
43
30
 
44
- def udp(*args, **options)
45
- args[3] = ::Socket::SOCK_DGRAM
46
-
47
- HostEndpoint.new(args, **options)
48
- end
49
-
50
- def unix(*args, **options)
51
- AddressEndpoint.new(Address.unix(*args), **options)
52
- end
31
+ self.send(uri.scheme, uri.host, uri.port, **options)
32
+ end
33
+
34
+ # args: nodename, service, family, socktype, protocol, flags
35
+ def self.tcp(*args, **options)
36
+ args[3] = ::Socket::SOCK_STREAM
53
37
 
54
- def ssl(*args, **options)
55
- SecureEndpoint.new(self.tcp(*args, **options), **options)
56
- end
38
+ HostEndpoint.new(args, **options)
39
+ end
40
+
41
+ def self.udp(*args, **options)
42
+ args[3] = ::Socket::SOCK_DGRAM
57
43
 
58
- # Generate a list of endpoints from an array.
59
- def each(specifications, &block)
60
- return to_enum(:each, specifications) unless block_given?
61
-
62
- specifications.each do |specification|
63
- if specification.is_a? self
64
- yield specification
65
- elsif specification.is_a? Array
66
- yield self.send(*specification)
67
- elsif specification.is_a? String
68
- yield self.parse(specification)
69
- elsif specification.is_a? ::BasicSocket
70
- yield SocketEndpoint.new(specification)
71
- elsif specification.is_a? Generic
72
- yield Endpoint.new(specification)
73
- else
74
- raise ArgumentError.new("Not sure how to convert #{specification} to endpoint!")
75
- end
76
- end
77
- end
44
+ HostEndpoint.new(args, **options)
78
45
  end
79
46
 
80
- def initialize(specification, **options)
81
- super(specification, options)
47
+ def self.unix(*args, **options)
48
+ AddressEndpoint.new(Address.unix(*args), **options)
82
49
  end
83
50
 
84
- def bind
85
- yield specification
51
+ def self.ssl(*args, **options)
52
+ SecureEndpoint.new(self.tcp(*args, **options), **options)
53
+ end
54
+
55
+ def self.try_convert(specification)
56
+ if specification.is_a? self
57
+ specification
58
+ elsif specification.is_a? Array
59
+ self.send(*specification)
60
+ elsif specification.is_a? String
61
+ self.parse(specification)
62
+ elsif specification.is_a? ::BasicSocket
63
+ SocketEndpoint.new(specification)
64
+ elsif specification.is_a? Generic
65
+ Endpoint.new(specification)
66
+ else
67
+ raise ArgumentError.new("Not sure how to convert #{specification} to endpoint!")
68
+ end
86
69
  end
87
70
 
88
- def accept(&block)
89
- backlog = self.options.fetch(:backlog, Socket::SOMAXCONN)
71
+ # Generate a list of endpoint from an array.
72
+ def self.each(specifications, &block)
73
+ return to_enum(:each, specifications) unless block_given?
90
74
 
75
+ specifications.each do |specification|
76
+ yield try_convert(specification)
77
+ end
78
+ end
79
+
80
+ def each
81
+ return to_enum unless block_given?
82
+
83
+ yield self
84
+ end
85
+
86
+ def accept(backlog = Socket::SOMAXCONN, &block)
91
87
  bind do |server|
92
88
  server.listen(backlog)
89
+
93
90
  server.accept_each(&block)
94
91
  end
95
92
  end
96
-
97
- def connect
98
- yield specification
99
- end
100
93
  end
101
94
 
102
95
  class HostEndpoint < Endpoint
103
- def bind(&block)
104
- task = Task.current
105
- tasks = []
106
-
107
- task.annotate("binding to #{specification.inspect}")
108
-
109
- Addrinfo.foreach(*specification).each do |address|
110
- tasks << task.async do
111
- Socket.bind(address, **options, &block)
112
- end
113
- end
114
-
115
- tasks.each(&:wait)
96
+ def initialize(specification, **options)
97
+ @specification = specification
98
+ @options = options
116
99
  end
117
100
 
118
101
  def connect(&block)
119
102
  last_error = nil
120
103
 
121
- Addrinfo.foreach(*specification).each do |address|
104
+ Addrinfo.foreach(*@specification).each do |address|
122
105
  begin
123
- return Socket.connect(address, &block)
106
+ return Socket.connect(address, **@options, &block)
124
107
  rescue
125
108
  last_error = $!
126
109
  end
@@ -128,41 +111,60 @@ module Async
128
111
 
129
112
  raise last_error
130
113
  end
131
- end
132
-
133
- # This class will open and close the socket automatically.
134
- class AddressEndpoint < Endpoint
114
+
135
115
  def bind(&block)
136
- Socket.bind(specification, **options, &block)
116
+ Addrinfo.foreach(*@specification) do |address|
117
+ Socket.bind(address, **@options, &block)
118
+ end
137
119
  end
138
120
 
139
- def connect(&block)
140
- Socket.connect(specification, &block)
121
+ def each
122
+ return to_enum unless block_given?
123
+
124
+ Addrinfo.foreach(*@specification).each do |address|
125
+ yield AddressEndpoint.new(address, **@options)
126
+ end
141
127
  end
142
128
  end
143
129
 
144
- # This class doesn't exert ownership over the specified socket, wraps a native ::IO.
145
- class SocketEndpoint < Endpoint
130
+ # This class will open and close the socket automatically.
131
+ class AddressEndpoint < Endpoint
132
+ def initialize(address, **options)
133
+ @address = address
134
+ @options = options
135
+ end
136
+
137
+ attr :address
138
+ attr :options
139
+
146
140
  def bind(&block)
147
- yield Socket.new(specification)
141
+ Socket.bind(@address, **@options, &block)
148
142
  end
149
143
 
150
144
  def connect(&block)
151
- yield Async::IO.try_convert(specification)
145
+ Socket.connect(@address, **@options, &block)
152
146
  end
153
147
  end
154
148
 
155
149
  class SecureEndpoint < Endpoint
150
+ def initialize(endpoint, **options)
151
+ @endpoint = endpoint
152
+ @options = options
153
+ end
154
+
155
+ attr :endpoint
156
+ attr :options
157
+
156
158
  def hostname
157
- options[:hostname]
159
+ @options[:hostname]
158
160
  end
159
161
 
160
162
  def params
161
- options[:ssl_params]
163
+ @options[:ssl_params]
162
164
  end
163
165
 
164
166
  def context
165
- if context = options[:ssl_context]
167
+ if context = @options[:ssl_context]
166
168
  if params = self.params
167
169
  context = context.dup
168
170
  context.set_params(params)
@@ -178,24 +180,47 @@ module Async
178
180
  return context
179
181
  end
180
182
 
181
- def bind(&block)
182
- specification.bind do |server|
183
+ def bind
184
+ @endpoint.bind do |server|
183
185
  yield SSLServer.new(server, context)
184
186
  end
185
187
  end
186
188
 
187
189
  def connect(&block)
188
- specification.connect do |socket|
189
- ssl_socket = SSLSocket.connect_socket(socket, context)
190
-
191
- # Used for SNI:
192
- if hostname = self.hostname
193
- ssl_socket.hostname = hostname
194
- end
195
-
196
- ssl_socket.connect
197
-
198
- yield ssl_socket
190
+ SSLSocket.connect(@endpoint.connect, context, hostname, &block)
191
+ end
192
+
193
+ def each
194
+ return to_enum unless block_given?
195
+
196
+ @endpoint.each do |endpoint|
197
+ yield self.class.new(endpoint, @options)
198
+ end
199
+ end
200
+ end
201
+
202
+ # This class doesn't exert ownership over the specified socket, wraps a native ::IO.
203
+ class SocketEndpoint < Endpoint
204
+ def initialize(socket)
205
+ # This socket should already be in the required state.
206
+ @socket = Async::IO.try_convert(socket)
207
+ end
208
+
209
+ attr :socket
210
+
211
+ def bind(&block)
212
+ if block_given?
213
+ yield @socket
214
+ else
215
+ return @socket
216
+ end
217
+ end
218
+
219
+ def connect(&block)
220
+ if block_given?
221
+ yield @socket
222
+ else
223
+ return @socket
199
224
  end
200
225
  end
201
226
  end
@@ -25,7 +25,11 @@ module Async
25
25
  module IO
26
26
  # Convert a Ruby ::IO object to a wrapped instance:
27
27
  def self.try_convert(io, &block)
28
- Generic::WRAPPERS[io.class].wrap(io, &block)
28
+ if wrapper_class = Generic::WRAPPERS[io.class]
29
+ wrapper_class.new(io, &block)
30
+ else
31
+ raise ArgumentError.new("Unsure how to wrap #{io.class}!")
32
+ end
29
33
  end
30
34
 
31
35
  # Represents an asynchronous IO within a reactor.
@@ -79,15 +83,39 @@ module Async
79
83
  end
80
84
  end
81
85
 
82
- wraps ::IO, :external_encoding, :internal_encoding, :autoclose?, :autoclose=, :pid, :stat, :binmode, :flush, :set_encoding, :to_io, :to_i, :reopen, :fileno, :fsync, :fdatasync, :sync, :sync=, :tell, :seek, :rewind, :pos, :pos=, :eof, :eof?, :close_on_exec?, :close_on_exec=, :closed?, :close_read, :close_write, :isatty, :tty?, :binmode?, :sysseek, :advise, :ioctl, :fcntl
86
+ wraps ::IO, :external_encoding, :internal_encoding, :autoclose?, :autoclose=, :pid, :stat, :binmode, :flush, :set_encoding, :to_io, :to_i, :reopen, :fileno, :fsync, :fdatasync, :sync, :sync=, :tell, :seek, :rewind, :pos, :pos=, :eof, :eof?, :close_on_exec?, :close_on_exec=, :closed?, :close_read, :close_write, :isatty, :tty?, :binmode?, :sysseek, :advise, :ioctl, :fcntl, :nread, :ready?, :pread, :pwrite, :pathconf
83
87
 
84
88
  # @example
85
89
  # data = io.read(512)
86
90
  wrap_blocking_method :read, :read_nonblock
91
+ alias sysread read
92
+ alias readpartial read
87
93
 
88
94
  # @example
89
95
  # io.write("Hello World")
90
96
  wrap_blocking_method :write, :write_nonblock
97
+ alias syswrite write
98
+ alias << write
99
+
100
+ def wait(timeout = nil, mode = :read)
101
+ case mode
102
+ when :read
103
+ wait_readable(timeout)
104
+ when :write
105
+ wait_writable(timeout)
106
+ else
107
+ wait_any(:rw, timeout)
108
+ end
109
+ end
110
+
111
+ def nonblock
112
+ true
113
+ end
114
+ alias nonblock= nonblock
115
+
116
+ def nonblock?
117
+ true
118
+ end
91
119
 
92
120
  protected
93
121
 
@@ -29,6 +29,10 @@ module Async
29
29
  @eol = eol
30
30
  end
31
31
 
32
+ def close
33
+ @stream.close
34
+ end
35
+
32
36
  attr :stream
33
37
  attr :eol
34
38
 
@@ -24,13 +24,11 @@ require_relative 'generic'
24
24
  module Async
25
25
  module IO
26
26
  class BasicSocket < Generic
27
- wraps ::BasicSocket, :setsockopt, :connect_address, :local_address, :remote_address, :do_not_reverse_lookup, :do_not_reverse_lookup=, :shutdown, :getsockopt, :getsockname, :getpeername, :getpeereid
27
+ wraps ::BasicSocket, :setsockopt, :connect_address, :close_read, :close_write, :local_address, :remote_address, :do_not_reverse_lookup, :do_not_reverse_lookup=, :shutdown, :getsockopt, :getsockname, :getpeername, :getpeereid
28
28
 
29
29
  wrap_blocking_method :recv, :recv_nonblock
30
30
  wrap_blocking_method :recvmsg, :recvmsg_nonblock
31
31
 
32
- wrap_blocking_method :recvfrom, :recvfrom_nonblock
33
-
34
32
  wrap_blocking_method :sendmsg, :sendmsg_nonblock
35
33
  wrap_blocking_method :send, :sendmsg_nonblock, invert: false
36
34
 
@@ -39,25 +37,7 @@ module Async
39
37
  end
40
38
  end
41
39
 
42
- module ServerSocket
43
- def accept(task: Task.current)
44
- peer, address = async_send(:accept_nonblock)
45
-
46
- wrapper = Socket.new(peer, self.reactor)
47
-
48
- return wrapper, address unless block_given?
49
-
50
- task.async do |task|
51
- task.annotate "incoming connection #{address.inspect}"
52
-
53
- begin
54
- yield wrapper, address
55
- ensure
56
- wrapper.close
57
- end
58
- end
59
- end
60
-
40
+ module Server
61
41
  def accept_each(task: Task.current)
62
42
  task.annotate "accepting connections #{self.local_address.inspect}"
63
43
 
@@ -72,8 +52,9 @@ module Async
72
52
  class Socket < BasicSocket
73
53
  wraps ::Socket, :bind, :ipv6only!, :listen
74
54
 
55
+ wrap_blocking_method :recvfrom, :recvfrom_nonblock
56
+
75
57
  include ::Socket::Constants
76
- include ServerSocket
77
58
 
78
59
  def connect(*args)
79
60
  begin
@@ -83,6 +64,28 @@ module Async
83
64
  end
84
65
  end
85
66
 
67
+ alias connect_nonblock connect
68
+
69
+ def accept(task: Task.current)
70
+ peer, address = async_send(:accept_nonblock)
71
+ wrapper = Socket.new(peer, task.reactor)
72
+
73
+ return wrapper, address unless block_given?
74
+
75
+ task.async do |task|
76
+ task.annotate "incoming connection #{address.inspect}"
77
+
78
+ begin
79
+ yield wrapper, address
80
+ ensure
81
+ wrapper.close
82
+ end
83
+ end
84
+ end
85
+
86
+ alias accept_nonblock accept
87
+ alias sysaccept accept
88
+
86
89
  def self.build(*args, task: Task.current)
87
90
  socket = wrapped_klass.new(*args)
88
91
 
@@ -101,11 +104,11 @@ module Async
101
104
  # @param remote_address [Addrinfo] The remote address to connect to.
102
105
  # @param local_address [Addrinfo] The local address to bind to before connecting.
103
106
  # @option protcol [Integer] The socket protocol to use.
104
- def self.connect(remote_address, local_address = nil, task: Task.current, **options)
107
+ def self.connect(remote_address, local_address = nil, reuse_port: false, task: Task.current, **options)
105
108
  task.annotate "connecting to #{remote_address.inspect}"
106
109
 
107
110
  wrapper = build(remote_address.afamily, remote_address.socktype, remote_address.protocol, **options) do |socket|
108
- socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, true)
111
+ socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, reuse_port)
109
112
 
110
113
  if local_address
111
114
  socket.bind(local_address.to_sockaddr)
@@ -138,8 +141,6 @@ module Async
138
141
  # @option protocol [Integer] The socket protocol to use.
139
142
  # @option reuse_port [Boolean] Allow this port to be bound in multiple processes.
140
143
  def self.bind(local_address, protocol: 0, reuse_port: false, task: Task.current, **options, &block)
141
- task.annotate "binding to #{local_address.inspect}"
142
-
143
144
  wrapper = build(local_address.afamily, local_address.socktype, protocol, **options) do |socket|
144
145
  socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, true)
145
146
  socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEPORT, true) if reuse_port
@@ -148,10 +149,14 @@ module Async
148
149
 
149
150
  return wrapper unless block_given?
150
151
 
151
- begin
152
- yield wrapper, task
153
- ensure
154
- wrapper.close
152
+ task.async do
153
+ task.annotate "binding to #{local_address.inspect}"
154
+
155
+ begin
156
+ yield wrapper, task
157
+ ensure
158
+ wrapper.close
159
+ end
155
160
  end
156
161
  end
157
162
 
@@ -163,10 +168,18 @@ module Async
163
168
  server.accept_each(task: task, &block)
164
169
  end
165
170
  end
171
+
172
+ include Server
173
+
174
+ def self.pair(*args)
175
+ ::Socket.pair(*args).map(&self.method(:new))
176
+ end
166
177
  end
167
178
 
168
179
  class IPSocket < BasicSocket
169
180
  wraps ::IPSocket, :addr, :peeraddr
181
+
182
+ wrap_blocking_method :recvfrom, :recvfrom_nonblock
170
183
  end
171
184
  end
172
185
  end