Skip to content

Curl::Easy#perform crashes on Ruby 2.6 #478

Description

@jbockler

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:

  1. Immediates (true, false, nil, Symbol, Integer)
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions