io-stream 0.0.1 → 0.1.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/io/buffered.rb +15 -13
- data/lib/io/readable.rb +5 -5
- data/lib/io/stream/buffered.rb +91 -0
- data/lib/io/stream/{buffered_stream.rb → generic.rb} +24 -45
- data/lib/io/stream/openssl.rb +31 -0
- data/lib/io/stream/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +4 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 15a89f071ddff7b47577a25a20195cb1d246f9b7105af8ee605ae01e0d267923
|
4
|
+
data.tar.gz: c29039d3ab0d1e234733ddf38f1ddc4be40cbffa92b5f9b47ba1dc247e9ea357
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
23
|
-
|
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
|
-
|
28
|
-
|
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
|
-
|
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
|
19
|
-
def
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
236
|
+
return !closed?
|
265
237
|
end
|
266
238
|
|
267
|
-
|
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
|
-
|
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
|
data/lib/io/stream/version.rb
CHANGED
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
|
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/
|
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
|