From 1cf111774f03c6d1ddba735cb8cc79483f16f699 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 25 Jun 2021 13:38:01 +0200 Subject: [PATCH] [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 --- lib/uri.rb | 8 +++++++- lib/uri/common.rb | 26 ++++++++++++++++++++------ lib/uri/file.rb | 2 +- lib/uri/ftp.rb | 3 ++- lib/uri/http.rb | 3 +-- lib/uri/https.rb | 3 ++- lib/uri/ldap.rb | 2 +- lib/uri/ldaps.rb | 3 ++- lib/uri/mailto.rb | 2 +- lib/uri/ws.rb | 3 +-- lib/uri/wss.rb | 3 ++- test/uri/test_common.rb | 20 ++++++++++++++++++++ 12 files changed, 60 insertions(+), 18 deletions(-) diff --git a/lib/uri.rb b/lib/uri.rb index 5e820f46c3..282e82c32c 100644 --- a/lib/uri.rb +++ b/lib/uri.rb @@ -30,7 +30,7 @@ # class RSYNC < Generic # DEFAULT_PORT = 873 # end -# @@schemes['RSYNC'] = RSYNC +# register_scheme 'RSYNC', RSYNC # end # #=> URI::RSYNC # @@ -100,3 +100,9 @@ require_relative 'uri/https' require_relative 'uri/ldap' require_relative 'uri/ldaps' require_relative 'uri/mailto' + +module URI + INITIAL_SCHEMES = scheme_list + private_constant :INITIAL_SCHEMES + Ractor.make_shareable(INITIAL_SCHEMES) if defined?(Ractor) +end diff --git a/lib/uri/common.rb b/lib/uri/common.rb index 915c0e9519..2df0536215 100644 --- a/lib/uri/common.rb +++ b/lib/uri/common.rb @@ -16,6 +16,7 @@ module URI REGEXP = RFC2396_REGEXP Parser = RFC2396_Parser RFC3986_PARSER = RFC3986_Parser.new + Ractor.make_shareable(RFC3986_PARSER) if defined?(Ractor) # URI::Parser.new DEFAULT_PARSER = Parser.new @@ -27,6 +28,7 @@ module URI DEFAULT_PARSER.regexp.each_pair do |sym, str| const_set(sym, str) end + Ractor.make_shareable(DEFAULT_PARSER) if defined?(Ractor) module Util # :nodoc: def make_components_hash(klass, array_hash) @@ -62,10 +64,19 @@ module URI 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. def self.scheme_list - @@schemes + Schemes.constants.map { |name| + [name.to_s.upcase, Schemes.const_get(name)] + }.to_h end # @@ -73,11 +84,13 @@ module URI # from +URI.scheme_list+. # def self.for(scheme, *arguments, default: Generic) - if scheme - uri_class = @@schemes[scheme.upcase] || default - else - uri_class = default + const_name = scheme.to_s.upcase + + uri_class = INITIAL_SCHEMES[const_name] + if !uri_class && !const_name.empty? && Schemes.const_defined?(const_name, false) + uri_class = Schemes.const_get(const_name, false) end + uri_class ||= default return uri_class.new(scheme, *arguments) end @@ -653,6 +666,7 @@ module URI "utf-16"=>"utf-16le", "utf-16le"=>"utf-16le", } # :nodoc: + Ractor.make_shareable(WEB_ENCODINGS_) if defined?(Ractor) # :nodoc: # return encoding or nil diff --git a/lib/uri/file.rb b/lib/uri/file.rb index 561ec703c4..7671ad6470 100644 --- a/lib/uri/file.rb +++ b/lib/uri/file.rb @@ -90,5 +90,5 @@ module URI end end - @@schemes['FILE'] = File + register_scheme 'FILE', File end diff --git a/lib/uri/ftp.rb b/lib/uri/ftp.rb index fb38481193..abad613c33 100644 --- a/lib/uri/ftp.rb +++ b/lib/uri/ftp.rb @@ -262,5 +262,6 @@ module URI return str end end - @@schemes['FTP'] = FTP + + register_scheme 'FTP', FTP end diff --git a/lib/uri/http.rb b/lib/uri/http.rb index 70cfb2a1bf..6e9c963ef1 100644 --- a/lib/uri/http.rb +++ b/lib/uri/http.rb @@ -82,6 +82,5 @@ module URI end end - @@schemes['HTTP'] = HTTP - + register_scheme 'HTTP', HTTP end diff --git a/lib/uri/https.rb b/lib/uri/https.rb index c481b1fe6d..50a5cabaf8 100644 --- a/lib/uri/https.rb +++ b/lib/uri/https.rb @@ -18,5 +18,6 @@ module URI # A Default port of 443 for URI::HTTPS DEFAULT_PORT = 443 end - @@schemes['HTTPS'] = HTTPS + + register_scheme 'HTTPS', HTTPS end diff --git a/lib/uri/ldap.rb b/lib/uri/ldap.rb index 14e6163292..4544349f18 100644 --- a/lib/uri/ldap.rb +++ b/lib/uri/ldap.rb @@ -257,5 +257,5 @@ module URI end end - @@schemes['LDAP'] = LDAP + register_scheme 'LDAP', LDAP end diff --git a/lib/uri/ldaps.rb b/lib/uri/ldaps.rb index 227e7fab35..58228f5894 100644 --- a/lib/uri/ldaps.rb +++ b/lib/uri/ldaps.rb @@ -17,5 +17,6 @@ module URI # A Default port of 636 for URI::LDAPS DEFAULT_PORT = 636 end - @@schemes['LDAPS'] = LDAPS + + register_scheme 'LDAPS', LDAPS end diff --git a/lib/uri/mailto.rb b/lib/uri/mailto.rb index d08c2ae9da..87cb99656f 100644 --- a/lib/uri/mailto.rb +++ b/lib/uri/mailto.rb @@ -289,5 +289,5 @@ module URI alias to_rfc822text to_mailtext end - @@schemes['MAILTO'] = MailTo + register_scheme 'MAILTO', MailTo end diff --git a/lib/uri/ws.rb b/lib/uri/ws.rb index 2bfee59003..ff3c554484 100644 --- a/lib/uri/ws.rb +++ b/lib/uri/ws.rb @@ -79,6 +79,5 @@ module URI end end - @@schemes['WS'] = WS - + register_scheme 'WS', WS end diff --git a/lib/uri/wss.rb b/lib/uri/wss.rb index 1cfa133389..7cea9d773b 100644 --- a/lib/uri/wss.rb +++ b/lib/uri/wss.rb @@ -18,5 +18,6 @@ module URI # A Default port of 443 for URI::WSS DEFAULT_PORT = 443 end - @@schemes['WSS'] = WSS + + register_scheme 'WSS', WSS end diff --git a/test/uri/test_common.rb b/test/uri/test_common.rb index 1afa35f93d..3d281758f4 100644 --- a/test/uri/test_common.rb +++ b/test/uri/test_common.rb @@ -33,6 +33,26 @@ class TestCommon < Test::Unit::TestCase 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 EnvUtil.suppress_warning do assert_instance_of Regexp, URI.regexp