async-io 1.4.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
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