thread_safe 0.3.3 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f01ddce23e913574f7d1b43fb6307097a944bd80
4
- data.tar.gz: c961292af3ebf3409fd37481c705c6c509f1da8a
3
+ metadata.gz: ae6423a7b9dcc55d72b4902edbe19f7250be3e7a
4
+ data.tar.gz: 6ebfedec76eb35f880df879162da2cdcd5690882
5
5
  SHA512:
6
- metadata.gz: 061a7b8615337b3db8a5ce99889c95b3dade77cfa151c6eed42d672593771e7814d5350009438b5b76fb0e2ca2e155b3e185417766673c5ae4c08da8646d8e52
7
- data.tar.gz: f751f6900efa28f043e0bca2db507cd70f215ad2e241c646c9b14dd7ca75eaf0a8b12000e966ae1e4cbe0cf3ac454534ed79ca4c2b3f6fdd0117e5a2da69ec8f
6
+ metadata.gz: 54b9546b99a8ca9414f17d1e4f210c5b4938028df6dcf8f43ffdcbb51e25b5cd2dea3e7dbc36e8bb8aabc0ec48845105f9d2b1ff42b2334ad8332f1d4b656ec2
7
+ data.tar.gz: 1b681c10cd317386b4f49668433889f7e8962eac41a7b8e2be208ac07d5c3758b2ef1c0a278095aa8a4bbb901ed7d071ed9887892fdc463cd864915aa2e3ae26
@@ -35,9 +35,13 @@ module ThreadSafe
35
35
  end
36
36
 
37
37
  def [](key)
38
- if value = super
38
+ if value = super # non-falsy value is an existing mapping, return it right away
39
39
  value
40
- elsif @default_proc && !key?(key)
40
+ # re-check is done with get_or_default(key, NULL) instead of a simple !key?(key) in order to avoid a race condition, whereby by the time the current thread gets to the key?(key) call
41
+ # a key => value mapping might have already been created by a different thread (key?(key) would then return true, this elsif branch wouldn't be taken and an incorrent +nil+ value
42
+ # would be returned)
43
+ # note: nil == value check is not technically necessary
44
+ elsif @default_proc && nil == value && NULL == (value = get_or_default(key, NULL))
41
45
  @default_proc.call(self, key)
42
46
  else
43
47
  value
@@ -55,7 +59,13 @@ module ThreadSafe
55
59
  elsif NULL != default_value
56
60
  default_value
57
61
  else
58
- raise KEY_ERROR, 'key not found'
62
+ raise_fetch_no_key
63
+ end
64
+ end
65
+
66
+ def fetch_or_store(key, default_value = NULL)
67
+ fetch(key) do
68
+ put(key, block_given? ? yield(key) : (NULL == default_value ? raise_fetch_no_key : default_value))
59
69
  end
60
70
  end
61
71
 
@@ -127,6 +137,10 @@ module ThreadSafe
127
137
  undef :freeze
128
138
 
129
139
  private
140
+ def raise_fetch_no_key
141
+ raise KEY_ERROR, 'key not found'
142
+ end
143
+
130
144
  def initialize_copy(other)
131
145
  super
132
146
  populate_from(other)
@@ -1,5 +1,5 @@
1
1
  module ThreadSafe
2
- VERSION = "0.3.3"
2
+ VERSION = "0.3.4"
3
3
  end
4
4
 
5
5
  # NOTE: <= 0.2.0 used Threadsafe::VERSION
data/test/test_array.rb CHANGED
@@ -1,20 +1,18 @@
1
- require 'test/unit'
2
1
  require 'thread_safe'
2
+ require File.join(File.dirname(__FILE__), "test_helper")
3
3
 
4
- class TestArray < Test::Unit::TestCase
4
+ class TestArray < Minitest::Test
5
5
  def test_concurrency
6
6
  ary = ThreadSafe::Array.new
7
- assert_nothing_raised do
8
- (1..100).map do |i|
9
- Thread.new do
10
- 1000.times do
11
- ary << i
12
- ary.each {|x| x * 2}
13
- ary.shift
14
- ary.last
15
- end
7
+ (1..100).map do |i|
8
+ Thread.new do
9
+ 1000.times do
10
+ ary << i
11
+ ary.each {|x| x * 2}
12
+ ary.shift
13
+ ary.last
16
14
  end
17
- end.map(&:join)
18
- end
15
+ end
16
+ end.map(&:join)
19
17
  end
20
18
  end
data/test/test_cache.rb CHANGED
@@ -1,29 +1,26 @@
1
- require 'test/unit'
2
1
  require 'thread_safe'
3
2
  require 'thread'
4
3
  require File.join(File.dirname(__FILE__), "test_helper")
5
4
 
6
5
  Thread.abort_on_exception = true
7
6
 
8
- class TestCache < Test::Unit::TestCase
7
+ class TestCache < Minitest::Test
9
8
  def setup
10
9
  @cache = ThreadSafe::Cache.new
11
10
  end
12
11
 
13
12
  def test_concurrency
14
13
  cache = @cache
15
- assert_nothing_raised do
16
- (1..100).map do |i|
17
- Thread.new do
18
- 1000.times do |j|
19
- key = i*1000+j
20
- cache[key] = i
21
- cache[key]
22
- cache.delete(key)
23
- end
14
+ (1..100).map do |i|
15
+ Thread.new do
16
+ 1000.times do |j|
17
+ key = i*1000+j
18
+ cache[key] = i
19
+ cache[key]
20
+ cache.delete(key)
24
21
  end
25
- end.map(&:join)
26
- end
22
+ end
23
+ end.map(&:join)
27
24
  end
28
25
 
29
26
  def test_retrieval
@@ -470,9 +467,14 @@ class TestCache < Test::Unit::TestCase
470
467
  assert_equal(1, (@cache.fetch(:a) {flunk}))
471
468
  end
472
469
 
473
- assert_raise(ThreadSafe::Cache::KEY_ERROR) do
470
+ assert_raises(ThreadSafe::Cache::KEY_ERROR) do
474
471
  @cache.fetch(:b)
475
472
  end
473
+
474
+ assert_no_size_change do
475
+ assert_equal 1, (@cache.fetch(:b, :c) {1}) # assert block supersedes default value argument
476
+ assert_equal false, @cache.key?(:b)
477
+ end
476
478
  end
477
479
  end
478
480
 
@@ -508,6 +510,86 @@ class TestCache < Test::Unit::TestCase
508
510
  end
509
511
  end
510
512
 
513
+ def test_fetch_or_store
514
+ with_or_without_default_proc do |default_proc_set|
515
+ assert_size_change 1 do
516
+ assert_equal 1, @cache.fetch_or_store(:a, 1)
517
+ assert_equal 1, @cache[:a]
518
+ end
519
+
520
+ @cache.delete(:a)
521
+
522
+ assert_size_change 1 do
523
+ assert_equal 1, (@cache.fetch_or_store(:a) {1})
524
+ assert_equal 1, @cache[:a]
525
+ end
526
+
527
+ assert_no_size_change do
528
+ assert_equal(1, (@cache.fetch_or_store(:a) {flunk}))
529
+ end
530
+
531
+ assert_raises(ThreadSafe::Cache::KEY_ERROR) do
532
+ @cache.fetch_or_store(:b)
533
+ end
534
+
535
+ assert_size_change 1 do
536
+ assert_equal 1, (@cache.fetch_or_store(:b, :c) {1}) # assert block supersedes default value argument
537
+ assert_equal 1, @cache[:b]
538
+ end
539
+ end
540
+ end
541
+
542
+ def test_falsy_fetch_or_store
543
+ with_or_without_default_proc do
544
+ assert_equal false, @cache.key?(:a)
545
+
546
+ assert_size_change 1 do
547
+ assert_equal(nil, @cache.fetch_or_store(:a, nil))
548
+ assert_equal nil, @cache[:a]
549
+ assert_equal true, @cache.key?(:a)
550
+ end
551
+ @cache.delete(:a)
552
+
553
+ assert_size_change 1 do
554
+ assert_equal(false, @cache.fetch_or_store(:a, false))
555
+ assert_equal false, @cache[:a]
556
+ assert_equal true, @cache.key?(:a)
557
+ end
558
+ @cache.delete(:a)
559
+
560
+ assert_size_change 1 do
561
+ assert_equal(nil, (@cache.fetch_or_store(:a) {}))
562
+ assert_equal nil, @cache[:a]
563
+ assert_equal true, @cache.key?(:a)
564
+ end
565
+ @cache.delete(:a)
566
+
567
+ assert_size_change 1 do
568
+ assert_equal(false, (@cache.fetch_or_store(:a) {false}))
569
+ assert_equal false, @cache[:a]
570
+ assert_equal true, @cache.key?(:a)
571
+ end
572
+
573
+ @cache[:a] = nil
574
+ assert_no_size_change do
575
+ assert_equal(nil, (@cache.fetch_or_store(:a) {flunk}))
576
+ end
577
+ end
578
+ end
579
+
580
+ def test_fetch_or_store_with_return
581
+ with_or_without_default_proc do
582
+ r = lambda do
583
+ @cache.fetch_or_store(:a) { return 10 }
584
+ end.call
585
+
586
+ assert_no_size_change do
587
+ assert_equal 10, r
588
+ assert_equal false, @cache.key?(:a)
589
+ end
590
+ end
591
+ end
592
+
511
593
  def test_clear
512
594
  @cache[:a] = 1
513
595
  assert_size_change -1 do
@@ -554,11 +636,9 @@ class TestCache < Test::Unit::TestCase
554
636
  @cache[:b] = 1
555
637
  @cache[:c] = 1
556
638
 
557
- assert_nothing_raised do
558
- assert_size_change 1 do
559
- @cache.each_pair do |k, v|
560
- @cache[:z] = 1
561
- end
639
+ assert_size_change 1 do
640
+ @cache.each_pair do |k, v|
641
+ @cache[:z] = 1
562
642
  end
563
643
  end
564
644
  end
@@ -704,14 +784,13 @@ class TestCache < Test::Unit::TestCase
704
784
  end
705
785
 
706
786
  def test_is_unfreezable
707
- assert_raise(NoMethodError) { @cache.freeze }
787
+ assert_raises(NoMethodError) { @cache.freeze }
708
788
  end
709
789
 
710
790
  def test_marshal_dump_load
711
- assert_nothing_raised do
712
- new_cache = Marshal.load(Marshal.dump(@cache))
713
- assert_equal 0, new_cache.size
714
- end
791
+ new_cache = Marshal.load(Marshal.dump(@cache))
792
+ assert_instance_of ThreadSafe::Cache, new_cache
793
+ assert_equal 0, new_cache.size
715
794
  @cache[:a] = 1
716
795
  new_cache = Marshal.load(Marshal.dump(@cache))
717
796
  assert_equal 1, @cache[:a]
@@ -719,7 +798,7 @@ class TestCache < Test::Unit::TestCase
719
798
  end
720
799
 
721
800
  def test_marshal_dump_doesnt_work_with_default_proc
722
- assert_raise(TypeError) do
801
+ assert_raises(TypeError) do
723
802
  Marshal.dump(ThreadSafe::Cache.new {})
724
803
  end
725
804
  end
@@ -740,7 +819,8 @@ class TestCache < Test::Unit::TestCase
740
819
  end
741
820
 
742
821
  def assert_valid_options(options)
743
- assert_nothing_raised { ThreadSafe::Cache.new(options) }
822
+ c = ThreadSafe::Cache.new(options)
823
+ assert_instance_of ThreadSafe::Cache, c
744
824
  end
745
825
 
746
826
  def assert_invalid_option(option_name, value)
@@ -748,7 +828,7 @@ class TestCache < Test::Unit::TestCase
748
828
  end
749
829
 
750
830
  def assert_invalid_options(options)
751
- assert_raise(ArgumentError) { ThreadSafe::Cache.new(options) }
831
+ assert_raises(ArgumentError) { ThreadSafe::Cache.new(options) }
752
832
  end
753
833
 
754
834
  def assert_size_change(change, cache = @cache)
@@ -782,7 +862,7 @@ class TestCache < Test::Unit::TestCase
782
862
  before_had_value = before_had_key ? @cache[key] : nil
783
863
 
784
864
  assert_no_size_change do
785
- assert_raise(TestException) do
865
+ assert_raises(TestException) do
786
866
  @cache.send(method, key, *args) { raise TestException, '' }
787
867
  end
788
868
  assert_equal before_had_key, @cache.key?(key)
@@ -1,11 +1,10 @@
1
1
  require 'thread'
2
- require 'test/unit'
3
2
  require 'thread_safe'
4
3
  require File.join(File.dirname(__FILE__), "test_helper")
5
4
 
6
5
  Thread.abort_on_exception = true
7
6
 
8
- class TestCacheTorture < Test::Unit::TestCase # this is not run unless RUBY_VERSION =~ /1\.8/ || ENV['TRAVIS'] (see the end of the file)
7
+ class TestCacheTorture < Minitest::Test # this is not run unless RUBY_VERSION =~ /1\.8/ || ENV['TRAVIS'] (see the end of the file)
9
8
  THREAD_COUNT = 40
10
9
  KEY_COUNT = (((2**13) - 2) * 0.75).to_i # get close to the doubling cliff
11
10
  LOW_KEY_COUNT = (((2**8 ) - 2) * 0.75).to_i # get close to the doubling cliff
@@ -310,15 +309,13 @@ class TestCacheTorture < Test::Unit::TestCase # this is not run unless RUBY_VERS
310
309
  def do_thread_loop(name, code, options = {}, &block)
311
310
  options = DEFAULTS.merge(options)
312
311
  meth = define_loop name, code, options[:prelude]
313
- assert_nothing_raised do
314
- keys = to_keys_array(options[:key_count])
315
- run_thread_loop(meth, keys, options, &block)
316
-
317
- if options[:key_count] > 1
318
- options[:key_count] = (options[:key_count] / 40).to_i
319
- keys = to_hash_collision_keys_array(options[:key_count])
320
- run_thread_loop(meth, keys, options.merge(:loop_count => (options[:loop_count] * 5)), &block)
321
- end
312
+ keys = to_keys_array(options[:key_count])
313
+ run_thread_loop(meth, keys, options, &block)
314
+
315
+ if options[:key_count] > 1
316
+ options[:key_count] = (options[:key_count] / 40).to_i
317
+ keys = to_hash_collision_keys_array(options[:key_count])
318
+ run_thread_loop(meth, keys, options.merge(:loop_count => (options[:loop_count] * 5)), &block)
322
319
  end
323
320
  end
324
321
 
data/test/test_hash.rb CHANGED
@@ -1,19 +1,17 @@
1
- require 'test/unit'
2
1
  require 'thread_safe'
2
+ require File.join(File.dirname(__FILE__), "test_helper")
3
3
 
4
- class TestHash < Test::Unit::TestCase
4
+ class TestHash < Minitest::Test
5
5
  def test_concurrency
6
6
  hsh = ThreadSafe::Hash.new
7
- assert_nothing_raised do
8
- (1..100).map do |i|
9
- Thread.new do
10
- 1000.times do |j|
11
- hsh[i*1000+j] = i
12
- hsh[i*1000+j]
13
- hsh.delete(i*1000+j)
14
- end
7
+ (1..100).map do |i|
8
+ Thread.new do
9
+ 1000.times do |j|
10
+ hsh[i*1000+j] = i
11
+ hsh[i*1000+j]
12
+ hsh.delete(i*1000+j)
15
13
  end
16
- end.map(&:join)
17
- end
14
+ end
15
+ end.map(&:join)
18
16
  end
19
17
  end
data/test/test_helper.rb CHANGED
@@ -1,4 +1,14 @@
1
1
  require 'thread'
2
+ require 'rubygems'
3
+ gem 'minitest', '>= 4'
4
+ require 'minitest/autorun'
5
+
6
+ if Minitest.const_defined?('Test')
7
+ # We're on Minitest 5+. Nothing to do here.
8
+ else
9
+ # Minitest 4 doesn't have Minitest::Test yet.
10
+ Minitest::Test = MiniTest::Unit::TestCase
11
+ end
2
12
 
3
13
  if defined?(JRUBY_VERSION) && ENV['TEST_NO_UNSAFE']
4
14
  # to be used like this: rake test TEST_NO_UNSAFE=true
@@ -10,7 +20,7 @@ if defined?(JRUBY_VERSION) && ENV['TEST_NO_UNSAFE']
10
20
  manager.deny java.lang.RuntimePermission.new("accessClassInPackage.sun.misc")
11
21
  java.lang.System.setSecurityManager manager
12
22
 
13
- class TestNoUnsafe < Test::Unit::TestCase
23
+ class TestNoUnsafe < Minitest::Test
14
24
  def test_security_manager_is_used
15
25
  begin
16
26
  java_import 'sun.misc.Unsafe'
@@ -1,7 +1,7 @@
1
- require 'test/unit'
2
1
  require 'thread_safe/synchronized_delegator.rb'
2
+ require File.join(File.dirname(__FILE__), "test_helper")
3
3
 
4
- class TestSynchronizedDelegator < Test::Unit::TestCase
4
+ class TestSynchronizedDelegator < Minitest::Test
5
5
 
6
6
  def test_wraps_array
7
7
  sync_array = SynchronizedDelegator.new(array = [])
data/thread_safe.gemspec CHANGED
@@ -11,6 +11,7 @@ Gem::Specification.new do |gem|
11
11
 
12
12
  gem.files = `git ls-files`.split($\)
13
13
  gem.files += ['lib/thread_safe/jruby_cache_backend.jar'] if defined?(JRUBY_VERSION)
14
+ gem.files -= ['.gitignore'] # see https://github.com/headius/thread_safe/issues/40#issuecomment-42315441
14
15
  gem.platform = 'java' if defined?(JRUBY_VERSION)
15
16
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
17
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
@@ -21,4 +22,5 @@ Gem::Specification.new do |gem|
21
22
 
22
23
  gem.add_development_dependency 'atomic', ['>= 1.1.7', '< 2']
23
24
  gem.add_development_dependency 'rake'
25
+ gem.add_development_dependency 'minitest', '>= 4'
24
26
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thread_safe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Charles Oliver Nutter
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-04-07 00:00:00.000000000 Z
12
+ date: 2014-05-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: atomic
@@ -45,6 +45,20 @@ dependencies:
45
45
  - - '>='
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
+ - !ruby/object:Gem::Dependency
49
+ name: minitest
50
+ requirement: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '4'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '4'
48
62
  description: Thread-safe collections and utilities for Ruby
49
63
  email:
50
64
  - headius@headius.com
@@ -53,7 +67,6 @@ executables: []
53
67
  extensions: []
54
68
  extra_rdoc_files: []
55
69
  files:
56
- - .gitignore
57
70
  - .travis.yml
58
71
  - Gemfile
59
72
  - LICENSE
data/.gitignore DELETED
@@ -1,22 +0,0 @@
1
- *.gem
2
- *.rbc
3
- .rbx/*
4
- .bundle
5
- .config
6
- .yardoc
7
- Gemfile.lock
8
- InstalledFiles
9
- _yardoc
10
- coverage
11
- doc/
12
- lib/bundler/man
13
- lib/thread_safe/jruby_cache_backend.jar
14
- pkg
15
- rdoc
16
- spec/reports
17
- test/tmp
18
- test/version_tmp
19
- tmp
20
- .DS_Store
21
- *.swp
22
- test/package.jar