dry-rb/dry-validation

Ractor incompatibility: contracts, schemas, types, and results cannot be used from non-main Ractors on Ruby 4.0

开放

#749 创建于 2026年5月8日

 (1 条评论) (0 个反应) (0 位负责人)Ruby (196 个派生)github user discovery
bughelp wanted

仓库指标

星标
 (1,422 个星标)
PR 合并指标
 (30 天内没有已合并 PR)

描述

dry-validation (1.11.1) and its underlying dependencies (dry-schema, dry-types, dry-initializer) cannot be used inside Ractors on Ruby 4.0. No operation works — instantiating contracts, calling them, defining schemas, looking up types, or even passing a validation result into a Ractor.

Environment

  • Ruby version: 4.0.3 (2026-04-21)
  • dry-validation: 1.11.1
  • dry-schema: 1.16.0
  • dry-types: 1.9.1
  • dry-initializer: 3.2.0
  • OS: Linux x86_64

Reproduction

Save as dry_validation_ractor_poc.rb and run with ruby dry_validation_ractor_poc.rb:

#!/usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"

gemfile(true) do
  source "https://rubygems.org"
  gem "dry-validation", "~> 1.10"
end

require "dry-validation"

version = Gem.loaded_specs["dry-validation"]&.version
puts "Ruby #{RUBY_VERSION}, dry-validation #{version}"
puts "-" * 50

class UserContract < Dry::Validation::Contract
  params do
    required(:name).filled(:string)
    required(:age).filled(:integer, gt?: 0)
  end
  rule(:name) { key.failure("too short") if value.length < 2 }
end

contract = UserContract.new

tests = {
  "Contract.new in Ractor"      => -> { Ractor.new { UserContract.new }.value },
  "contract.call in Ractor"     => -> { Ractor.new(contract) { |c| c.call(name: "Alice", age: 30) }.value },
  "Define contract in Ractor"   => -> {
    Ractor.new {
      Class.new(Dry::Validation::Contract) { params { required(:x).filled(:string) } }.new.call(x: "hi")
    }.value
  },
  "Dry::Types[] in Ractor"      => -> { Ractor.new { Dry::Types["strict.string"] }.value },
  "Dry::Schema.Params in Ractor"=> -> { Ractor.new { Dry::Schema.Params { required(:a).filled(:string) } }.value },
  "Pass result across Ractor"   => -> { Ractor.new(contract.call(name: "Alice", age: 30)) { |r| r.success? }.value },
}

tests.each do |name, test|
  print "#{name}: "
  begin
    test.call
    puts "OK"
  rescue Ractor::RemoteError => e
    puts "FAILED — #{e.cause.class}: #{e.cause.message}"
  rescue => e
    puts "FAILED — #{e.class}: #{e.message}"
  end
end

Actual output

Ruby 4.0.3, dry-validation 1.11.1
--------------------------------------------------
Contract.new in Ractor: FAILED — RuntimeError: defined with an un-shareable Proc in a different Ractor
contract.call in Ractor: FAILED — TypeError: allocator undefined for Proc
Define contract in Ractor: FAILED — Ractor::IsolationError: can not get unshareable values from instance variables of classes/modules from non-main Ractors (@dry_initializer from Dry::Validation::Contract)
Dry::Types[] in Ractor: FAILED — Ractor::IsolationError: can not get unshareable values from instance variables of classes/modules from non-main Ractors (@type_map from Dry::Types)
Dry::Schema.Params in Ractor: FAILED — RuntimeError: defined with an un-shareable Proc in a different Ractor
Pass result across Ractor: FAILED — TypeError: allocator undefined for Proc

Expected behavior

At minimum, pre-built contracts should be callable from non-main Ractors, and results should be transferable across Ractor boundaries. Ideally, defining new contracts and looking up types should also work.

贡献者指南