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
16 changes: 11 additions & 5 deletions ext/zlib/zlib.c
Original file line number Diff line number Diff line change
Expand Up @@ -1094,8 +1094,9 @@ zstream_run_func(struct zstream_run_args *args)
break;
}

if (err != Z_OK && err != Z_BUF_ERROR)
if (err != Z_OK && err != Z_BUF_ERROR) {
break;
}

if (z->stream.avail_out > 0) {
z->flags |= ZSTREAM_FLAG_IN_STREAM;
Expand Down Expand Up @@ -1170,12 +1171,17 @@ zstream_run_try(VALUE value_arg)
/* retry if no exception is thrown */
if (err == Z_OK && args->interrupt) {
args->interrupt = 0;
goto loop;

/* Retry only if both avail_in > 0 (more input to process) and avail_out > 0
* (output buffer has space). If avail_out == 0, the buffer is full and should
* be consumed by the caller first. If avail_in == 0, there's nothing more to process. */
if (z->stream.avail_in > 0 && z->stream.avail_out > 0) {
goto loop;
}
}

if (flush != Z_FINISH && err == Z_BUF_ERROR
&& z->stream.avail_out > 0) {
z->flags |= ZSTREAM_FLAG_IN_STREAM;
if (flush != Z_FINISH && err == Z_BUF_ERROR && z->stream.avail_out > 0) {
z->flags |= ZSTREAM_FLAG_IN_STREAM;
}

zstream_reset_input(z);
Expand Down
61 changes: 61 additions & 0 deletions test/zlib/test_zlib.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1266,6 +1266,36 @@ def test_gzfile_read_size_boundary
end
}
end

# Test for signal interrupt bug: Z_BUF_ERROR with avail_out > 0
# This reproduces the issue where thread wakeup during GzipReader operations
# can cause Z_BUF_ERROR to be raised incorrectly
def test_thread_wakeup_interrupt
pend 'fails' if RUBY_ENGINE == 'truffleruby'
content = SecureRandom.base64(5000)
gzipped = Zlib.gzip(content)

1000.times do
thr = Thread.new do
loop do
Zlib::GzipReader.new(StringIO.new(gzipped)).read
end
end

# Wakeup the thread multiple times to trigger interrupts
10.times do
thr.wakeup
Thread.pass
end

# Give thread a moment to process
sleep 0.001

# Clean up
thr.kill
thr.join
end
end
end

class TestZlibGzipWriter < Test::Unit::TestCase
Expand Down Expand Up @@ -1534,5 +1564,36 @@ def test_gunzip_no_memory_leak
10_000.times {Zlib.gunzip(d)}
};
end

# Test for signal interrupt bug: Z_BUF_ERROR with avail_out > 0
# This reproduces the issue where thread wakeup during GzipReader operations
# can cause Z_BUF_ERROR to be raised incorrectly
def test_thread_wakeup_interrupt
pend 'fails' if RUBY_ENGINE == 'truffleruby'

content = SecureRandom.base64(5000)
gzipped = Zlib.gzip(content)

1000.times do
thr = Thread.new do
loop do
Zlib::GzipReader.new(StringIO.new(gzipped)).read
end
end

# Wakeup the thread multiple times to trigger interrupts
10.times do
thr.wakeup
Thread.pass
end

# Give thread a moment to process
sleep 0.001

# Clean up
thr.kill
thr.join
end
end
end
end