Since curb 1.3.0 every call to Curl::Easy#perform raises on Ruby 2.6. The gemspec advertises required_ruby_version >= 2.2, but the bug is invisible to curb's own CI because the test matrix starts at Ruby 2.7.
Discovered this while submitting a PR to webmock bumping the confirmed-curb-version range to 1.3.5. webmock's CI still includes Ruby 2.6 and the curb-adapter test suite calls easy.perform.
Reproduction
require 'curb'
Curl::Easy.new("file:///etc/hostname").perform
Result on Ruby 2.6
ArgumentError: cannot define finalizer for TrueClass
lib/curl/multi.rb:278:in `[]='
lib/curl/multi.rb:278:in `__register_idle_easy_reference'
lib/curl/easy.rb:100:in `multi='
lib/curl/easy.rb:177:in `perform'
Result on Ruby 2.7+
Works as expected.
Root cause
Curl::Easy#perform creates an implicit Curl::Multi, then assigns it via the Ruby setter.
Curl::Easy#multi= (lib/curl/easy.rb:94–102) calls
multi.__send__(:__register_idle_easy_reference, self), which does:
def __register_idle_easy_reference(easy)
__idle_easy_references[easy] = true # <- raises on Ruby 2.6
self
end
__idle_easy_references is an ObjectSpace::WeakMap. In Ruby ≤ 2.6, WeakMap calls ObjectSpace.define_finalizer on the key and the value. define_finalizer rejects:
- Immediates (
true, false, nil, Symbol, Integer)
- Frozen objects
Ruby 2.7 reworked WeakMap to no longer require define_finalizer for either side (Bug #16035), which is why the issue is invisible on every tested CI version.
I tried fixing this with
__idle_easy_references[easy] = easy # instead of "true"
but that doesn't work for frozen Easy objects.
Since curb 1.3.0 every call to
Curl::Easy#performraises on Ruby 2.6. The gemspec advertisesrequired_ruby_version >= 2.2, but the bug is invisible to curb's own CI because the test matrix starts at Ruby 2.7.Discovered this while submitting a PR to webmock bumping the confirmed-curb-version range to 1.3.5. webmock's CI still includes Ruby 2.6 and the curb-adapter test suite calls
easy.perform.Reproduction
Result on Ruby 2.6
Result on Ruby 2.7+
Works as expected.
Root cause
Curl::Easy#performcreates an implicitCurl::Multi, then assigns it via the Ruby setter.Curl::Easy#multi=(lib/curl/easy.rb:94–102) callsmulti.__send__(:__register_idle_easy_reference, self), which does:__idle_easy_referencesis anObjectSpace::WeakMap. In Ruby ≤ 2.6, WeakMap callsObjectSpace.define_finalizeron the key and the value.define_finalizerrejects:true,false,nil, Symbol, Integer)Ruby 2.7 reworked WeakMap to no longer require
define_finalizerfor either side (Bug #16035), which is why the issue is invisible on every tested CI version.I tried fixing this with
but that doesn't work for frozen
Easyobjects.