Skip to content

Commit

Permalink
Make Digest() thread-safe.
Browse files Browse the repository at this point in the history
* 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 ruby#9494]
  cf. aws/aws-sdk-ruby#525

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@48213 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
  • Loading branch information
knu committed Oct 31, 2014
1 parent 7f08e2a commit 9cd5f13
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 3 deletions.
9 changes: 9 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -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
Expand Down
5 changes: 5 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 21 additions & 3 deletions ext/digest/lib/digest.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -76,15 +79,30 @@ def base64digest!
# 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
10 changes: 10 additions & 0 deletions test/digest/digest/foo.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module Digest
Foo = nil

sleep 0.2

remove_const(:Foo)

class Foo < Class
end
end
61 changes: 61 additions & 0 deletions test/digest/test_digest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -208,4 +208,65 @@ def test_initialize_copy_md5_rmd160
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

0 comments on commit 9cd5f13

Please sign in to comment.