summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog9
-rw-r--r--NEWS5
-rw-r--r--ext/digest/lib/digest.rb24
-rw-r--r--test/digest/digest/foo.rb10
-rw-r--r--test/digest/test_digest.rb61
5 files changed, 106 insertions, 3 deletions
diff --git a/ChangeLog b/ChangeLog
index 244b6169ef..82bab4d456 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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
diff --git a/NEWS b/NEWS
index 8c46c971a1..333bf38650 100644
--- a/NEWS
+++ b/NEWS
@@ -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