diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index 8afa726334a90c..27e21e36389f77 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -44,7 +44,7 @@ jobs: ) }} MINGW_PACKAGE_PREFIX: >- mingw-w${{ - endsWith(matrix.msystem, '64') && '64' || '32' + case(endsWith(matrix.msystem, '64'), '64', '32') }}-${{ case( startsWith(matrix.msystem, 'clang'), 'clang', startsWith(matrix.msystem, 'ucrt'), 'ucrt', diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index 7e319cae0de3cf..578ad108515d64 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -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; @@ -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); diff --git a/parse.y b/parse.y index 6e0c610a7c4359..3a22c68d5df3eb 100644 --- a/parse.y +++ b/parse.y @@ -5008,12 +5008,6 @@ block_param : f_arg ',' f_opt_arg(primary_value) ',' f_rest_arg opt_args_tail(bl $$ = new_args(p, $1, 0, $3, 0, $4, &@$); /*% ripper: params!($:1, Qnil, $:3, Qnil, *$:4[0..2]) %*/ } - | f_arg excessed_comma - { - $$ = new_args_tail(p, 0, 0, 0, &@2); - $$ = new_args(p, $1, 0, $2, 0, $$, &@$); - /*% ripper: params!($:1, Qnil, $:2, Qnil, Qnil, Qnil, Qnil) %*/ - } | f_arg ',' f_rest_arg ',' f_arg opt_args_tail(block_args_tail) { $$ = new_args(p, $1, 0, $3, $5, $6, &@$); @@ -5075,6 +5069,15 @@ block_param_def : '|' opt_block_param opt_bv_decl '|' $$ = $2; /*% ripper: block_var!($:2, $:3) %*/ } + | '|' f_arg excessed_comma opt_bv_decl '|' + { + p->max_numparam = ORDINAL_PARAM; + p->ctxt.in_argdef = 0; + $$ = new_args_tail(p, 0, 0, 0, &@3); + $$ = new_args(p, $2, 0, $3, 0, $$, &@$); + /*% ripper: params!($:2, Qnil, $:3, Qnil, Qnil, Qnil, Qnil) %*/ + /*% ripper: block_var!($:$, $:4) %*/ + } ; opt_block_param : /* none */ diff --git a/test/zlib/test_zlib.rb b/test/zlib/test_zlib.rb index bedf243b3e5919..5b0bf9c9807ce7 100644 --- a/test/zlib/test_zlib.rb +++ b/test/zlib/test_zlib.rb @@ -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 @@ -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