io-stream 0.0.1 → 0.1.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: 259533ca29c9be8e0476777d9060a4aa6928fa856848396110b3f668628db636
4
- data.tar.gz: ed074a9ff19c5f0f74223228d1bb9929ff7534f921c11e4adf34fe88c5f2824b
3
+ metadata.gz: 15a89f071ddff7b47577a25a20195cb1d246f9b7105af8ee605ae01e0d267923
4
+ data.tar.gz: c29039d3ab0d1e234733ddf38f1ddc4be40cbffa92b5f9b47ba1dc247e9ea357
5
5
  SHA512:
6
- metadata.gz: f90e4bca685926a8cc28b21b65ddceea17fd8d3ba25ccefd6daf9095403ef7446c71b9afdfe57367faccf2ee46635bcf939e14f8ac36e9ebc6b9ee55bdafaada
7
- data.tar.gz: f7aaa56fb744df8cd057412083ad63c707165e3e1c404397dfa447f2135f7d11fef6d0b855cf88c23d28346ba96c320bc9566e7395520889d89ea7b63ea4fd1c
6
+ metadata.gz: 90d91151a245677dd5e7b96c2942af1b85f16d2a59c64c981e0d3d256d4af0ce5cc81115bfeb3a1981f765695e4777a0e76caf9c8f3d8995bbba204ebce8d91e
7
+ data.tar.gz: e40408fa7e69bab982229290640cb0e9c7a4efb9bc39ac40003ef1fe698f1aac962cd10a7464b5b2643bf46dddf6697e26296209527d507a0d135f112bc4bbae
checksums.yaml.gz.sig CHANGED
Binary file
data/lib/io/buffered.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2023-2024, by Samuel Williams.
5
5
 
6
- unless IO.method_defined?(:buffered?)
6
+ unless IO.method_defined?(:buffered?, false)
7
7
  class IO
8
8
  def buffered?
9
9
  return !self.sync
@@ -17,16 +17,19 @@ end
17
17
 
18
18
  require 'socket'
19
19
 
20
- unless BasicSocket.method_defined?(:buffered?)
20
+ unless BasicSocket.method_defined?(:buffered?, false)
21
21
  class BasicSocket
22
- # Is it likely that the socket is still connected?
23
- # May return false positive, but won't return false negative.
22
+ def ip_protocol_tcp?
23
+ local_address = self.local_address
24
+
25
+ return (local_address.afamily == ::Socket::AF_INET || local_address.afamily == ::Socket::AF_INET6) && local_address.socktype == ::Socket::SOCK_STREAM
26
+ end
27
+
24
28
  def buffered?
25
29
  return false unless super
26
30
 
27
- case self.local_address.protocol
28
- when IPPROTO_TCP
29
- return !self.getsockopt(IPPROTO_TCP, TCP_NODELAY).bool
31
+ if ip_protocol_tcp?
32
+ return !self.getsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY).bool
30
33
  else
31
34
  return true
32
35
  end
@@ -35,14 +38,13 @@ unless BasicSocket.method_defined?(:buffered?)
35
38
  def buffered=(value)
36
39
  super
37
40
 
38
- case self.local_address.socktype
39
- when SOCK_STREAM
41
+ if ip_protocol_tcp?
40
42
  # When buffered is set to true, TCP_NODELAY shold be disabled.
41
- self.setsockopt(IPPROTO_TCP, TCP_NODELAY, value ? 0 : 1)
43
+ self.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, value ? 0 : 1)
42
44
  end
43
- rescue Errno::EINVAL
45
+ rescue ::Errno::EINVAL
44
46
  # On Darwin, sometimes occurs when the connection is not yet fully formed. Empirically, TCP_NODELAY is enabled despite this result.
45
- rescue Errno::EOPNOTSUPP
47
+ rescue ::Errno::EOPNOTSUPP
46
48
  # Some platforms may simply not support the operation.
47
49
  end
48
50
  end
@@ -50,7 +52,7 @@ end
50
52
 
51
53
  require 'stringio'
52
54
 
53
- unless StringIO.method_defined?(:buffered?)
55
+ unless StringIO.method_defined?(:buffered?, false)
54
56
  class StringIO
55
57
  def buffered?
56
58
  return !self.sync
data/lib/io/readable.rb CHANGED
@@ -4,7 +4,7 @@
4
4
  # Copyright, 2023-2024, by Samuel Williams.
5
5
 
6
6
  class IO
7
- unless method_defined?(:readable?)
7
+ unless method_defined?(:readable?, false)
8
8
  def readable?
9
9
  # Do not call `eof?` here as it is not concurrency-safe and it can block.
10
10
  !closed?
@@ -15,10 +15,10 @@ end
15
15
  require 'socket'
16
16
 
17
17
  class BasicSocket
18
- unless method_defined?(:readable?)
18
+ unless method_defined?(:readable?, false)
19
19
  def readable?
20
20
  # If we can wait for the socket to become readable, we know that the socket may still be open.
21
- result = self.recv_nonblock(1, MSG_PEEK, exception: false)
21
+ result = self.recv_nonblock(1, ::Socket::MSG_PEEK, exception: false)
22
22
 
23
23
  # No data was available - newer Ruby can return nil instead of empty string:
24
24
  return false if result.nil?
@@ -35,7 +35,7 @@ end
35
35
  require 'stringio'
36
36
 
37
37
  class StringIO
38
- unless method_defined?(:readable?)
38
+ unless method_defined?(:readable?, false)
39
39
  def readable?
40
40
  !eof?
41
41
  end
@@ -45,7 +45,7 @@ end
45
45
  require 'openssl'
46
46
 
47
47
  class OpenSSL::SSL::SSLSocket
48
- unless method_defined?(:readable?)
48
+ unless method_defined?(:readable?, false)
49
49
  def readable?
50
50
  to_io.readable?
51
51
  end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023-2024, by Samuel Williams.
5
+
6
+ require_relative 'generic'
7
+
8
+ module IO::Stream
9
+ class Buffered < Generic
10
+ def self.open(path, mode = "r+", **options)
11
+ stream = self.new(::File.open(path, mode), **options)
12
+
13
+ return stream unless block_given?
14
+
15
+ begin
16
+ yield stream
17
+ ensure
18
+ stream.close
19
+ end
20
+ end
21
+
22
+ def self.wrap(io, **options)
23
+ if io.respond_to?(:buffered=)
24
+ io.buffered = false
25
+ end
26
+
27
+ stream = self.new(io, **options)
28
+
29
+ return stream unless block_given?
30
+
31
+ begin
32
+ yield stream
33
+ ensure
34
+ stream.close
35
+ end
36
+ end
37
+
38
+ def initialize(io, ...)
39
+ super(...)
40
+
41
+ @io = io
42
+ end
43
+
44
+ attr :io
45
+
46
+ def closed?
47
+ @io.closed?
48
+ end
49
+
50
+ def close_read
51
+ @io.close_read
52
+ end
53
+
54
+ def close_write
55
+ super
56
+ ensure
57
+ @io.close_write
58
+ end
59
+
60
+ def readable?
61
+ super && @io.readable?
62
+ end
63
+
64
+ protected
65
+
66
+ def sysclose
67
+ @io.close
68
+ end
69
+
70
+ def syswrite(buffer)
71
+ @io.write(buffer)
72
+ end
73
+
74
+ # Reads data from the underlying stream as efficiently as possible.
75
+ def sysread(size, buffer)
76
+ # Come on Ruby, why couldn't this just return `nil`? EOF is not exceptional. Every file has one.
77
+ while true
78
+ result = @io.read_nonblock(size, buffer, exception: false)
79
+
80
+ case result
81
+ when :wait_readable
82
+ @io.wait_readable
83
+ when :wait_writable
84
+ @io.wait_writable
85
+ else
86
+ return result
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -8,6 +8,8 @@ require_relative 'string_buffer'
8
8
  require_relative '../buffered'
9
9
  require_relative '../readable'
10
10
 
11
+ require_relative 'openssl'
12
+
11
13
  module IO::Stream
12
14
  # The default block size for IO buffers. Defaults to 64KB (typical pipe buffer size).
13
15
  BLOCK_SIZE = ENV.fetch('IO_STREAM_BLOCK_SIZE', 1024*64).to_i
@@ -15,32 +17,11 @@ module IO::Stream
15
17
  # The maximum read size when appending to IO buffers. Defaults to 8MB.
16
18
  MAXIMUM_READ_SIZE = ENV.fetch('IO_STREAM_MAXIMUM_READ_SIZE', BLOCK_SIZE * 128).to_i
17
19
 
18
- class BufferedStream
19
- def self.open(path, mode = "r+", **options)
20
- stream = self.new(File.open(path, mode), **options)
21
-
22
- return stream unless block_given?
23
-
24
- begin
25
- yield stream
26
- ensure
27
- stream.close
28
- end
29
- end
30
-
31
- def self.wrap(io, **options)
32
- if io.respond_to?(:buffered=)
33
- io.buffered = false
34
- end
35
-
36
- self.new(io, **options)
37
- end
38
-
39
- def initialize(io, block_size: BLOCK_SIZE, maximum_read_size: MAXIMUM_READ_SIZE)
40
- @io = io
20
+ class Generic
21
+ def initialize(block_size: BLOCK_SIZE, maximum_read_size: MAXIMUM_READ_SIZE)
41
22
  @eof = false
42
23
 
43
- @writing = Thread::Mutex.new
24
+ @writing = ::Thread::Mutex.new
44
25
 
45
26
  @block_size = block_size
46
27
  @maximum_read_size = maximum_read_size
@@ -53,9 +34,7 @@ module IO::Stream
53
34
  @input_buffer = StringBuffer.new
54
35
  end
55
36
 
56
- attr :io
57
-
58
- attr :block_size
37
+ attr_accessor :block_size
59
38
 
60
39
  # Reads `size` bytes from the stream. If size is not specified, read until end of file.
61
40
  def read(size = nil)
@@ -101,7 +80,7 @@ module IO::Stream
101
80
  raise exception, "encountered eof while reading data"
102
81
  end
103
82
 
104
- # This is a compatibility shim for existing code:
83
+ # This is a compatibility shim for existing code that uses `readpartial`.
105
84
  def readpartial(size = nil)
106
85
  read_partial(size) or raise EOFError, "Encountered eof while reading data!"
107
86
  end
@@ -158,7 +137,7 @@ module IO::Stream
158
137
  @write_buffer, @drain_buffer = @drain_buffer, @write_buffer
159
138
 
160
139
  begin
161
- @io.syswrite(@drain_buffer)
140
+ syswrite(@drain_buffer)
162
141
  ensure
163
142
  # If the write operation fails, we still need to clear this buffer, and the data is essentially lost.
164
143
  @drain_buffer.clear
@@ -195,34 +174,27 @@ module IO::Stream
195
174
  flush
196
175
  end
197
176
 
198
- def connected?
199
- @io.connected?
200
- end
201
-
202
177
  def closed?
203
- @io.closed?
178
+ false
204
179
  end
205
180
 
206
181
  def close_read
207
- @io.close_read
208
182
  end
209
183
 
210
184
  def close_write
211
185
  flush
212
- ensure
213
- @io.close_write
214
186
  end
215
187
 
216
188
  # Best effort to flush any unwritten data, and then close the underling IO.
217
189
  def close
218
- return if @io.closed?
190
+ return if closed?
219
191
 
220
192
  begin
221
193
  flush
222
194
  rescue
223
195
  # We really can't do anything here unless we want #close to raise exceptions.
224
196
  ensure
225
- @io.close
197
+ sysclose
226
198
  end
227
199
  end
228
200
 
@@ -261,19 +233,26 @@ module IO::Stream
261
233
  end
262
234
 
263
235
  # If the underlying stream is readable, we can read more data:
264
- return @io.readable?
236
+ return !closed?
265
237
  end
266
238
 
267
- private
239
+ protected
240
+
241
+ def sysclose
242
+ raise NotImplementedError
243
+ end
244
+
245
+ def syswrite(buffer)
246
+ raise NotImplementedError
247
+ end
268
248
 
269
249
  # Reads data from the underlying stream as efficiently as possible.
270
250
  def sysread(size, buffer)
271
- # Come on Ruby, why couldn't this just return `nil`? EOF is not exceptional. Every file has one.
272
- @io.sysread(size, buffer)
273
- rescue EOFError
274
- return false
251
+ raise NotImplementedError
275
252
  end
276
253
 
254
+ private
255
+
277
256
  # Fills the buffer from the underlying stream.
278
257
  def fill_read_buffer(size = @block_size)
279
258
  # We impose a limit because the underlying `read` system call can fail if we request too much data in one go.
@@ -0,0 +1,31 @@
1
+ require 'openssl'
2
+
3
+ module OpenSSL
4
+ module SSL
5
+ class SSLSocket
6
+ unless method_defined?(:close_read)
7
+ def close_read
8
+ # Ignored.
9
+ end
10
+ end
11
+
12
+ unless method_defined?(:close_write)
13
+ def close_write
14
+ self.stop
15
+ end
16
+ end
17
+
18
+ unless method_defined?(:wait_readable)
19
+ def wait_readable(...)
20
+ to_io.wait_readable(...)
21
+ end
22
+ end
23
+
24
+ unless method_defined?(:wait_writable)
25
+ def wait_writable(...)
26
+ to_io.wait_writable(...)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -4,5 +4,5 @@
4
4
  # Copyright, 2023-2024, by Samuel Williams.
5
5
 
6
6
  module IO::Stream
7
- VERSION = "0.0.1"
7
+ VERSION = "0.1.0"
8
8
  end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: io-stream
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -48,7 +48,9 @@ files:
48
48
  - lib/io/buffered.rb
49
49
  - lib/io/readable.rb
50
50
  - lib/io/stream.rb
51
- - lib/io/stream/buffered_stream.rb
51
+ - lib/io/stream/buffered.rb
52
+ - lib/io/stream/generic.rb
53
+ - lib/io/stream/openssl.rb
52
54
  - lib/io/stream/string_buffer.rb
53
55
  - lib/io/stream/version.rb
54
56
  - license.md
metadata.gz.sig CHANGED
Binary file