Skip to content

Commit

Permalink
Merge pull request #471 from ruby/ractor
Browse files Browse the repository at this point in the history
Ractor support
  • Loading branch information
hsbt authored Dec 23, 2020
2 parents e2a628c + 7da2635 commit f150e59
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 22 deletions.
3 changes: 3 additions & 0 deletions ext/psych/psych.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ VALUE mPsych;

void Init_psych(void)
{
#ifdef HAVE_RB_EXT_RACTOR_SAFE
RB_EXT_RACTOR_SAFE(true);
#endif
mPsych = rb_define_module("Psych");

rb_define_singleton_method(mPsych, "libyaml_version", libyaml_version, 0);
Expand Down
48 changes: 34 additions & 14 deletions lib/psych.rb
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,9 @@

module Psych
# The version of libyaml Psych is using
LIBYAML_VERSION = Psych.libyaml_version.join '.'
LIBYAML_VERSION = Psych.libyaml_version.join('.').freeze
# Deprecation guard
NOT_GIVEN = Object.new
NOT_GIVEN = Object.new.freeze
private_constant :NOT_GIVEN

###
Expand Down Expand Up @@ -595,28 +595,25 @@ def self.safe_load_file filename, **kwargs
end

# :stopdoc:
@domain_types = {}
def self.add_domain_type domain, type_tag, &block
key = ['tag', domain, type_tag].join ':'
@domain_types[key] = [key, block]
@domain_types["tag:#{type_tag}"] = [key, block]
domain_types[key] = [key, block]
domain_types["tag:#{type_tag}"] = [key, block]
end

def self.add_builtin_type type_tag, &block
domain = 'yaml.org,2002'
key = ['tag', domain, type_tag].join ':'
@domain_types[key] = [key, block]
domain_types[key] = [key, block]
end

def self.remove_type type_tag
@domain_types.delete type_tag
domain_types.delete type_tag
end

@load_tags = {}
@dump_tags = {}
def self.add_tag tag, klass
@load_tags[tag] = klass.name
@dump_tags[klass] = tag
load_tags[tag] = klass.name
dump_tags[klass] = tag
end

# Workaround for emulating `warn '...', uplevel: 1` in Ruby 2.4 or lower.
Expand All @@ -635,9 +632,32 @@ def self.parse_caller(at)
private_class_method :warn_with_uplevel, :parse_caller

class << self
attr_accessor :load_tags
attr_accessor :dump_tags
attr_accessor :domain_types
if defined?(Ractor)
require 'forwardable'
extend Forwardable

class Config
attr_accessor :load_tags, :dump_tags, :domain_types
def initialize
@load_tags = {}
@dump_tags = {}
@domain_types = {}
end
end

def config
Ractor.current[:PsychConfig] ||= Config.new
end

def_delegators :config, :load_tags, :dump_tags, :domain_types, :load_tags=, :dump_tags=, :domain_types=
else
attr_accessor :load_tags
attr_accessor :dump_tags
attr_accessor :domain_types
end
end
self.load_tags = {}
self.dump_tags = {}
self.domain_types = {}
# :startdoc:
end
10 changes: 6 additions & 4 deletions lib/psych/class_loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ def symbolize sym

constants.each do |const|
konst = const_get const
define_method(const.to_s.downcase) do
load konst
end
class_eval <<~RUBY
def #{const.to_s.downcase}
load #{konst.inspect}
end
RUBY
end

private
Expand Down Expand Up @@ -69,7 +71,7 @@ def resolve klassname
rescue
nil
end
}.compact]
}.compact].freeze

class Restricted < ClassLoader
def initialize classes, symbols
Expand Down
20 changes: 17 additions & 3 deletions lib/psych/visitors/visitor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,26 @@ def accept target

private

DISPATCH = Hash.new do |hash, klass|
hash[klass] = "visit_#{klass.name.gsub('::', '_')}"
# @api private
def self.dispatch_cache
Hash.new do |hash, klass|
hash[klass] = :"visit_#{klass.name.gsub('::', '_')}"
end.compare_by_identity
end

if defined?(Ractor)
def dispatch
Ractor.current[:Psych_Visitors_Visitor] ||= Visitor.dispatch_cache
end
else
DISPATCH = dispatch_cache
def dispatch
DISPATCH
end
end

def visit target
send DISPATCH[target.class], target
send dispatch[target.class], target
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/psych/visitors/yaml_tree.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def initialize emitter, ss, options
raise(TypeError, "Can't dump #{target.class}") unless method

h[klass] = method
end
end.compare_by_identity
end

def start encoding = Nodes::Stream::UTF8
Expand Down
50 changes: 50 additions & 0 deletions test/psych/test_ractor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# frozen_string_literal: true
require_relative 'helper'

class TestPsychRactor < Test::Unit::TestCase
def test_ractor_round_trip
assert_ractor(<<~RUBY, require_relative: 'helper')
obj = {foo: [42]}
obj2 = Ractor.new(obj) do |obj|
Psych.load(Psych.dump(obj))
end.take
assert_equal obj, obj2
RUBY
end

def test_not_shareable
# There's no point in making these frozen / shareable
# and the C-ext disregards begin frozen
assert_ractor(<<~RUBY, require_relative: 'helper')
parser = Psych::Parser.new
emitter = Psych::Emitter.new(nil)
assert_raise(Ractor::Error) { Ractor.make_shareable(parser) }
assert_raise(Ractor::Error) { Ractor.make_shareable(emitter) }
RUBY
end

def test_ractor_config
# Config is ractor-local
# Test is to make sure it works, even though usage is probably very low.
# The methods are not documented and might be deprecated one day
assert_ractor(<<~RUBY, require_relative: 'helper')
r = Ractor.new do
Psych.add_builtin_type 'omap' do |type, val|
val * 2
end
Psych.load('--- !!omap hello')
end.take
assert_equal 'hellohello', r
assert_equal 'hello', Psych.load('--- !!omap hello')
RUBY
end

def test_ractor_constants
assert_ractor(<<~RUBY, require_relative: 'helper')
r = Ractor.new do
Psych.libyaml_version.join('.') == Psych::LIBYAML_VERSION
end.take
assert_equal true, r
RUBY
end
end if defined?(Test::Unit::TestCase)

0 comments on commit f150e59

Please sign in to comment.