io-stream 0.9.1 → 0.11.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: 506d80a7bb9aee846def3d18d0bf53f2085b751de5d511a8a6a31f8a5de0efd2
4
- data.tar.gz: 9c828735d78bdeb4dba18b81ed350f5776ba3436cca7f18b17c465d6efd4ce7e
3
+ metadata.gz: 1c071e8e033bc6afd35a809466299187cfbee327acf26787968a7af2baeb89ce
4
+ data.tar.gz: 748cee4f872237453f35ef91aea936f29a632a27d35cf09235dcdd72b12e5826
5
5
  SHA512:
6
- metadata.gz: 19d26e89eca00835b6331548e72fc74bf17add777028af9bb6d6a7749ee760eab41a5bae780876de278c6f82eeec02366ce83bb160c798cb441a28dbef56f263
7
- data.tar.gz: 798d2863752eca4a03d9ae7b21000530442b7a4af218f2e89d1590baa2e821650fcb66689e6ae88ba91b5ec742136ef46c328a7a48afb2d7048990084a12af05
6
+ metadata.gz: 758b8a379a4b745e51a58810011a33fd5473527863da8c655d2f84a8fa135925b258edf25dd2431ab9b4f2b9e983a725cf6855ead510ac832705eb55c08a5d41
7
+ data.tar.gz: d7acbd5c30572b838503b1e25faea6b6ce046e76581d712bcd2fa6792a3f156992a69610a19d76be6e3746b4782cba479b9e318762dbb5864d5f4ff732fd273e
checksums.yaml.gz.sig CHANGED
Binary file
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2024, by Samuel Williams.
4
+ # Copyright, 2024-2025, by Samuel Williams.
5
5
 
6
6
  require_relative "generic"
7
+ require_relative "connection_reset_error"
7
8
 
8
9
  module IO::Stream
9
10
  # A buffered stream implementation that wraps an underlying IO object to provide efficient buffered reading and writing.
@@ -59,34 +60,34 @@ module IO::Stream
59
60
  @timeout = nil
60
61
  end
61
62
  end
62
-
63
+
63
64
  # @attribute [IO] The wrapped IO object.
64
65
  attr :io
65
-
66
+
66
67
  # Get the underlying IO object.
67
68
  # @returns [IO] The underlying IO object.
68
69
  def to_io
69
70
  @io.to_io
70
71
  end
71
-
72
+
72
73
  # Check if the stream is closed.
73
74
  # @returns [Boolean] True if the stream is closed.
74
75
  def closed?
75
76
  @io.closed?
76
77
  end
77
-
78
+
78
79
  # Close the read end of the stream.
79
80
  def close_read
80
81
  @io.close_read
81
82
  end
82
-
83
+
83
84
  # Close the write end of the stream.
84
85
  def close_write
85
86
  super
86
87
  ensure
87
88
  @io.close_write
88
89
  end
89
-
90
+
90
91
  # Check if the stream is readable.
91
92
  # @returns [Boolean] True if the stream is readable.
92
93
  def readable?
@@ -146,6 +147,12 @@ module IO::Stream
146
147
  return result
147
148
  end
148
149
  end
150
+ rescue OpenSSL::SSL::SSLError => error
151
+ if error.message =~ /unexpected eof while reading/
152
+ raise ConnectionResetError, "Connection reset by peer!"
153
+ end
154
+ rescue Errno::ECONNRESET
155
+ raise ConnectionResetError, "Connection reset by peer!"
149
156
  rescue Errno::EBADF
150
157
  raise ::IOError, "stream closed"
151
158
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IO::Stream
4
+ # Represents a connection reset error in IO streams, usually occurring when the remote side closes the connection unexpectedly.
5
+ class ConnectionResetError < Errno::ECONNRESET
6
+ end
7
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023-2024, by Samuel Williams.
4
+ # Copyright, 2023-2025, by Samuel Williams.
5
5
 
6
6
  require_relative "string_buffer"
7
7
  require_relative "readable"
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2024, by Samuel Williams.
4
+ # Copyright, 2024-2025, by Samuel Williams.
5
5
 
6
6
  require "openssl"
7
7
 
@@ -1,17 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023-2024, by Samuel Williams.
4
+ # Copyright, 2025, by Samuel Williams.
5
5
 
6
6
  require_relative "string_buffer"
7
7
 
8
8
  module IO::Stream
9
9
  # The default block size for IO buffers. Defaults to 256KB (optimized for modern SSDs and networks).
10
10
  BLOCK_SIZE = ENV.fetch("IO_STREAM_BLOCK_SIZE", 1024*256).to_i
11
-
11
+
12
12
  # The minimum read size for efficient I/O operations. Defaults to the same as BLOCK_SIZE.
13
13
  MINIMUM_READ_SIZE = ENV.fetch("IO_STREAM_MINIMUM_READ_SIZE", BLOCK_SIZE).to_i
14
-
14
+
15
15
  # The maximum read size for a single read operation. This limit exists because:
16
16
  # 1. System calls like read() cannot handle requests larger than SSIZE_MAX
17
17
  # 2. Very large reads can cause memory pressure and poor interactive performance
@@ -30,7 +30,7 @@ module IO::Stream
30
30
  # @parameter maximum_read_size [Integer] The maximum size for read operations.
31
31
  # @parameter block_size [Integer] Legacy parameter, use minimum_read_size instead.
32
32
  def initialize(minimum_read_size: MINIMUM_READ_SIZE, maximum_read_size: MAXIMUM_READ_SIZE, block_size: nil, **, &block)
33
- @done = false
33
+ @finished = false
34
34
  @read_buffer = StringBuffer.new
35
35
  # Used as destination buffer for underlying reads.
36
36
  @input_buffer = StringBuffer.new
@@ -41,21 +41,21 @@ module IO::Stream
41
41
 
42
42
  super(**, &block) if defined?(super)
43
43
  end
44
-
44
+
45
45
  attr_accessor :minimum_read_size
46
-
46
+
47
47
  # Legacy accessor for backwards compatibility
48
48
  # @returns [Integer] The minimum read size.
49
49
  def block_size
50
50
  @minimum_read_size
51
51
  end
52
-
52
+
53
53
  # Legacy setter for backwards compatibility
54
54
  # @parameter value [Integer] The minimum read size.
55
55
  def block_size=(value)
56
56
  @minimum_read_size = value
57
57
  end
58
-
58
+
59
59
  # Read data from the stream.
60
60
  # @parameter size [Integer | Nil] The number of bytes to read. If nil, read until end of stream.
61
61
  # @parameter buffer [String | Nil] An optional buffer to fill with data instead of allocating a new string.
@@ -72,7 +72,7 @@ module IO::Stream
72
72
  end
73
73
 
74
74
  if size
75
- until @done or @read_buffer.bytesize >= size
75
+ until @finished or @read_buffer.bytesize >= size
76
76
  # Compute the amount of data we need to read from the underlying stream:
77
77
  read_size = size - @read_buffer.bytesize
78
78
 
@@ -80,7 +80,7 @@ module IO::Stream
80
80
  fill_read_buffer(read_size > @minimum_read_size ? read_size : @minimum_read_size)
81
81
  end
82
82
  else
83
- until @done
83
+ until @finished
84
84
  fill_read_buffer
85
85
  end
86
86
 
@@ -113,8 +113,8 @@ module IO::Stream
113
113
  return String.new(encoding: Encoding::BINARY)
114
114
  end
115
115
  end
116
-
117
- if !@done and @read_buffer.empty?
116
+
117
+ if !@finished and @read_buffer.empty?
118
118
  fill_read_buffer
119
119
  end
120
120
 
@@ -134,7 +134,7 @@ module IO::Stream
134
134
  return buffer
135
135
  end
136
136
 
137
- raise exception, "Encountered done while reading data!"
137
+ raise exception, "Stream finished before reading enough data!"
138
138
  end
139
139
 
140
140
  # This is a compatibility shim for existing code that uses `readpartial`.
@@ -142,7 +142,7 @@ module IO::Stream
142
142
  # @parameter buffer [String | Nil] An optional buffer to fill with data instead of allocating a new string.
143
143
  # @returns [String] The data read from the stream.
144
144
  def readpartial(size = nil, buffer = nil)
145
- read_partial(size, buffer) or raise EOFError, "Encountered done while reading data!"
145
+ read_partial(size, buffer) or raise EOFError, "Stream finished before reading enough data!"
146
146
  end
147
147
 
148
148
  # Find the index of a pattern in the read buffer, reading more data if needed.
@@ -222,7 +222,7 @@ module IO::Stream
222
222
  # @returns [String] The data in the buffer without consuming it.
223
223
  def peek(size = nil)
224
224
  if size
225
- until @done or @read_buffer.bytesize >= size
225
+ until @finished or @read_buffer.bytesize >= size
226
226
  # Compute the amount of data we need to read from the underlying stream:
227
227
  read_size = size - @read_buffer.bytesize
228
228
 
@@ -231,7 +231,7 @@ module IO::Stream
231
231
  end
232
232
  return @read_buffer[..([size, @read_buffer.size].min - 1)]
233
233
  end
234
- until (block_given? && yield(@read_buffer)) or @done
234
+ until (block_given? && yield(@read_buffer)) or @finished
235
235
  fill_read_buffer
236
236
  end
237
237
  return @read_buffer
@@ -287,33 +287,33 @@ module IO::Stream
287
287
  # See {readable?} for a non-blocking alternative.
288
288
  #
289
289
  # @returns [Boolean] If the stream is at file which means there is no more data to be read.
290
- def done?
290
+ def finished?
291
291
  if !@read_buffer.empty?
292
292
  return false
293
- elsif @done
293
+ elsif @finished
294
294
  return true
295
295
  else
296
296
  return !self.fill_read_buffer
297
297
  end
298
298
  end
299
299
 
300
- alias eof? done?
300
+ alias eof? finished?
301
301
 
302
- # Mark the stream as done and raise `EOFError`.
303
- def done!
302
+ # Mark the stream as finished and raise `EOFError`.
303
+ def finish!
304
304
  @read_buffer.clear
305
- @done = true
305
+ @finished = true
306
306
 
307
307
  raise EOFError
308
308
  end
309
309
 
310
- alias eof! done!
310
+ alias eof! finish!
311
311
 
312
312
  # Whether there is a chance that a read operation will succeed or not.
313
313
  # @returns [Boolean] If the stream is readable, i.e. a `read` operation has a chance of success.
314
314
  def readable?
315
315
  # If we are at the end of the file, we can't read any more data:
316
- if @done
316
+ if @finished
317
317
  return false
318
318
  end
319
319
 
@@ -358,7 +358,7 @@ module IO::Stream
358
358
  end
359
359
 
360
360
  # else for both cases above:
361
- @done = true
361
+ @finished = true
362
362
  return false
363
363
  end
364
364
 
@@ -367,8 +367,8 @@ module IO::Stream
367
367
  # @parameter buffer [String | Nil] An optional buffer to fill with data instead of allocating a new string.
368
368
  # @returns [String | Nil] The consumed data, or nil if no data available.
369
369
  def consume_read_buffer(size = nil, buffer = nil)
370
- # If we are at done, and the read buffer is empty, we can't consume anything.
371
- if @done && @read_buffer.empty?
370
+ # If we are at finished, and the read buffer is empty, we can't consume anything.
371
+ if @finished && @read_buffer.empty?
372
372
  # Clear the buffer even when returning nil
373
373
  if buffer
374
374
  buffer.clear
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023-2024, by Samuel Williams.
4
+ # Copyright, 2023-2025, by Samuel Williams.
5
5
 
6
6
  unless IO.method_defined?(:buffered?, false)
7
7
  class IO
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023-2024, by Samuel Williams.
4
+ # Copyright, 2023-2025, by Samuel Williams.
5
5
 
6
6
  class IO
7
7
  unless method_defined?(:readable?, false)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023-2024, by Samuel Williams.
4
+ # Copyright, 2023-2025, by Samuel Williams.
5
5
 
6
6
  module IO::Stream
7
7
  # A specialized string buffer for binary data with automatic encoding handling.
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023-2024, by Samuel Williams.
4
+ # Copyright, 2023-2025, by Samuel Williams.
5
5
 
6
6
  module IO::Stream
7
- VERSION = "0.9.1"
7
+ VERSION = "0.11.0"
8
8
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023-2024, by Samuel Williams.
4
+ # Copyright, 2025, by Samuel Williams.
5
5
 
6
6
  require_relative "readable"
7
7
 
data/lib/io/stream.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023-2024, by Samuel Williams.
4
+ # Copyright, 2023-2025, by Samuel Williams.
5
5
 
6
6
  require_relative "stream/version"
7
7
  require_relative "stream/buffered"
data/readme.md CHANGED
@@ -12,6 +12,15 @@ Please see the [project documentation](https://socketry.github.io/io-stream) for
12
12
 
13
13
  Please see the [project releases](https://socketry.github.io/io-streamreleases/index) for all releases.
14
14
 
15
+ ### v0.11.0
16
+
17
+ - Introduce `class IO::Stream::ConnectionResetError < Errno::ECONNRESET` to standardize connection reset error handling across different IO types.
18
+ - `OpenSSL::SSL::SSLSocket` raises `OpenSSL::SSL::SSLError` on connection reset, while other IO types raise `Errno::ECONNRESET`. `SSLError` is now rescued and re-raised as `IO::Stream::ConnectionResetError` for consistency.
19
+
20
+ ### v0.10.0
21
+
22
+ - Rename `done?` to `finished?` for clarity and consistency.
23
+
15
24
  ### v0.9.1
16
25
 
17
26
  - Fix EOF behavior to match Ruby IO semantics: `read()` returns empty string `""` at EOF while `read(size)` returns `nil` at EOF.
@@ -50,18 +59,6 @@ Please see the [project releases](https://socketry.github.io/io-streamreleases/i
50
59
  - Ensure TLS connections have correct buffering behavior.
51
60
  - Improve test suite organization and readability.
52
61
 
53
- ### v0.4.2
54
-
55
- - Add external test suite for better integration testing.
56
- - Update dependencies and improve code style with RuboCop.
57
-
58
- ### v0.4.1
59
-
60
- - Add compatibility fix for `SSLSocket` raising `EBADF` errors.
61
- - Fix `IO#close` hang issue in certain scenarios.
62
- - Add `#to_io` method to `IO::Stream::Buffered` for better compatibility.
63
- - Modernize gem structure and dependencies.
64
-
65
62
  ## See Also
66
63
 
67
64
  - [async-io](https://github.com/socketry/async-io) — Where this implementation originally came from.
data/releases.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Releases
2
2
 
3
+ ## v0.11.0
4
+
5
+ - Introduce `class IO::Stream::ConnectionResetError < Errno::ECONNRESET` to standardize connection reset error handling across different IO types.
6
+ - `OpenSSL::SSL::SSLSocket` raises `OpenSSL::SSL::SSLError` on connection reset, while other IO types raise `Errno::ECONNRESET`. `SSLError` is now rescued and re-raised as `IO::Stream::ConnectionResetError` for consistency.
7
+
8
+ ## v0.10.0
9
+
10
+ - Rename `done?` to `finished?` for clarity and consistency.
11
+
3
12
  ## v0.9.1
4
13
 
5
14
  - Fix EOF behavior to match Ruby IO semantics: `read()` returns empty string `""` at EOF while `read(size)` returns `nil` at EOF.
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.9.1
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -44,6 +44,7 @@ extra_rdoc_files: []
44
44
  files:
45
45
  - lib/io/stream.rb
46
46
  - lib/io/stream/buffered.rb
47
+ - lib/io/stream/connection_reset_error.rb
47
48
  - lib/io/stream/generic.rb
48
49
  - lib/io/stream/openssl.rb
49
50
  - lib/io/stream/readable.rb
@@ -75,7 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
75
76
  - !ruby/object:Gem::Version
76
77
  version: '0'
77
78
  requirements: []
78
- rubygems_version: 3.6.7
79
+ rubygems_version: 3.7.2
79
80
  specification_version: 4
80
81
  summary: Provides a generic stream wrapper for IO instances.
81
82
  test_files: []
metadata.gz.sig CHANGED
Binary file