[ruby/uri] Add proper Ractor support to URI

* Using a module to map scheme name to scheme class, which also works with Ractor.
* No constant redefinition, no ObjectSpace, still fast lookup for initial schemes.

https://github.com/ruby/uri/commit/883567fd81
This commit is contained in:
Benoit Daloze 2021-06-25 13:38:01 +02:00 committed by Hiroshi SHIBATA
parent 090d799c24
commit 1cf111774f
No known key found for this signature in database
GPG Key ID: F9CF13417264FAC2
12 changed files with 60 additions and 18 deletions

View File

@ -30,7 +30,7 @@
# class RSYNC < Generic # class RSYNC < Generic
# DEFAULT_PORT = 873 # DEFAULT_PORT = 873
# end # end
# @@schemes['RSYNC'] = RSYNC # register_scheme 'RSYNC', RSYNC
# end # end
# #=> URI::RSYNC # #=> URI::RSYNC
# #
@ -100,3 +100,9 @@ require_relative 'uri/https'
require_relative 'uri/ldap' require_relative 'uri/ldap'
require_relative 'uri/ldaps' require_relative 'uri/ldaps'
require_relative 'uri/mailto' require_relative 'uri/mailto'
module URI
INITIAL_SCHEMES = scheme_list
private_constant :INITIAL_SCHEMES
Ractor.make_shareable(INITIAL_SCHEMES) if defined?(Ractor)
end

View File

@ -16,6 +16,7 @@ module URI
REGEXP = RFC2396_REGEXP REGEXP = RFC2396_REGEXP
Parser = RFC2396_Parser Parser = RFC2396_Parser
RFC3986_PARSER = RFC3986_Parser.new RFC3986_PARSER = RFC3986_Parser.new
Ractor.make_shareable(RFC3986_PARSER) if defined?(Ractor)
# URI::Parser.new # URI::Parser.new
DEFAULT_PARSER = Parser.new DEFAULT_PARSER = Parser.new
@ -27,6 +28,7 @@ module URI
DEFAULT_PARSER.regexp.each_pair do |sym, str| DEFAULT_PARSER.regexp.each_pair do |sym, str|
const_set(sym, str) const_set(sym, str)
end end
Ractor.make_shareable(DEFAULT_PARSER) if defined?(Ractor)
module Util # :nodoc: module Util # :nodoc:
def make_components_hash(klass, array_hash) def make_components_hash(klass, array_hash)
@ -62,10 +64,19 @@ module URI
include REGEXP include REGEXP
@@schemes = {} module Schemes
end
private_constant :Schemes
def self.register_scheme(scheme, klass)
Schemes.const_set(scheme, klass)
end
# Returns a Hash of the defined schemes. # Returns a Hash of the defined schemes.
def self.scheme_list def self.scheme_list
@@schemes Schemes.constants.map { |name|
[name.to_s.upcase, Schemes.const_get(name)]
}.to_h
end end
# #
@ -73,11 +84,13 @@ module URI
# from +URI.scheme_list+. # from +URI.scheme_list+.
# #
def self.for(scheme, *arguments, default: Generic) def self.for(scheme, *arguments, default: Generic)
if scheme const_name = scheme.to_s.upcase
uri_class = @@schemes[scheme.upcase] || default
else uri_class = INITIAL_SCHEMES[const_name]
uri_class = default if !uri_class && !const_name.empty? && Schemes.const_defined?(const_name, false)
uri_class = Schemes.const_get(const_name, false)
end end
uri_class ||= default
return uri_class.new(scheme, *arguments) return uri_class.new(scheme, *arguments)
end end
@ -653,6 +666,7 @@ module URI
"utf-16"=>"utf-16le", "utf-16"=>"utf-16le",
"utf-16le"=>"utf-16le", "utf-16le"=>"utf-16le",
} # :nodoc: } # :nodoc:
Ractor.make_shareable(WEB_ENCODINGS_) if defined?(Ractor)
# :nodoc: # :nodoc:
# return encoding or nil # return encoding or nil

View File

@ -90,5 +90,5 @@ module URI
end end
end end
@@schemes['FILE'] = File register_scheme 'FILE', File
end end

View File

@ -262,5 +262,6 @@ module URI
return str return str
end end
end end
@@schemes['FTP'] = FTP
register_scheme 'FTP', FTP
end end

View File

@ -82,6 +82,5 @@ module URI
end end
end end
@@schemes['HTTP'] = HTTP register_scheme 'HTTP', HTTP
end end

View File

@ -18,5 +18,6 @@ module URI
# A Default port of 443 for URI::HTTPS # A Default port of 443 for URI::HTTPS
DEFAULT_PORT = 443 DEFAULT_PORT = 443
end end
@@schemes['HTTPS'] = HTTPS
register_scheme 'HTTPS', HTTPS
end end

View File

@ -257,5 +257,5 @@ module URI
end end
end end
@@schemes['LDAP'] = LDAP register_scheme 'LDAP', LDAP
end end

View File

@ -17,5 +17,6 @@ module URI
# A Default port of 636 for URI::LDAPS # A Default port of 636 for URI::LDAPS
DEFAULT_PORT = 636 DEFAULT_PORT = 636
end end
@@schemes['LDAPS'] = LDAPS
register_scheme 'LDAPS', LDAPS
end end

View File

@ -289,5 +289,5 @@ module URI
alias to_rfc822text to_mailtext alias to_rfc822text to_mailtext
end end
@@schemes['MAILTO'] = MailTo register_scheme 'MAILTO', MailTo
end end

View File

@ -79,6 +79,5 @@ module URI
end end
end end
@@schemes['WS'] = WS register_scheme 'WS', WS
end end

View File

@ -18,5 +18,6 @@ module URI
# A Default port of 443 for URI::WSS # A Default port of 443 for URI::WSS
DEFAULT_PORT = 443 DEFAULT_PORT = 443
end end
@@schemes['WSS'] = WSS
register_scheme 'WSS', WSS
end end

View File

@ -33,6 +33,26 @@ class TestCommon < Test::Unit::TestCase
end end
end end
def test_ractor
return unless defined?(Ractor)
r = Ractor.new { URI.parse("https://ruby-lang.org/").inspect }
assert_equal(URI.parse("https://ruby-lang.org/").inspect, r.take)
end
def test_register_scheme
assert_equal(["FILE", "FTP", "HTTP", "HTTPS", "LDAP", "LDAPS", "MAILTO", "WS"].sort, URI.scheme_list.keys.sort)
foobar = Class.new(URI::Generic)
URI.register_scheme 'FOOBAR', foobar
begin
assert_equal(["FILE", "FTP", "HTTP", "HTTPS", "LDAP", "LDAPS", "MAILTO", "WS", "FOOBAR"].sort, URI.scheme_list.keys.sort)
ensure
URI.const_get(:Schemes).send(:remove_const, :FOOBAR)
end
assert_equal(["FILE", "FTP", "HTTP", "HTTPS", "LDAP", "LDAPS", "MAILTO", "WS"].sort, URI.scheme_list.keys.sort)
end
def test_regexp def test_regexp
EnvUtil.suppress_warning do EnvUtil.suppress_warning do
assert_instance_of Regexp, URI.regexp assert_instance_of Regexp, URI.regexp