io-endpoint 0.3.0 → 0.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: c519269d1e8459a607159be713100765c2e424f199a08cce97e1ba6c877b5b6a
4
- data.tar.gz: ee2e142c5320d9412395c80098e669971b8956b43ad58252d52a85e2a97a5a97
3
+ metadata.gz: 204ed5172990d0df3a5d02e63b8e75d8caaf161808fea4e99b67f0d422241cc6
4
+ data.tar.gz: 7ba7e7052949565c4cb327076c493db15d6f3057493f93d4210ec5ac5bc7808b
5
5
  SHA512:
6
- metadata.gz: 67ce6a415b998cd1d072a1faee146803f30de818666278846cbdf69f8f72fd8c30d0df371ec44747a2ea74a1fdd0d86f100e187ba15d17275ffbb335d29e6df5
7
- data.tar.gz: 360a4f1ee5e162e71be467b0090e54eade25f141abde4452f6ba0e389def1398eaabfe5f76aaed4cbf333ba0c40334e014029d7fb76e13d09ff60f428ac63d9f
6
+ metadata.gz: 12aa8f963bc0787de988e9efcf80fbff38a857e8130e663ed8c329402048ab9e1940dcc2637f17de2f77f43d9222914808b5ec89e91420ed0d07475dbabc9d0e
7
+ data.tar.gz: ce830974878205da1f7e91378af0bdb05f1a4017cc60715c2dca5607573992f38d97712e6b879d99ab31db95a5b12c40ab3df959da12cc56f4b2189dd37b314e
checksums.yaml.gz.sig CHANGED
Binary file
@@ -0,0 +1,27 @@
1
+ require 'socket'
2
+
3
+ class IO
4
+ def connected?
5
+ !closed?
6
+ end
7
+ end
8
+
9
+ class Socket
10
+ def connected?
11
+ # Is it likely that the socket is still connected?
12
+ # May return false positive, but won't return false negative.
13
+ return false unless super
14
+
15
+ # If we can wait for the socket to become readable, we know that the socket may still be open.
16
+ result = to_io.recv_nonblock(1, MSG_PEEK, exception: false)
17
+
18
+ # No data was available - newer Ruby can return nil instead of empty string:
19
+ return false if result.nil?
20
+
21
+ # Either there was some data available, or we can wait to see if there is data avaialble.
22
+ return !result.empty? || result == :wait_readable
23
+ rescue Errno::ECONNRESET
24
+ # This might be thrown by recv_nonblock.
25
+ return false
26
+ end
27
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023, by Samuel Williams.
5
+
6
+ require_relative 'generic'
7
+ require_relative 'composite_endpoint'
8
+ require_relative 'address_endpoint'
9
+
10
+ module IO::Endpoint
11
+ class BoundEndpoint < Generic
12
+ def self.bound(endpoint, backlog: Socket::SOMAXCONN, close_on_exec: false)
13
+ sockets = endpoint.bind
14
+
15
+ sockets.each do |server|
16
+ # This is somewhat optional. We want to have a generic interface as much as possible so that users of this interface can just call it without knowing a lot of internal details. Therefore, we ignore errors here if it's because the underlying socket does not support the operation.
17
+ begin
18
+ server.listen(backlog)
19
+ rescue Errno::EOPNOTSUPP
20
+ # Ignore.
21
+ end
22
+
23
+ server.close_on_exec = close_on_exec
24
+ end
25
+
26
+ return self.new(endpoint, sockets, **endpoint.options)
27
+ end
28
+
29
+ def initialize(endpoint, sockets, **options)
30
+ super(**options)
31
+
32
+ @endpoint = endpoint
33
+ @sockets = sockets
34
+ end
35
+
36
+ attr :endpoint
37
+ attr :sockets
38
+
39
+ # A endpoint for the local end of the bound socket.
40
+ # @returns [CompositeEndpoint] A composite endpoint for the local end of the bound socket.
41
+ def local_address_endpoint(**options)
42
+ endpoints = @sockets.map do |socket|
43
+ AddressEndpoint.new(socket.to_io.local_address, **options)
44
+ end
45
+
46
+ return CompositeEndpoint.new(endpoints)
47
+ end
48
+
49
+ # A endpoint for the remote end of the bound socket.
50
+ # @returns [CompositeEndpoint] A composite endpoint for the remote end of the bound socket.
51
+ def remote_address_endpoint(**options)
52
+ endpoints = @sockets.map do |wrapper|
53
+ AddressEndpoint.new(socket.to_io.remote_address, **options)
54
+ end
55
+
56
+ return CompositeEndpoint.new(endpoints)
57
+ end
58
+
59
+ def close
60
+ @sockets.each(&:close)
61
+ @sockets.clear
62
+ end
63
+
64
+ def to_s
65
+ "\#<#{self.class} #{@sockets.size} bound sockets for #{@endpoint}>"
66
+ end
67
+
68
+ def bind(wrapper = Wrapper.default, &block)
69
+ @sockets.map do |server|
70
+ if block_given?
71
+ wrapper.async do
72
+ yield server
73
+ end
74
+ else
75
+ server.dup
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ class Generic
82
+ def bound(**options)
83
+ BoundEndpoint.bound(self, **options)
84
+ end
85
+ end
86
+ end
@@ -6,6 +6,7 @@
6
6
  require_relative 'generic'
7
7
 
8
8
  module IO::Endpoint
9
+ # A composite endpoint is a collection of endpoints that are used in order.
9
10
  class CompositeEndpoint < Generic
10
11
  def initialize(endpoints, **options)
11
12
  super(**options)
@@ -18,12 +19,12 @@ module IO::Endpoint
18
19
  end
19
20
  end
20
21
 
21
- def connect(&block)
22
+ def connect(wrapper = Wrapper.default, &block)
22
23
  last_error = nil
23
24
 
24
25
  @endpoints.each do |endpoint|
25
26
  begin
26
- return endpoint.connect(&block)
27
+ return endpoint.connect(wrapper, &block)
27
28
  rescue => last_error
28
29
  end
29
30
  end
@@ -31,7 +32,7 @@ module IO::Endpoint
31
32
  raise last_error
32
33
  end
33
34
 
34
- def bind(&block)
35
+ def bind(wrapper = Wrapper.default, &block)
35
36
  if block_given?
36
37
  @endpoints.each do |endpoint|
37
38
  endpoint.bind(&block)
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023, by Samuel Williams.
5
+
6
+ require_relative 'generic'
7
+ require_relative 'composite_endpoint'
8
+ require_relative 'socket_endpoint'
9
+
10
+ require 'openssl'
11
+
12
+ module IO::Endpoint
13
+ class ConnectedEndpoint < Generic
14
+ def self.connected(endpoint, close_on_exec: false)
15
+ socket = endpoint.connect
16
+
17
+ socket.close_on_exec = close_on_exec
18
+
19
+ return self.new(endpoint, socket, **endpoint.options)
20
+ end
21
+
22
+ def initialize(endpoint, socket, **options)
23
+ super(**options)
24
+
25
+ @endpoint = endpoint
26
+ @socket = socket
27
+ end
28
+
29
+ attr :endpoint
30
+ attr :socket
31
+
32
+ # A endpoint for the local end of the bound socket.
33
+ # @returns [AddressEndpoint] A endpoint for the local end of the connected socket.
34
+ def local_address_endpoint(**options)
35
+ AddressEndpoint.new(socket.to_io.local_address, **options)
36
+ end
37
+
38
+ # A endpoint for the remote end of the bound socket.
39
+ # @returns [AddressEndpoint] A endpoint for the remote end of the connected socket.
40
+ def remote_address_endpoint(**options)
41
+ AddressEndpoint.new(socket.to_io.remote_address, **options)
42
+ end
43
+
44
+ def connect(wrapper = Wrapper.default, &block)
45
+ if block_given?
46
+ yield @socket
47
+ else
48
+ return @socket.dup
49
+ end
50
+ end
51
+
52
+ def close
53
+ if @socket
54
+ @socket.close
55
+ @socket = nil
56
+ end
57
+ end
58
+
59
+ def to_s
60
+ "\#<#{self.class} #{@socket} connected for #{@endpoint}>"
61
+ end
62
+ end
63
+
64
+ class Generic
65
+ def connected(**options)
66
+ ConnectedEndpoint.connected(self, **options)
67
+ end
68
+ end
69
+ end
@@ -59,31 +59,41 @@ module IO::Endpoint
59
59
  @options[:local_address]
60
60
  end
61
61
 
62
- # Endpoints sometimes have multiple paths.
63
- # @yield [Endpoint] Enumerate all discrete paths as endpoints.
62
+ # Bind a socket to the given address. If a block is given, the socket will be automatically closed when the block exits.
63
+ # @parameter wrapper [Wrapper] The wrapper to use for binding.
64
+ # @yields {|socket| ...} An optional block which will be passed the socket.
65
+ # @parameter socket [Socket] The socket which has been bound.
66
+ # @returns [Array(Socket)] the bound socket
67
+ def bind(wrapper = Wrapper.default, &block)
68
+ raise NotImplementedError
69
+ end
70
+
71
+ # Connects a socket to the given address. If a block is given, the socket will be automatically closed when the block exits.
72
+ # @parameter wrapper [Wrapper] The wrapper to use for connecting.
73
+ # @return [Socket] the connected socket
74
+ def connect(wrapper = Wrapper.default, &block)
75
+ raise NotImplementedError
76
+ end
77
+
78
+ # Bind and accept connections on the given address.
79
+ # @parameter wrapper [Wrapper] The wrapper to use for accepting connections.
80
+ # @yields [Socket] The accepted socket.
81
+ def accept(wrapper = Wrapper.default, &block)
82
+ bind(wrapper) do |server|
83
+ wrapper.accept(server, **@options, &block)
84
+ end
85
+ end
86
+
87
+ # Enumerate all discrete paths as endpoints.
88
+ # @yields {|endpoint| ...} A block which will be passed each endpoint.
89
+ # @parameter endpoint [Endpoint] The endpoint.
64
90
  def each
65
91
  return to_enum unless block_given?
66
92
 
67
93
  yield self
68
94
  end
69
95
 
70
- # Accept connections from the specified endpoint.
71
- # @param backlog [Integer] the number of connections to listen for.
72
- def accept(backlog: Socket::SOMAXCONN, &block)
73
- bind do |server|
74
- server.listen(backlog) if backlog
75
-
76
- while true
77
- socket, address = server.accept
78
-
79
- Fiber.schedule do
80
- yield socket, address
81
- end
82
- end
83
- end
84
- end
85
-
86
- # 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.\
96
+ # 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.
87
97
  #
88
98
  # You should not use untrusted input as it may execute arbitrary code.
89
99
  #
@@ -1,136 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023, by Samuel Williams.
4
+ # Copyright, 2024, by Samuel Williams.
5
5
 
6
- require_relative 'generic'
7
- require_relative 'composite_endpoint'
8
- require_relative 'socket_endpoint'
9
-
10
- require 'openssl'
11
-
12
- module IO::Endpoint
13
- # Pre-connect and pre-bind sockets so that it can be used between processes.
14
- class SharedEndpoint < Generic
15
- # Create a new `SharedEndpoint` by binding to the given endpoint.
16
- def self.bound(endpoint, backlog: Socket::SOMAXCONN, close_on_exec: false, **options)
17
- sockets = endpoint.bind(**options)
18
-
19
- sockets.each do |server|
20
- # This is somewhat optional. We want to have a generic interface as much as possible so that users of this interface can just call it without knowing a lot of internal details. Therefore, we ignore errors here if it's because the underlying socket does not support the operation.
21
- begin
22
- server.listen(backlog)
23
- rescue Errno::EOPNOTSUPP
24
- # Ignore.
25
- end
26
-
27
- server.close_on_exec = close_on_exec
28
- end
29
-
30
- return self.new(endpoint, sockets)
31
- end
32
-
33
- # Create a new `SharedEndpoint` by connecting to the given endpoint.
34
- def self.connected(endpoint, close_on_exec: false)
35
- wrapper = endpoint.connect
36
-
37
- wrapper.close_on_exec = close_on_exec
38
-
39
- return self.new(endpoint, [wrapper])
40
- end
41
-
42
- def initialize(endpoint, sockets, **options)
43
- super(**options)
44
-
45
- raise TypeError, "sockets must be an Array" unless sockets.is_a?(Array)
46
-
47
- @endpoint = endpoint
48
- @sockets = sockets
49
- end
50
-
51
- attr :endpoint
52
- attr :sockets
53
-
54
- def local_address_endpoint(**options)
55
- endpoints = @sockets.map do |wrapper|
56
- AddressEndpoint.new(wrapper.to_io.local_address)
57
- end
58
-
59
- return CompositeEndpoint.new(endpoints, **options)
60
- end
61
-
62
- def remote_address_endpoint(**options)
63
- endpoints = @sockets.map do |wrapper|
64
- AddressEndpoint.new(wrapper.to_io.remote_address)
65
- end
66
-
67
- return CompositeEndpoint.new(endpoints, **options)
68
- end
69
-
70
- # Close all the internal sockets.
71
- def close
72
- @sockets.each(&:close)
73
- @sockets.clear
74
- end
75
-
76
- def each(&block)
77
- return to_enum unless block_given?
78
-
79
- @sockets.each do |socket|
80
- yield SocketEndpoint.new(socket.dup)
81
- end
82
- end
83
-
84
- def bind(wrapper = Wrapper.default, &block)
85
- @sockets.each.map do |server|
86
- server = server.dup
87
-
88
- if block_given?
89
- wrapper.async do
90
- begin
91
- yield server
92
- ensure
93
- server.close
94
- end
95
- end
96
- else
97
- server
98
- end
99
- end
100
- end
101
-
102
- def connect(wrapper = Wrapper.default, &block)
103
- @sockets.each do |socket|
104
- socket = socket.dup
105
-
106
- return socket unless block_given?
107
-
108
- begin
109
- return yield(socket)
110
- ensure
111
- socket.close
112
- end
113
- end
114
- end
115
-
116
- def accept(**options, &block)
117
- bind do |server|
118
- server.accept(&block)
119
- end
120
- end
121
-
122
- def to_s
123
- "\#<#{self.class} #{@sockets.size} descriptors for #{@endpoint}>"
124
- end
125
- end
126
-
127
- class Generic
128
- def bound(**options)
129
- SharedEndpoint.bound(self, **options)
130
- end
131
-
132
- def connected(**options)
133
- SharedEndpoint.connected(self, **options)
134
- end
135
- end
136
- end
6
+ require_relative 'bound_endpoint'
7
+ require_relative 'connected_endpoint'
@@ -8,34 +8,52 @@ require_relative 'generic'
8
8
 
9
9
  require 'openssl'
10
10
 
11
- module OpenSSL::SSL::SocketForwarder
12
- unless method_defined?(:close_on_exec=)
13
- def close_on_exec=(value)
14
- to_io.close_on_exec = value
15
- end
16
- end
17
-
18
- unless method_defined?(:close_on_exec)
19
- def local_address
20
- to_io.local_address
21
- end
22
- end
23
-
24
- unless method_defined?(:wait)
25
- def wait(*arguments)
26
- to_io.wait(*arguments)
27
- end
28
- end
29
-
30
- unless method_defined?(:wait_readable)
31
- def wait_readable(*arguments)
32
- to_io.wait_readable(*arguments)
33
- end
34
- end
35
-
36
- unless method_defined?(:wait_writable)
37
- def wait_writable(*arguments)
38
- to_io.wait_writable(*arguments)
11
+ module OpenSSL
12
+ module SSL
13
+ module SocketForwarder
14
+ unless method_defined?(:close_on_exec=)
15
+ def close_on_exec=(value)
16
+ to_io.close_on_exec = value
17
+ end
18
+ end
19
+
20
+ unless method_defined?(:close_on_exec)
21
+ def local_address
22
+ to_io.local_address
23
+ end
24
+ end
25
+
26
+ unless method_defined?(:wait)
27
+ def wait(*arguments)
28
+ to_io.wait(*arguments)
29
+ end
30
+ end
31
+
32
+ unless method_defined?(:wait_readable)
33
+ def wait_readable(*arguments)
34
+ to_io.wait_readable(*arguments)
35
+ end
36
+ end
37
+
38
+ unless method_defined?(:wait_writable)
39
+ def wait_writable(*arguments)
40
+ to_io.wait_writable(*arguments)
41
+ end
42
+ end
43
+
44
+ if IO.method_defined?(:timeout)
45
+ unless method_defined?(:timeout)
46
+ def timeout
47
+ to_io.timeout
48
+ end
49
+ end
50
+
51
+ unless method_defined?(:timeout=)
52
+ def timeout=(value)
53
+ to_io.timeout = value
54
+ end
55
+ end
56
+ end
39
57
  end
40
58
  end
41
59
  end
@@ -91,14 +109,14 @@ module IO::Endpoint
91
109
  # Connect to the underlying endpoint and establish a SSL connection.
92
110
  # @yield [Socket] the socket which is being connected
93
111
  # @return [Socket] the connected socket
94
- def bind
112
+ def bind(*arguments, **options, &block)
95
113
  if block_given?
96
- @endpoint.bind do |server|
97
- yield ::OpenSSL::SSL::SSLServer.new(server, context)
114
+ @endpoint.bind(*arguments, **options) do |server|
115
+ yield ::OpenSSL::SSL::SSLServer.new(server, self.context)
98
116
  end
99
117
  else
100
- @endpoint.bind.map do |server|
101
- ::OpenSSL::SSL::SSLServer.new(server, context)
118
+ @endpoint.bind(*arguments, **options).map do |server|
119
+ ::OpenSSL::SSL::SSLServer.new(server, self.context)
102
120
  end
103
121
  end
104
122
  end
@@ -107,7 +125,7 @@ module IO::Endpoint
107
125
  # @yield [Socket] the socket which is being connected
108
126
  # @return [Socket] the connected socket
109
127
  def connect(&block)
110
- socket = ::OpenSSL::SSL::SSLSocket.new(@endpoint.connect, context)
128
+ socket = ::OpenSSL::SSL::SSLSocket.new(@endpoint.connect, self.context)
111
129
 
112
130
  if hostname = self.hostname
113
131
  socket.hostname = hostname
@@ -5,6 +5,6 @@
5
5
 
6
6
  class IO
7
7
  module Endpoint
8
- VERSION = "0.3.0"
8
+ VERSION = "0.5.0"
9
9
  end
10
10
  end
@@ -30,7 +30,6 @@ module IO::Endpoint
30
30
  # On Darwin, sometimes occurs when the connection is not yet fully formed. Empirically, TCP_NODELAY is enabled despite this result.
31
31
  rescue Errno::EOPNOTSUPP
32
32
  # Some platforms may simply not support the operation.
33
- # Console.logger.warn(self) {"Unable to set sync=#{value}!"}
34
33
  rescue Errno::ENOPROTOOPT
35
34
  # It may not be supported by the protocol (e.g. UDP). ¯\_(ツ)_/¯
36
35
  end
@@ -39,50 +38,30 @@ module IO::Endpoint
39
38
  raise NotImplementedError
40
39
  end
41
40
 
42
- # Build and wrap the underlying io.
43
- # @option reuse_port [Boolean] Allow this port to be bound in multiple processes.
44
- # @option reuse_address [Boolean] Allow this port to be bound in multiple processes.
45
- # @option linger [Boolean] Wait for data to be sent before closing the socket.
46
- # @option buffered [Boolean] Enable or disable Nagle's algorithm for TCP sockets.
47
- def build(*arguments, timeout: nil, reuse_address: true, reuse_port: nil, linger: nil, buffered: false)
48
- socket = ::Socket.new(*arguments)
49
-
50
- # Set the timeout:
51
- if timeout
52
- set_timeout(socket, timeout)
53
- end
54
-
55
- if reuse_address
56
- socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
57
- end
58
-
59
- if reuse_port
60
- socket.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1)
61
- end
62
-
63
- if linger
64
- socket.setsockopt(SOL_SOCKET, SO_LINGER, 1)
65
- end
66
-
67
- if buffered == false
68
- set_buffered(socket, buffered)
69
- end
70
-
71
- yield socket if block_given?
72
-
73
- return socket
74
- rescue
75
- socket&.close
76
- raise
77
- end
78
-
79
41
  # Establish a connection to a given `remote_address`.
80
42
  # @example
81
43
  # socket = Async::IO::Socket.connect(Async::IO::Address.tcp("8.8.8.8", 53))
82
- # @param remote_address [Address] The remote address to connect to.
83
- # @option local_address [Address] The local address to bind to before connecting.
84
- def connect(remote_address, local_address: nil, **options)
85
- socket = build(remote_address.afamily, remote_address.socktype, remote_address.protocol, **options) do |socket|
44
+ # @parameter remote_address [Address] The remote address to connect to.
45
+ # @parameter linger [Boolean] Wait for data to be sent before closing the socket.
46
+ # @parameter local_address [Address] The local address to bind to before connecting.
47
+ def connect(remote_address, local_address: nil, linger: nil, timeout: nil, buffered: false, **options)
48
+ socket = nil
49
+
50
+ begin
51
+ socket = ::Socket.new(remote_address.afamily, remote_address.socktype, remote_address.protocol)
52
+
53
+ if linger
54
+ socket.setsockopt(SOL_SOCKET, SO_LINGER, 1)
55
+ end
56
+
57
+ if buffered == false
58
+ set_buffered(socket, buffered)
59
+ end
60
+
61
+ if timeout
62
+ set_timeout(socket, timeout)
63
+ end
64
+
86
65
  if local_address
87
66
  if defined?(IP_BIND_ADDRESS_NO_PORT)
88
67
  # 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.
@@ -91,6 +70,9 @@ module IO::Endpoint
91
70
 
92
71
  socket.bind(local_address.to_sockaddr)
93
72
  end
73
+ rescue
74
+ socket&.close
75
+ raise
94
76
  end
95
77
 
96
78
  begin
@@ -109,14 +91,48 @@ module IO::Endpoint
109
91
  end
110
92
  end
111
93
 
94
+ # JRuby requires ServerSocket
95
+ if defined?(::ServerSocket)
96
+ ServerSocket = ::ServerSocket
97
+ else
98
+ ServerSocket = ::Socket
99
+ end
100
+
112
101
  # Bind to a local address.
113
102
  # @example
114
103
  # socket = Async::IO::Socket.bind(Async::IO::Address.tcp("0.0.0.0", 9090))
115
- # @param local_address [Address] The local address to bind to.
116
- # @option protocol [Integer] The socket protocol to use.
117
- def bind(local_address, protocol: 0, **options, &block)
118
- socket = build(local_address.afamily, local_address.socktype, protocol, **options) do |socket|
104
+ # @parameter local_address [Address] The local address to bind to.
105
+ # @parameter reuse_port [Boolean] Allow this port to be bound in multiple processes.
106
+ # @parameter reuse_address [Boolean] Allow this port to be bound in multiple processes.
107
+ # @parameter linger [Boolean] Wait for data to be sent before closing the socket.
108
+ # @parameter protocol [Integer] The socket protocol to use.
109
+ def bind(local_address, protocol: 0, reuse_address: true, reuse_port: nil, linger: nil, bound_timeout: nil, **options, &block)
110
+ socket = nil
111
+
112
+ begin
113
+ socket = ServerSocket.new(local_address.afamily, local_address.socktype, protocol)
114
+
115
+ if reuse_address
116
+ socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
117
+ end
118
+
119
+ if reuse_port
120
+ socket.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1)
121
+ end
122
+
123
+ if linger
124
+ socket.setsockopt(SOL_SOCKET, SO_LINGER, 1)
125
+ end
126
+
127
+ # Set the timeout:
128
+ if bound_timeout
129
+ set_timeout(socket, bound_timeout)
130
+ end
131
+
119
132
  socket.bind(local_address.to_sockaddr)
133
+ rescue
134
+ socket&.close
135
+ raise
120
136
  end
121
137
 
122
138
  return socket unless block_given?
@@ -131,14 +147,14 @@ module IO::Endpoint
131
147
  end
132
148
 
133
149
  # Bind to a local address and accept connections in a loop.
134
- def accept(*arguments, backlog: SOMAXCONN, **options, &block)
135
- bind(*arguments, **options) do |server|
136
- server.listen(backlog) if backlog
150
+ def accept(server, timeout: nil, &block)
151
+ while true
152
+ socket, address = server.accept
153
+
154
+ set_timeout(socket, timeout) if timeout != false
137
155
 
138
156
  async do
139
- while true
140
- server.accept(&block)
141
- end
157
+ yield socket, address
142
158
  end
143
159
  end
144
160
  end
@@ -146,20 +162,26 @@ module IO::Endpoint
146
162
 
147
163
  class ThreadWrapper < Wrapper
148
164
  def async(&block)
149
- Thread.new(&block)
165
+ ::Thread.new(&block)
150
166
  end
151
167
  end
152
168
 
153
169
  class FiberWrapper < Wrapper
154
170
  def async(&block)
155
- Fiber.schedule(&block)
171
+ ::Fiber.schedule(&block)
156
172
  end
157
173
  end
158
174
 
159
- def Wrapper.default
160
- if Fiber.scheduler
161
- FiberWrapper.new
162
- else
175
+ if Fiber.respond_to?(:scheduler)
176
+ def Wrapper.default
177
+ if Fiber.scheduler
178
+ FiberWrapper.new
179
+ else
180
+ ThreadWrapper.new
181
+ end
182
+ end
183
+ else
184
+ def Wrapper.default
163
185
  ThreadWrapper.new
164
186
  end
165
187
  end
data/lib/io/endpoint.rb CHANGED
@@ -5,6 +5,7 @@
5
5
 
6
6
  require_relative "endpoint/version"
7
7
  require_relative "endpoint/generic"
8
+ require_relative "endpoint/shared_endpoint"
8
9
 
9
10
  module IO::Endpoint
10
11
  def self.file_descriptor_limit
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: io-endpoint
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -37,7 +37,7 @@ cert_chain:
37
37
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
38
38
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
39
39
  -----END CERTIFICATE-----
40
- date: 2023-12-28 00:00:00.000000000 Z
40
+ date: 2024-01-25 00:00:00.000000000 Z
41
41
  dependencies: []
42
42
  description:
43
43
  email:
@@ -45,9 +45,12 @@ executables: []
45
45
  extensions: []
46
46
  extra_rdoc_files: []
47
47
  files:
48
+ - lib/io/connected.rb
48
49
  - lib/io/endpoint.rb
49
50
  - lib/io/endpoint/address_endpoint.rb
51
+ - lib/io/endpoint/bound_endpoint.rb
50
52
  - lib/io/endpoint/composite_endpoint.rb
53
+ - lib/io/endpoint/connected_endpoint.rb
51
54
  - lib/io/endpoint/generic.rb
52
55
  - lib/io/endpoint/host_endpoint.rb
53
56
  - lib/io/endpoint/shared_endpoint.rb
metadata.gz.sig CHANGED
Binary file