io-endpoint 0.1.0 → 0.2.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: c10e7e5b154e1ad990982315ba03565d5e880f392eb404a696c63eb3c9240cb7
4
- data.tar.gz: 37ebf4a7b0c0d7905c0e4b9d23373e1665f595637ab049163dafab7d7c9df535
3
+ metadata.gz: cbc52bab1fde2330c0b971cf987db15dc5865f9f3ce72a130121db97d00671ba
4
+ data.tar.gz: a7503b553781fd02e86a082015e82f1c5af24b5fa3a735dbdeb7415498901da8
5
5
  SHA512:
6
- metadata.gz: f8446d2169e82a34b836edaaf76b4e13409202dc1f0cebf64c80f693728bfe4dcd133dddd2ee7fd932bbc13b45e3d37b8ab2a7b928b3a39629455cd1fd9e62bf
7
- data.tar.gz: 0ca66dd6bffc9925e7e782d46234b61777d432df527b8595ed10d26655a2f4d30a0eb74e5cff54ae5ec31b21a3e94af79eb1609c546d9c2b552817937ef47adc
6
+ metadata.gz: 40343e43190bd695c0152fb5ec45946efe281245eb033eacae1eeda9291c37b4bc975dffac6db9d007b060bf9d375cdf3beed31f70a81f344c65f17b6772bff4
7
+ data.tar.gz: 40a34915f55409d96fddfaea9925c71c5bcddee36c0f6a6cc001982ec1e884a7694a3f4d89aad534353df3c182eb9b94f618619fdec9a77fc36d723620b5af09
checksums.yaml.gz.sig CHANGED
Binary file
@@ -17,7 +17,7 @@ module IO::Endpoint
17
17
  end
18
18
 
19
19
  def to_s
20
- "\#<#{self.class} #{@address.inspect}>"
20
+ "\#<#{self.class} address=#{@address.inspect}>"
21
21
  end
22
22
 
23
23
  attr :address
@@ -34,9 +34,7 @@ module IO::Endpoint
34
34
  end
35
35
  end
36
36
 
37
- class Endpoint
38
- def self.composite(*endpoints, **options)
39
- CompositeEndpoint.new(endpoints, **options)
40
- end
37
+ def self.composite(*endpoints, **options)
38
+ CompositeEndpoint.new(endpoints, **options)
41
39
  end
42
40
  end
@@ -83,23 +83,6 @@ module IO::Endpoint
83
83
  end
84
84
  end
85
85
 
86
- # Map all endpoints by invoking `#bind`.
87
- # @yield the bound wrapper.
88
- def bound
89
- wrappers = []
90
-
91
- self.each do |endpoint|
92
- wrapper = endpoint.bind
93
- wrappers << wrapper
94
-
95
- yield wrapper
96
- end
97
-
98
- return wrappers
99
- ensure
100
- wrappers.each(&:close) if $!
101
- end
102
-
103
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.
104
87
  #
105
88
  # @param string [String] URI as string. Scheme will decide implementation used.
@@ -19,9 +19,8 @@ module IO::Endpoint
19
19
  "\#<#{self.class} name=#{nodename.inspect} service=#{service.inspect} family=#{family.inspect} type=#{socktype.inspect} protocol=#{protocol.inspect} flags=#{flags.inspect}>"
20
20
  end
21
21
 
22
- def address
23
- @specification
24
- end
22
+
23
+ attr :specification
25
24
 
26
25
  def hostname
27
26
  @specification.first
@@ -31,13 +30,14 @@ module IO::Endpoint
31
30
  # @yield [Socket] the socket which is being connected, may be invoked more than once
32
31
  # @return [Socket] the connected socket
33
32
  # @raise if no connection could complete successfully
34
- def connect
33
+ def connect(wrapper = Wrapper.default, &block)
35
34
  last_error = nil
36
35
 
37
36
  Addrinfo.foreach(*@specification) do |address|
38
37
  begin
39
- socket = Socket.connect(address, **@options)
38
+ socket = wrapper.connect(@address, **@options)
40
39
  rescue Errno::ECONNREFUSED, Errno::ENETUNREACH, Errno::EAGAIN => last_error
40
+ # Try again unless if possible, otherwise raise...
41
41
  else
42
42
  return socket unless block_given?
43
43
 
@@ -55,9 +55,9 @@ module IO::Endpoint
55
55
  # Invokes the given block for every address which can be bound to.
56
56
  # @yield [Socket] the bound socket
57
57
  # @return [Array<Socket>] an array of bound sockets
58
- def bind(&block)
58
+ def bind(wrapper = Wrapper.default, &block)
59
59
  Addrinfo.foreach(*@specification).map do |address|
60
- Socket.bind(address, **@options, &block)
60
+ wrapper.bind(address, **@options, &block)
61
61
  end
62
62
  end
63
63
 
@@ -71,23 +71,23 @@ module IO::Endpoint
71
71
  end
72
72
  end
73
73
 
74
- # @param args nodename, service, family, socktype, protocol, flags. `socktype` will be set to Socket::SOCK_STREAM.
74
+ # @param arguments nodename, service, family, socktype, protocol, flags. `socktype` will be set to Socket::SOCK_STREAM.
75
75
  # @param options keyword arguments passed on to {HostEndpoint#initialize}
76
76
  #
77
77
  # @return [HostEndpoint]
78
- def self.tcp(*args, **options)
79
- args[3] = ::Socket::SOCK_STREAM
78
+ def self.tcp(*arguments, **options)
79
+ arguments[3] = ::Socket::SOCK_STREAM
80
80
 
81
- HostEndpoint.new(args, **options)
81
+ HostEndpoint.new(arguments, **options)
82
82
  end
83
83
 
84
- # @param args nodename, service, family, socktype, protocol, flags. `socktype` will be set to Socket::SOCK_DGRAM.
84
+ # @param arguments nodename, service, family, socktype, protocol, flags. `socktype` will be set to Socket::SOCK_DGRAM.
85
85
  # @param options keyword arguments passed on to {HostEndpoint#initialize}
86
86
  #
87
87
  # @return [HostEndpoint]
88
- def self.udp(*args, **options)
89
- args[3] = ::Socket::SOCK_DGRAM
88
+ def self.udp(*arguments, **options)
89
+ arguments[3] = ::Socket::SOCK_DGRAM
90
90
 
91
- HostEndpoint.new(args, **options)
91
+ HostEndpoint.new(arguments, **options)
92
92
  end
93
93
  end
@@ -5,13 +5,18 @@
5
5
 
6
6
  require_relative 'generic'
7
7
  require_relative 'composite_endpoint'
8
+ require_relative 'socket_endpoint'
8
9
 
9
10
  module IO::Endpoint
10
11
  # Pre-connect and pre-bind sockets so that it can be used between processes.
11
12
  class SharedEndpoint < Generic
12
13
  # Create a new `SharedEndpoint` by binding to the given endpoint.
13
- def self.bound(endpoint, backlog: Socket::SOMAXCONN, close_on_exec: false)
14
- wrappers = endpoint.bound do |server|
14
+ def self.bound(endpoint, backlog: Socket::SOMAXCONN, close_on_exec: false, **options)
15
+ sockets = []
16
+
17
+ endpoint.each do |server_endpoint|
18
+ server = server_endpoint.bind(**options)
19
+
15
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.
16
21
  begin
17
22
  server.listen(backlog)
@@ -20,9 +25,11 @@ module IO::Endpoint
20
25
  end
21
26
 
22
27
  server.close_on_exec = close_on_exec
28
+
29
+ sockets << server
23
30
  end
24
31
 
25
- return self.new(endpoint, wrappers)
32
+ return self.new(endpoint, sockets)
26
33
  end
27
34
 
28
35
  # Create a new `SharedEndpoint` by connecting to the given endpoint.
@@ -34,18 +41,20 @@ module IO::Endpoint
34
41
  return self.new(endpoint, [wrapper])
35
42
  end
36
43
 
37
- def initialize(endpoint, wrappers, **options)
44
+ def initialize(endpoint, sockets, **options)
38
45
  super(**options)
39
46
 
47
+ raise TypeError, "sockets must be an Array" unless sockets.is_a?(Array)
48
+
40
49
  @endpoint = endpoint
41
- @wrappers = wrappers
50
+ @sockets = sockets
42
51
  end
43
52
 
44
53
  attr :endpoint
45
- attr :wrappers
54
+ attr :sockets
46
55
 
47
56
  def local_address_endpoint(**options)
48
- endpoints = @wrappers.map do |wrapper|
57
+ endpoints = @sockets.map do |wrapper|
49
58
  AddressEndpoint.new(wrapper.to_io.local_address)
50
59
  end
51
60
 
@@ -53,51 +62,77 @@ module IO::Endpoint
53
62
  end
54
63
 
55
64
  def remote_address_endpoint(**options)
56
- endpoints = @wrappers.map do |wrapper|
65
+ endpoints = @sockets.map do |wrapper|
57
66
  AddressEndpoint.new(wrapper.to_io.remote_address)
58
67
  end
59
68
 
60
69
  return CompositeEndpoint.new(endpoints, **options)
61
70
  end
62
71
 
63
- # Close all the internal wrappers.
72
+ # Close all the internal sockets.
64
73
  def close
65
- @wrappers.each(&:close)
66
- @wrappers.clear
74
+ @sockets.each(&:close)
75
+ @sockets.clear
76
+ end
77
+
78
+ def each(&block)
79
+ return to_enum unless block_given?
80
+
81
+ @sockets.each do |socket|
82
+ yield SocketEndpoint.new(socket.dup)
83
+ end
67
84
  end
68
85
 
69
- def bind
70
- @wrappers.each do |server|
86
+ def bind(wrapper = Wrapper.default, &block)
87
+ @sockets.each.map do |server|
71
88
  server = server.dup
72
89
 
73
- begin
74
- yield server
75
- ensure
76
- server.close
90
+ if block_given?
91
+ wrapper.async do
92
+ begin
93
+ yield server
94
+ ensure
95
+ server.close
96
+ end
97
+ end
98
+ else
99
+ server
77
100
  end
78
101
  end
79
102
  end
80
103
 
81
- def connect
82
- @wrappers.each do |peer|
83
- peer = peer.dup
104
+ def connect(wrapper = Wrapper.default, &block)
105
+ @sockets.each do |socket|
106
+ socket = socket.dup
107
+
108
+ return socket unless block_given?
84
109
 
85
110
  begin
86
- yield peer
111
+ return yield(socket)
87
112
  ensure
88
- peer.close
113
+ socket.close
89
114
  end
90
115
  end
91
116
  end
92
117
 
93
- def accept(backlog = nil, &block)
118
+ def accept(**options, &block)
94
119
  bind do |server|
95
- server.accept_each(&block)
120
+ server.accept(&block)
96
121
  end
97
122
  end
98
123
 
99
124
  def to_s
100
- "\#<#{self.class} #{@wrappers.size} descriptors for #{@endpoint}>"
125
+ "\#<#{self.class} #{@sockets.size} descriptors for #{@endpoint}>"
126
+ end
127
+ end
128
+
129
+ class Generic
130
+ def bound(**options)
131
+ SharedEndpoint.bound(self, **options)
132
+ end
133
+
134
+ def connected(**options)
135
+ SharedEndpoint.connected(self, **options)
101
136
  end
102
137
  end
103
138
  end
@@ -111,13 +111,13 @@ module IO::Endpoint
111
111
  end
112
112
  end
113
113
 
114
- # @param args
114
+ # @param arguments
115
115
  # @param ssl_context [OpenSSL::SSL::SSLContext, nil]
116
116
  # @param hostname [String, nil]
117
117
  # @param options keyword arguments passed through to {Endpoint.tcp}
118
118
  #
119
119
  # @return [SSLEndpoint]
120
- def self.ssl(*args, ssl_context: nil, hostname: nil, **options)
121
- SSLEndpoint.new(self.tcp(*args, **options), ssl_context: ssl_context, hostname: hostname)
120
+ def self.ssl(*arguments, ssl_context: nil, hostname: nil, **options)
121
+ SSLEndpoint.new(self.tcp(*arguments, **options), ssl_context: ssl_context, hostname: hostname)
122
122
  end
123
123
  end
@@ -27,14 +27,16 @@ module IO::Endpoint
27
27
  end
28
28
  rescue Errno::ECONNREFUSED
29
29
  return false
30
+ rescue Errno::ENOENT
31
+ return false
30
32
  end
31
33
 
32
34
  def bind(&block)
33
35
  super
34
36
  rescue Errno::EADDRINUSE
35
37
  # If you encounter EADDRINUSE from `bind()`, you can check if the socket is actually accepting connections by attempting to `connect()` to it. If the socket is still bound by an active process, the connection will succeed. Otherwise, it should be safe to `unlink()` the path and try again.
36
- if !bound? && File.exist?(@path)
37
- File.unlink(@path)
38
+ if !bound?
39
+ File.unlink(@path) rescue nil
38
40
  retry
39
41
  else
40
42
  raise
@@ -5,6 +5,6 @@
5
5
 
6
6
  class IO
7
7
  module Endpoint
8
- VERSION = "0.1.0"
8
+ VERSION = "0.2.0"
9
9
  end
10
10
  end
@@ -9,16 +9,32 @@ module IO::Endpoint
9
9
  class Wrapper
10
10
  include ::Socket::Constants
11
11
 
12
- if $stdin.respond_to?(:timeout=)
13
- def self.set_timeout(io, timeout)
12
+ if IO.method_defined?(:timeout=)
13
+ def set_timeout(io, timeout)
14
14
  io.timeout = timeout
15
15
  end
16
16
  else
17
- def self.set_timeout(io, timeout)
17
+ def set_timeout(io, timeout)
18
18
  warn "IO#timeout= not supported on this platform."
19
19
  end
20
20
  end
21
21
 
22
+ def set_buffered(socket, buffered)
23
+ case buffered
24
+ when true
25
+ socket.setsockopt(IPPROTO_TCP, TCP_NODELAY, 0)
26
+ when false
27
+ socket.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1)
28
+ end
29
+ rescue Errno::EINVAL
30
+ # On Darwin, sometimes occurs when the connection is not yet fully formed. Empirically, TCP_NODELAY is enabled despite this result.
31
+ rescue Errno::EOPNOTSUPP
32
+ # Some platforms may simply not support the operation.
33
+ # Console.logger.warn(self) {"Unable to set sync=#{value}!"}
34
+ rescue Errno::ENOPROTOOPT
35
+ # It may not be supported by the protocol (e.g. UDP). ¯\_(ツ)_/¯
36
+ end
37
+
22
38
  def async
23
39
  raise NotImplementedError
24
40
  end
@@ -26,7 +42,9 @@ module IO::Endpoint
26
42
  # Build and wrap the underlying io.
27
43
  # @option reuse_port [Boolean] Allow this port to be bound in multiple processes.
28
44
  # @option reuse_address [Boolean] Allow this port to be bound in multiple processes.
29
- def build(*arguments, timeout: nil, reuse_address: true, reuse_port: nil, linger: nil)
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)
30
48
  socket = ::Socket.new(*arguments)
31
49
 
32
50
  # Set the timeout:
@@ -43,7 +61,11 @@ module IO::Endpoint
43
61
  end
44
62
 
45
63
  if linger
46
- socket.setsockopt(SOL_SOCKET, SO_LINGER, linger)
64
+ socket.setsockopt(SOL_SOCKET, SO_LINGER, 1)
65
+ end
66
+
67
+ if buffered == false
68
+ set_buffered(socket, buffered)
47
69
  end
48
70
 
49
71
  yield socket if block_given?
@@ -51,6 +73,7 @@ module IO::Endpoint
51
73
  return socket
52
74
  rescue
53
75
  socket&.close
76
+ raise
54
77
  end
55
78
 
56
79
  # Establish a connection to a given `remote_address`.
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.1.0
4
+ version: 0.2.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-06-15 00:00:00.000000000 Z
40
+ date: 2023-12-16 00:00:00.000000000 Z
41
41
  dependencies:
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: bake
@@ -120,7 +120,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
120
120
  - !ruby/object:Gem::Version
121
121
  version: '0'
122
122
  requirements: []
123
- rubygems_version: 3.4.7
123
+ rubygems_version: 3.4.22
124
124
  signing_key:
125
125
  specification_version: 4
126
126
  summary: Provides a separation of concerns interface for IO endpoints.
metadata.gz.sig CHANGED
Binary file