io-endpoint 0.1.0 → 0.2.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: 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