socketry 0.3.0 → 0.4.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
  SHA1:
3
- metadata.gz: c144331b5b09954959008e255335d5ac43c70be3
4
- data.tar.gz: 704cb38c89f0cc0779aaeba5859877f189f5664c
3
+ metadata.gz: ca5c2f89cca095c9a9512b02c3dff7d7af615af2
4
+ data.tar.gz: 5d4ccc55cb34dcea0bb63986882c52195a1dabc3
5
5
  SHA512:
6
- metadata.gz: f49d7c5ee328c0c6b7f6366aca43d147a56045b1b1f3dca05219caef2216e791454585e512a7195f61dc1fb767230a2dde82f5130e1a174474d26b6ad0fbbabb
7
- data.tar.gz: 7147ec4e25e1d4af36d27452be851adbec957ce65747f95ce873e91936ec962b3576d62b0f171304079dad998030289874a55267682c7842debb7fe7d7ac0413
6
+ metadata.gz: c7cfa8860c9e4aa4f0685d8d707f433d1d98ecdf56bad86146d8347b5480492ad51ac03e1d18035899e1ac2457f64b77f0822b91a7ca8e24af85a0262ffe1a27
7
+ data.tar.gz: b63f66204999a96fe018d168d9448de66d1ad97b1793c145af5b41cd4a04e806fce9cf1d84901d99872ffbf0ec9cbbd0cafded0777db4ca14e9a5ea904fff3d9
@@ -1 +1 @@
1
- 2.3.1
1
+ 2.3.3
@@ -5,8 +5,8 @@ bundler_args: --without development doc
5
5
 
6
6
  rvm:
7
7
  - 2.2
8
- - 2.3.1
9
- - jruby-9.0.5.0
8
+ - 2.3.3
9
+ - jruby-9.1.6.0
10
10
 
11
11
  matrix:
12
12
  fast_finish: true
data/CHANGES.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## 0.4.0 (2016-11-25)
2
+
3
+ * Specs and bugfixes for SSL sockets
4
+ * Specs and bugfixes for UDP sockets
5
+ * Add Socketry::UDP::Datagram class
6
+ * Add Socketry::AddressInUseError exception
7
+
1
8
  ## 0.3.0 (2016-09-24)
2
9
 
3
10
  * Implement Socketry::TCP::Socket#read and #write
data/README.md CHANGED
@@ -11,13 +11,9 @@
11
11
  [license-image]: https://img.shields.io/badge/license-MIT-blue.svg
12
12
  [license-link]: https://github.com/socketry/socketry/blob/master/LICENSE.txt
13
13
 
14
- High-level wrappers for Ruby sockets with advanced thread-safe timeout support.
14
+ High-level Ruby socket library with support for TCP, UDP, and SSL sockets.
15
15
 
16
- **Does not require Celluloid!** Socketry provides sockets with thread-safe
17
- timeout support that can be used with any multithreaded Ruby app. That said,
18
- Socketry can also be used to provide asynchronous I/O with [Celluloid::IO].
19
-
20
- [Celluloid::IO]: https://github.com/celluloid/celluloid-io
16
+ Implements thread-safe timeouts using asynchronous I/O and high-precision monotonic timers.
21
17
 
22
18
  ## Motivation
23
19
 
@@ -21,4 +21,5 @@ require "socketry/tcp/server"
21
21
  require "socketry/tcp/socket"
22
22
  require "socketry/ssl/server"
23
23
  require "socketry/ssl/socket"
24
+ require "socketry/udp/datagram"
24
25
  require "socketry/udp/socket"
@@ -10,6 +10,9 @@ module Socketry
10
10
  # Invalid address
11
11
  AddressError = Class.new(Socketry::Error)
12
12
 
13
+ # Address is already in use
14
+ AddressInUseError = Class.new(Socketry::Error)
15
+
13
16
  # Timeouts performing an I/O operation
14
17
  TimeoutError = Class.new(Socketry::Error)
15
18
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "resolv"
4
+
3
5
  module Socketry
4
6
  module Resolver
5
7
  # Pure Ruby DNS resolver provided by the standard library
@@ -26,9 +26,10 @@ module Socketry
26
26
  raise TypeError, "expected Hash, got #{ssl_params.class}" if ssl_params && !ssl_params.is_a?(Hash)
27
27
 
28
28
  @ssl_socket_class = ssl_socket_class
29
+
29
30
  @ssl_context = ssl_context
30
31
  @ssl_context.set_params(ssl_params) if ssl_params && !ssl_params.empty?
31
- @ssl_context.freeze
32
+
32
33
  @ssl_socket = nil
33
34
 
34
35
  super(**args)
@@ -41,7 +42,8 @@ module Socketry
41
42
  # @param local_addr [String] DNS name or IP address to bind to locally
42
43
  # @param local_port [Fixnum] Local TCP port to bind to
43
44
  # @param timeout [Numeric] Number of seconds to wait before aborting connect
44
- # @param socket_class [Class] Custom low-level socket class
45
+ # @param enable_sni [true, false] (default: true) Enables Server Name Indication (SNI)
46
+ # @param verify_hostname [true, false] (default: true) Ensure server's hostname matches cert
45
47
  # @raise [Socketry::AddressError] an invalid address was given
46
48
  # @raise [Socketry::TimeoutError] connect operation timed out
47
49
  # @raise [Socketry::SSL::Error] an error occurred negotiating an SSL connection
@@ -52,12 +54,13 @@ module Socketry
52
54
  local_addr: nil,
53
55
  local_port: nil,
54
56
  timeout: Socketry::Timeout::DEFAULT_TIMEOUTS[:connect],
57
+ enable_sni: true,
55
58
  verify_hostname: true
56
59
  )
57
60
  super(remote_addr, remote_port, local_addr: local_addr, local_port: local_port, timeout: timeout)
58
61
 
59
62
  @ssl_socket = OpenSSL::SSL::SSLSocket.new(@socket, @ssl_context)
60
- @ssl_socket.hostname = remote_addr
63
+ @ssl_socket.hostname = remote_addr if enable_sni
61
64
 
62
65
  begin
63
66
  @ssl_socket.connect_nonblock
@@ -110,23 +113,13 @@ module Socketry
110
113
  # @raise [Socketry::Error] an I/O operation failed
111
114
  # @return [String, :wait_readable] data read, or :wait_readable if operation would block
112
115
  def read_nonblock(size, outbuf: nil)
113
- ensure_connected
114
116
  case outbuf
115
117
  when String
116
- @ssl_socket.read_nonblock(size, outbuf, exception: false)
118
+ perform { @ssl_socket.read_nonblock(size, outbuf, exception: false) }
117
119
  when NilClass
118
- @ssl_socket.read_nonblock(size, exception: false)
120
+ perform { @ssl_socket.read_nonblock(size, exception: false) }
119
121
  else raise TypeError, "unexpected outbuf class: #{outbuf.class}"
120
122
  end
121
- # Some buggy Rubies continue to raise exceptions in these cases
122
- rescue IO::WaitReadable
123
- :wait_readable
124
- # Due to SSL, we may need to write to complete a read (e.g. renegotiation)
125
- rescue IO::WaitWritable
126
- :wait_writable
127
- rescue => ex
128
- # TODO: more specific exceptions
129
- raise Socketry::Error, ex.message, ex.backtrace
130
123
  end
131
124
 
132
125
  # Perform a non-blocking write operation
@@ -135,26 +128,33 @@ module Socketry
135
128
  # @raise [Socketry::Error] an I/O operation failed
136
129
  # @return [Fixnum, :wait_writable] number of bytes written, or :wait_writable if op would block
137
130
  def write_nonblock(data)
131
+ perform { @ssl_socket.write_nonblock(data, exception: false) }
132
+ end
133
+
134
+ # Close the socket
135
+ #
136
+ # @return [true, false] true if the socket was open, false if closed
137
+ def close
138
+ @ssl_socket.close rescue nil
139
+ super
140
+ end
141
+
142
+ private
143
+
144
+ # Perform a non-blocking I/O operation
145
+ def perform
138
146
  ensure_connected
139
- @ssl_socket.write_nonblock(data, exception: false)
147
+ yield
140
148
  # Some buggy Rubies continue to raise this exception
141
- rescue IO::WaitWriteable
149
+ rescue IO::WaitWritable
142
150
  :wait_writable
143
- # Due to SSL, we may need to write to complete a read (e.g. renegotiation)
151
+ # Due to SSL, we may need to write to complete a read (e.g. handshaking, renegotiation)
144
152
  rescue IO::WaitReadable
145
153
  :wait_readable
146
154
  rescue => ex
147
155
  # TODO: more specific exceptions
148
156
  raise Socketry::Error, ex.message, ex.backtrace
149
157
  end
150
-
151
- # Close the socket
152
- #
153
- # @return [true, false] true if the socket was open, false if closed
154
- def close
155
- @ssl_socket.close
156
- super
157
- end
158
158
  end
159
159
  end
160
160
  end
@@ -43,6 +43,8 @@ module Socketry
43
43
  end
44
44
 
45
45
  start_timer(timer)
46
+ rescue Errno::EADDRINUSE => ex
47
+ raise AddressInUseError, ex.message, ex.backtrace
46
48
  end
47
49
 
48
50
  # Accept a connection to the server
@@ -7,7 +7,7 @@ module Socketry
7
7
  class Socket
8
8
  include Socketry::Timeout
9
9
 
10
- attr_reader :remote_addr, :remote_port, :local_addr, :local_port
10
+ attr_reader :addr_fmaily, :remote_addr, :remote_port, :local_addr, :local_port
11
11
  attr_reader :read_timeout, :write_timeout, :resolver, :socket_class
12
12
 
13
13
  # Create a Socketry::TCP::Socket with the default options, then connect
@@ -15,6 +15,7 @@ module Socketry
15
15
  #
16
16
  # @param remote_addr [String] DNS name or IP address of the host to connect to
17
17
  # @param remote_port [Fixnum] TCP port to connect to
18
+ #
18
19
  # @return [Socketry::TCP::Socket]
19
20
  def self.connect(remote_addr, remote_port, **args)
20
21
  new.connect(remote_addr, remote_port, **args)
@@ -27,6 +28,7 @@ module Socketry
27
28
  # @param timer [Object] A timekeeping object to use for measuring timeouts
28
29
  # @param resolver [Object] A resolver object to use for resolving DNS names
29
30
  # @param socket_class [Object] Underlying socket class which implements I/O ops
31
+ #
30
32
  # @return [Socketry::TCP::Socket]
31
33
  def initialize(
32
34
  read_timeout: Socketry::Timeout::DEFAULT_TIMEOUTS[:read],
@@ -41,7 +43,7 @@ module Socketry
41
43
  @socket_class = socket_class
42
44
  @resolver = resolver
43
45
 
44
- @family = nil
46
+ @addr_family = nil
45
47
  @socket = nil
46
48
 
47
49
  @remote_addr = nil
@@ -59,9 +61,10 @@ module Socketry
59
61
  # @param local_addr [String] DNS name or IP address to bind to locally
60
62
  # @param local_port [Fixnum] Local TCP port to bind to
61
63
  # @param timeout [Numeric] Number of seconds to wait before aborting connect
62
- # @param socket_class [Class] Custom low-level socket class
64
+ #
63
65
  # @raise [Socketry::AddressError] an invalid address was given
64
66
  # @raise [Socketry::TimeoutError] connect operation timed out
67
+ #
65
68
  # @return [self]
66
69
  def connect(
67
70
  remote_addr,
@@ -84,14 +87,12 @@ module Socketry
84
87
  local_addr = @resolver.resolve(local_addr, timeout: time_remaining(timeout)) if local_addr
85
88
  raise ArgumentError, "expected IPAddr from resolver, got #{remote_addr.class}" unless remote_addr.is_a?(IPAddr)
86
89
 
87
- if remote_addr.ipv4?
88
- @family = ::Socket::AF_INET
89
- elsif remote_addr.ipv6?
90
- @family = ::Socket::AF_INET6
91
- else raise Socketry::AddressError, "unsupported IP address family: #{remote_addr}"
92
- end
90
+ @addr_family = if remote_addr.ipv4? then ::Socket::AF_INET
91
+ elsif remote_addr.ipv6? then ::Socket::AF_INET6
92
+ else raise Socketry::AddressError, "unsupported IP address family: #{remote_addr}"
93
+ end
93
94
 
94
- socket = @socket_class.new(@family, ::Socket::SOCK_STREAM, 0)
95
+ socket = @socket_class.new(@addr_family, ::Socket::SOCK_STREAM, 0)
95
96
  socket.bind Addrinfo.tcp(local_addr.to_s, local_port) if local_addr
96
97
  remote_sockaddr = ::Socket.sockaddr_in(remote_port, remote_addr.to_s)
97
98
 
@@ -143,7 +144,9 @@ module Socketry
143
144
  #
144
145
  # @param size [Fixnum] number of bytes to attempt to read
145
146
  # @param outbuf [String, NilClass] an optional buffer into which data should be read
147
+ #
146
148
  # @raise [Socketry::Error] an I/O operation failed
149
+ #
147
150
  # @return [String, :wait_readable] data read, or :wait_readable if operation would block
148
151
  def read_nonblock(size, outbuf: nil)
149
152
  ensure_connected
@@ -188,7 +191,9 @@ module Socketry
188
191
  # @param size [Fixnum] number of bytes to attempt to read
189
192
  # @param outbuf [String] an output buffer to read data into
190
193
  # @param timeout [Numeric] Number of seconds to wait for read operation to complete
194
+ #
191
195
  # @raise [Socketry::Error] an I/O operation failed
196
+ #
192
197
  # @return [String, :eof] bytes read, or :eof if socket closed while reading
193
198
  def read(size, outbuf: String.new, timeout: @write_timeout)
194
199
  outbuf.clear
@@ -212,7 +217,9 @@ module Socketry
212
217
  # Perform a non-blocking write operation
213
218
  #
214
219
  # @param data [String] data to write to the socket
220
+ #
215
221
  # @raise [Socketry::Error] an I/O operation failed
222
+ #
216
223
  # @return [Fixnum, :wait_writable] number of bytes written, or :wait_writable if op would block
217
224
  def write_nonblock(data)
218
225
  ensure_connected
@@ -249,7 +256,9 @@ module Socketry
249
256
  #
250
257
  # @param data [String] data to write to the socket
251
258
  # @param timeout [Numeric] Number of seconds to wait for write operation to complete
259
+ #
252
260
  # @raise [Socketry::Error] an I/O operation failed
261
+ #
253
262
  # @return [Fixnum] number of bytes written, or :eof if socket closed during writing
254
263
  def write(data, timeout: @write_timeout)
255
264
  total_written = data.size
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Socketry
4
+ # User Datagram Protocol: "fire-and-forget" packet protocol
5
+ module UDP
6
+ # Represents a received UDP message
7
+ class Datagram
8
+ attr_reader :message, :sockaddr, :host, :addr, :port
9
+
10
+ def initialize(message, sockaddr)
11
+ @message = message
12
+ @sockaddr = sockaddr
13
+ @port = sockaddr[1]
14
+ @host = sockaddr[2]
15
+ @addr = sockaddr[3]
16
+ end
17
+
18
+ def addrinfo
19
+ addr_family = case @sockaddr[0]
20
+ when "AF_INET" then ::Socket::AF_INET
21
+ when "AF_INET6" then ::Socket::AF_INET6
22
+ else raise Socketry::AddressError, "unsupported IP address family: #{@sockaddr[0]}"
23
+ end
24
+
25
+ Addrinfo.new(@sockaddr, addr_family, ::Socket::SOCK_DGRAM)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -7,31 +7,39 @@ module Socketry
7
7
  class Socket
8
8
  include Socketry::Timeout
9
9
 
10
- attr_reader :read_timeout, :write_timeout, :resolver, :socket_class
10
+ attr_reader :addr_family, :read_timeout, :write_timeout, :resolver, :socket_class
11
11
 
12
12
  # Create a UDP socket matching the given socket's address family
13
13
  #
14
- # @param remote_addr [String] address to connect/bind to
14
+ # @param remote_addr [String] Address to connect/bind to
15
+ # @param resolver [Object] Resolver object to use for resolving DNS names
16
+ #
15
17
  # @return [Socketry::UDP::Socket]
16
18
  def self.from_addr(remote_addr, resolver: Socketry::Resolver::DEFAULT_RESOLVER)
17
19
  addr = resolver.resolve(remote_addr)
18
- if addr.ipv4?
19
- new(family: :ipv4)
20
- elsif addr.ipv6?
21
- new(family: :ipv6)
20
+ if addr.ipv4? then new(addr_family: :ipv4)
21
+ elsif addr.ipv6? then new(addr_family: :ipv6)
22
22
  else raise Socketry::AddressError, "unsupported IP address family: #{addr}"
23
23
  end
24
24
  end
25
25
 
26
- # Bind to the given address and port
26
+ # Create a UDP server bound to the given address and port
27
+ #
28
+ # @param local_addr [String] Local DNS name or IP address to listen on
29
+ # @param local_port [Fixnum] Local UDP port to listen on
30
+ # @param resolver [Object] Resolver object to use for resolving DNS names
27
31
  #
28
32
  # @return [Socketry::UDP::Socket]
29
- def self.bind(remote_addr, remote_port, resolver: Socketry::Resolver::DEFAULT_RESOLVER)
30
- from_addr(remote_addr, resolver: resolver).bind(remote_addr, remote_port)
33
+ def self.bind(local_addr, local_port, resolver: Socketry::Resolver::DEFAULT_RESOLVER)
34
+ from_addr(local_addr, resolver: resolver).bind(local_addr, local_port)
31
35
  end
32
36
 
33
37
  # Connect to the given address and port
34
38
  #
39
+ # @param remote_addr [String] DNS name or IP address of the host to connect to
40
+ # @param remote_port [Fixnum] UDP port to connect to
41
+ # @param resolver [Object] Resolver object to use for resolving DNS names
42
+ #
35
43
  # @return [Socketry::UDP::Socket]
36
44
  def self.connect(remote_addr, remote_port, resolver: Socketry::Resolver::DEFAULT_RESOLVER)
37
45
  from_addr(remote_addr, resolver: resolver).connect(remote_addr, remote_port)
@@ -39,26 +47,32 @@ module Socketry
39
47
 
40
48
  # Create a new UDP socket
41
49
  #
50
+ # @param addr_family [:ipv4, :ipv6] (default :ipv4) address family for this socket
51
+ # @param read_timeout [Numeric] Seconds to wait before an uncompleted read errors
52
+ # @param write_timeout [Numeric] Seconds to wait before an uncompleted write errors
53
+ # @param timer [Object] Time interval object to use for measuring timeouts
54
+ # @param resolver [Object] Resolver object to use for resolving DNS names
55
+ # @param socket_class [Object] Underlying socket class which implements I/O ops
56
+ #
57
+ # @raise [ArgumentError] an invalid argument was given
58
+ #
42
59
  # @return [Socketry::UDP::Socket]
43
60
  def initialize(
44
- family: :ipv4,
61
+ addr_family: :ipv4,
45
62
  read_timeout: Socketry::Timeout::DEFAULT_TIMEOUTS[:read],
46
63
  write_timeout: Socketry::Timeout::DEFAULT_TIMEOUTS[:write],
47
64
  timer: Socketry::Timeout::DEFAULT_TIMER.new,
48
65
  resolver: Socketry::Resolver::DEFAULT_RESOLVER,
49
66
  socket_class: ::UDPSocket
50
67
  )
51
- case family
52
- when :ipv4
53
- @address_family = ::Socket::AF_INET
54
- when :ipv6
55
- @address_family = ::Socket::AF_INET6
56
- when ::Socket::AF_INET, ::Socket::AF_INET6
57
- @address_family = address_family
58
- else raise ArgumentError, "invalid address family: #{address_family.inspect}"
59
- end
68
+ @addr_family = case addr_family
69
+ when :ipv4 then ::Socket::AF_INET
70
+ when :ipv6 then ::Socket::AF_INET6
71
+ when ::Socket::AF_INET, ::Socket::AF_INET6 then addr_family
72
+ else raise ArgumentError, "invalid address family: #{addr_family.inspect}"
73
+ end
60
74
 
61
- @socket = socket_class.new(@address_family)
75
+ @socket = socket_class.new(@addr_family)
62
76
  @read_timeout = read_timeout
63
77
  @write_timeout = write_timeout
64
78
  @resolver = resolver
@@ -66,22 +80,29 @@ module Socketry
66
80
  start_timer(timer)
67
81
  end
68
82
 
69
- # Bind to the given address and port
83
+ # Start a UDP server bound to a particular address and port
84
+ #
85
+ # @param local_addr [String] Local DNS name or IP address to listen on
86
+ # @param local_port [Fixnum] Local UDP port to listen on
70
87
  #
71
88
  # @return [self]
72
- def bind(remote_addr, remote_port)
73
- @socket.bind(@resolver.resolve(remote_addr), remote_port)
89
+ def bind(local_addr, local_port)
90
+ @socket.bind(@resolver.resolve(local_addr).to_s, local_port)
74
91
  self
92
+ rescue Errno::EADDRINUSE => ex
93
+ raise AddressInUseError, ex.message, ex.backtrace
75
94
  rescue => ex
76
- # TODO: more specific exceptions
77
95
  raise Socketry::Error, ex.message, ex.backtrace
78
96
  end
79
97
 
80
- # Create a new UDP socket
98
+ # Make a UDP client connection to the given address and port
99
+ #
100
+ # @param remote_addr [String] DNS name or IP address of the host to connect to
101
+ # @param remote_port [Fixnum] UDP port to connect to
81
102
  #
82
103
  # @return [self]
83
104
  def connect(remote_addr, remote_port)
84
- @socket.connect(@resolver.resolve(remote_addr), remote_port)
105
+ @socket.connect(@resolver.resolve(remote_addr).to_s, remote_port)
85
106
  self
86
107
  rescue => ex
87
108
  # TODO: more specific exceptions
@@ -90,9 +111,11 @@ module Socketry
90
111
 
91
112
  # Perform a non-blocking receive
92
113
  #
93
- # @return [String, :wait_readable] received packet or indication to wait
114
+ # @param maxlen [Fixnum] Maximum packet length to receive
115
+ #
116
+ # @return [Socketry::UDP::Datagram, :wait_readable] Received datagram or indication to wait
94
117
  def recvfrom_nonblock(maxlen)
95
- @socket.recvfrom_nonblock(maxlen)
118
+ Socketry::UDP::Datagram.new(*@socket.recvfrom_nonblock(maxlen))
96
119
  rescue ::IO::WaitReadable
97
120
  :wait_readable
98
121
  rescue => ex
@@ -102,7 +125,10 @@ module Socketry
102
125
 
103
126
  # Perform a blocking receive
104
127
  #
105
- # @return [String] received data
128
+ # @param maxlen [Fixnum] Maximum packet length to receive
129
+ # @param timeout [Numeric] Number of seconds to wait for recvfrom operation to complete
130
+ #
131
+ # @return [String] Received data
106
132
  def recvfrom(maxlen, timeout: @read_timeout)
107
133
  set_timeout(timeout)
108
134
 
@@ -112,19 +138,48 @@ module Socketry
112
138
  raise Socketry::TimeoutError, "recvfrom timed out after #{timeout} seconds"
113
139
  end
114
140
  ensure
115
- clear_timeout(imeout)
141
+ clear_timeout(timeout)
116
142
  end
117
143
 
118
144
  result
119
145
  end
120
146
 
121
- # Send data to the given host and port
122
- def send(msg, host:, port:)
123
- @socket.send(msg, 0, @resolver.resolve(host), port)
147
+ # Send a UDP packet to a remote host
148
+ #
149
+ # @param msg [String] Data to write to the remote host/port
150
+ # @param host [String] Remote host to send data to. May be omitted if `connect` was called previously
151
+ # @param port [Fixnum] UDP port to send data to. May be omitted if `connect` was called previously
152
+ #
153
+ # @return [Fixum] Number of bytes sent
154
+ def send(msg, host: nil, port: nil)
155
+ host = @resolver.resolve(host).to_s if host
156
+ if host || port
157
+ @socket.send(msg, 0, host, port)
158
+ else
159
+ @socket.send(msg, 0)
160
+ end
124
161
  rescue => ex
125
162
  # TODO: more specific exceptions
126
163
  raise Socketry::Error, ex.message, ex.backtrace
127
164
  end
165
+
166
+ # Close the socket
167
+ #
168
+ # @return [true, false] true if the socket was open, false if closed
169
+ def close
170
+ return false if closed?
171
+ @socket.close
172
+ true
173
+ ensure
174
+ @socket = nil
175
+ end
176
+
177
+ # Is the socket closed?
178
+ #
179
+ # @return [true, false] do we locally think the socket is closed?
180
+ def closed?
181
+ @socket.nil?
182
+ end
128
183
  end
129
184
  end
130
185
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Socketry
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: socketry
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tony Arcieri
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-09-24 00:00:00.000000000 Z
11
+ date: 2016-11-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hitimes
@@ -66,6 +66,7 @@ files:
66
66
  - lib/socketry/tcp/server.rb
67
67
  - lib/socketry/tcp/socket.rb
68
68
  - lib/socketry/timeout.rb
69
+ - lib/socketry/udp/datagram.rb
69
70
  - lib/socketry/udp/socket.rb
70
71
  - lib/socketry/version.rb
71
72
  - socketry.gemspec
@@ -89,7 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
89
90
  version: '0'
90
91
  requirements: []
91
92
  rubyforge_project:
92
- rubygems_version: 2.5.1
93
+ rubygems_version: 2.5.2
93
94
  signing_key:
94
95
  specification_version: 4
95
96
  summary: High-level wrappers for Ruby sockets with advanced thread-safe timeout support