io-endpoint 0.3.0 → 0.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: 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