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 +4 -4
- data/lib/thread_safe/cache.rb +17 -3
- data/lib/thread_safe/version.rb +1 -1
- data/test/test_array.rb +11 -13
- data/test/test_cache.rb +108 -28
- data/test/test_cache_loops.rb +8 -11
- data/test/test_hash.rb +10 -12
- data/test/test_helper.rb +11 -1
- data/test/test_synchronized_delegator.rb +2 -2
- data/thread_safe.gemspec +2 -0
- metadata +16 -3
- data/.gitignore +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ae6423a7b9dcc55d72b4902edbe19f7250be3e7a
|
4
|
+
data.tar.gz: 6ebfedec76eb35f880df879162da2cdcd5690882
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 54b9546b99a8ca9414f17d1e4f210c5b4938028df6dcf8f43ffdcbb51e25b5cd2dea3e7dbc36e8bb8aabc0ec48845105f9d2b1ff42b2334ad8332f1d4b656ec2
|
7
|
+
data.tar.gz: 1b681c10cd317386b4f49668433889f7e8962eac41a7b8e2be208ac07d5c3758b2ef1c0a278095aa8a4bbb901ed7d071ed9887892fdc463cd864915aa2e3ae26
|
data/lib/thread_safe/cache.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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)
|
data/lib/thread_safe/version.rb
CHANGED
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
|
4
|
+
class TestArray < Minitest::Test
|
5
5
|
def test_concurrency
|
6
6
|
ary = ThreadSafe::Array.new
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
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
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
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
|
-
|
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
|
-
|
558
|
-
|
559
|
-
@cache
|
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
|
-
|
787
|
+
assert_raises(NoMethodError) { @cache.freeze }
|
708
788
|
end
|
709
789
|
|
710
790
|
def test_marshal_dump_load
|
711
|
-
|
712
|
-
|
713
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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)
|
data/test/test_cache_loops.rb
CHANGED
@@ -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
|
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
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
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
|
4
|
+
class TestHash < Minitest::Test
|
5
5
|
def test_concurrency
|
6
6
|
hsh = ThreadSafe::Hash.new
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
1000
|
11
|
-
|
12
|
-
|
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
|
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
|
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
|
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.
|
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-
|
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
|