Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,6 @@ Style/CaseLikeIf:

Style/EmptyMethod:
Enabled: false

Style/AccessModifierDeclarations:
Enabled: false
111 changes: 76 additions & 35 deletions lib/redis_client/ruby_connection/buffered_io.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,80 @@ class BufferedIO

attr_accessor :read_timeout, :write_timeout

def initialize(io, read_timeout:, write_timeout:, chunk_size: 4096)
@io = io
@buffer = +""
@offset = 0
@chunk_size = chunk_size
@read_timeout = read_timeout
@write_timeout = write_timeout
@blocking_reads = false
if String.method_defined?(:byteindex) # Ruby 3.2+
ENCODING = Encoding::UTF_8

def initialize(io, read_timeout:, write_timeout:, chunk_size: 4096)
@io = io
@buffer = +""
@offset = 0
@chunk_size = chunk_size
@read_timeout = read_timeout
@write_timeout = write_timeout
@blocking_reads = false
end

def gets_chomp
fill_buffer(false) if @offset >= @buffer.bytesize
until eol_index = @buffer.byteindex(EOL, @offset)
fill_buffer(false)
end

line = @buffer.byteslice(@offset, eol_index - @offset)
@offset = eol_index + EOL_SIZE
line
end

def read_chomp(bytes)
ensure_remaining(bytes + EOL_SIZE)
str = @buffer.byteslice(@offset, bytes)
@offset += bytes + EOL_SIZE
str
end

private def ensure_line
fill_buffer(false) if @offset >= @buffer.bytesize
until @buffer.byteindex(EOL, @offset)
fill_buffer(false)
end
end
else
ENCODING = Encoding::BINARY

def initialize(io, read_timeout:, write_timeout:, chunk_size: 4096)
@io = io
@buffer = "".b
@offset = 0
@chunk_size = chunk_size
@read_timeout = read_timeout
@write_timeout = write_timeout
@blocking_reads = false
end

def gets_chomp
fill_buffer(false) if @offset >= @buffer.bytesize
until eol_index = @buffer.index(EOL, @offset)
fill_buffer(false)
end

line = @buffer.byteslice(@offset, eol_index - @offset)
@offset = eol_index + EOL_SIZE
line
end

def read_chomp(bytes)
ensure_remaining(bytes + EOL_SIZE)
str = @buffer.byteslice(@offset, bytes)
@offset += bytes + EOL_SIZE
str.force_encoding(Encoding::UTF_8)
end

private def ensure_line
fill_buffer(false) if @offset >= @buffer.bytesize
until @buffer.index(EOL, @offset)
fill_buffer(false)
end
end
end

def close
Expand Down Expand Up @@ -90,17 +156,6 @@ def getbyte
byte
end

def gets_chomp
fill_buffer(false) if @offset >= @buffer.bytesize
until eol_index = @buffer.index(EOL, @offset)
fill_buffer(false)
end

line = @buffer.byteslice(@offset, eol_index - @offset)
@offset = eol_index + EOL_SIZE
line
end

def gets_integer
int = 0
offset = @offset
Expand All @@ -124,22 +179,8 @@ def gets_integer
int
end

def read_chomp(bytes)
ensure_remaining(bytes + EOL_SIZE)
str = @buffer.byteslice(@offset, bytes)
@offset += bytes + EOL_SIZE
str
end

private

def ensure_line
fill_buffer(false) if @offset >= @buffer.bytesize
until @buffer.index(EOL, @offset)
fill_buffer(false)
end
end

def ensure_remaining(bytes)
needed = bytes - (@buffer.bytesize - @offset)
if needed > 0
Expand Down Expand Up @@ -171,9 +212,9 @@ def fill_buffer(strict, size = @chunk_size)
if empty_buffer
@offset = start
empty_buffer = false
@buffer.force_encoding(Encoding::UTF_8) unless @buffer.encoding == Encoding::UTF_8
@buffer.force_encoding(ENCODING) unless @buffer.encoding == ENCODING
else
@buffer << bytes.force_encoding(Encoding::UTF_8)
@buffer << bytes.force_encoding(ENCODING)
end
remaining -= bytes.bytesize
return if !strict || remaining <= 0
Expand Down
5 changes: 5 additions & 0 deletions test/redis_client/resp3_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ def test_load_array
assert_parses [1, 2, 3], "*3\r\n:1\r\n:2\r\n:3\r\n"
end

def test_load_multibyte_chars
# Check that the buffer is properly operating over bytes and not characters
assert_parses ["۪۪", 2], "*2\r\n$12\r\n۪۪\r\n:2\r\n"
end

def test_load_set
assert_parses ['orange', 'apple', true, 100, 999], "~5\r\n+orange\r\n+apple\r\n#t\r\n:100\r\n:999\r\n"
end
Expand Down