diff options
-rw-r--r-- | ChangeLog | 9 | ||||
-rw-r--r-- | NEWS | 5 | ||||
-rw-r--r-- | ext/digest/lib/digest.rb | 24 | ||||
-rw-r--r-- | test/digest/digest/foo.rb | 10 | ||||
-rw-r--r-- | test/digest/test_digest.rb | 61 |
5 files changed, 106 insertions, 3 deletions
@@ -1,3 +1,12 @@ +Fri Oct 31 22:19:30 2014 Akinori MUSHA <knu@iDaemons.org> + + * ext/digest/lib/digest.rb (Digest()): This function should now be + thread-safe. If you have a problem with regard to on-demand + loading under a multi-threaded environment, preload "digest/*" + modules on boot or use this method instead of directly + referencing Digest::*. [Bug #9494] + cf. https://github.com/aws/aws-sdk-ruby/issues/525 + Fri Oct 31 21:33:17 2014 Akinori MUSHA <knu@iDaemons.org> * test/digest/test_digest.rb: Drop #!. This no longer runs @@ -128,6 +128,11 @@ with all sufficient information, see the ChangeLog file. === Stdlib updates (outstanding ones only) * Digest + + * Digest() should now be thread-safe. If you have a problem with + regard to on-demand loading under a multi-threaded environment, + preload "digest/*" modules on boot or use this method instead of + directly referencing Digest::*. * Digest::HMAC has been removed just as previously noticed. * Etc diff --git a/ext/digest/lib/digest.rb b/ext/digest/lib/digest.rb index 5f7ebc2237..ef43c46ac3 100644 --- a/ext/digest/lib/digest.rb +++ b/ext/digest/lib/digest.rb @@ -1,6 +1,9 @@ require 'digest.so' module Digest + # A mutex for Digest(). + REQUIRE_MUTEX = Mutex.new + def self.const_missing(name) # :nodoc: case name when :SHA256, :SHA384, :SHA512 @@ -76,15 +79,30 @@ end # call-seq: # Digest(name) -> digest_subclass # -# Returns a Digest subclass by +name+. +# Returns a Digest subclass by +name+ in a thread-safe manner even +# when on-demand loading is involved. # # require 'digest' # # Digest("MD5") # # => Digest::MD5 # -# Digest("Foo") +# Digest(:SHA256) +# # => Digest::SHA256 +# +# Digest(:Foo) # # => LoadError: library not found for class Digest::Foo -- digest/foo def Digest(name) - Digest.const_get(name) + const = name.to_sym + Digest::REQUIRE_MUTEX.synchronize { + # Ignore autoload's because it is void when we have #const_missing + Digest.const_missing(const) + } +rescue LoadError + # Constants do not necessarily rely on digest/*. + if Digest.const_defined?(const) + Digest.const_get(const) + else + raise + end end diff --git a/test/digest/digest/foo.rb b/test/digest/digest/foo.rb new file mode 100644 index 0000000000..d576ef00e0 --- /dev/null +++ b/test/digest/digest/foo.rb @@ -0,0 +1,10 @@ +module Digest + Foo = nil + + sleep 0.2 + + remove_const(:Foo) + + class Foo < Class + end +end diff --git a/test/digest/test_digest.rb b/test/digest/test_digest.rb index f4ec0234f1..ccf0a26b90 100644 --- a/test/digest/test_digest.rb +++ b/test/digest/test_digest.rb @@ -208,4 +208,65 @@ module TestDigest end end end + + class TestDigestParen < Test::Unit::TestCase + def test_sha2 + assert_separately(%w[-rdigest], <<-'end;') + assert_nothing_raised { + Digest(:SHA256).new + Digest(:SHA384).new + Digest(:SHA512).new + } + end; + end + + def test_no_lib + assert_separately(%w[-rdigest], <<-'end;') + class Digest::Nolib < Digest::Class + end + + assert_nothing_raised { + Digest(:Nolib).new + } + end; + end + + def test_no_lib_no_def + assert_separately(%w[-rdigest], <<-'end;') + assert_raise(LoadError) { + Digest(:Nodef).new + } + end; + end + + def test_race + assert_separately(['-rdigest', "-I#{File.dirname(__FILE__)}"], <<-'end;') + assert_nothing_raised { + t = Thread.start { + sleep 0.1 + Digest(:Foo).new + } + Digest(:Foo).new + t.join + } + end; + end + + def test_race_mixed + assert_separately(['-rdigest', "-I#{File.dirname(__FILE__)}"], <<-'end;') + assert_nothing_raised { + t = Thread.start { + sleep 0.1 + Digest::Foo.new + } + Digest(:Foo).new + begin + t.join + rescue NoMethodError, NameError + # NoMethodError is highly likely; NameError is listed just in case + end + } + end; + end + end end |