thread_safe 0.3.4 → 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,14 +1,21 @@
1
1
  module ThreadSafe
2
2
  class MriCacheBackend < NonConcurrentCacheBackend
3
- # We can get away with a single global write lock (instead of a per-instance one) because of the GVL/green threads.
3
+ # We can get away with a single global write lock (instead of a per-instance
4
+ # one) because of the GVL/green threads.
4
5
  #
5
- # The previous implementation used `Thread.critical` on 1.8 MRI to implement the 4 composed atomic operations (`put_if_absent`, `replace_pair`,
6
- # `replace_if_exists`, `delete_pair`) this however doesn't work for `compute_if_absent` because on 1.8 the Mutex class is itself implemented
7
- # via `Thread.critical` and a call to `Mutex#lock` does not restore the previous `Thread.critical` value (thus any synchronisation clears the
8
- # `Thread.critical` flag and we loose control). This poses a problem as the provided block might use synchronisation on its own.
6
+ # The previous implementation used `Thread.critical` on 1.8 MRI to implement
7
+ # the 4 composed atomic operations (`put_if_absent`, `replace_pair`,
8
+ # `replace_if_exists`, `delete_pair`) this however doesn't work for
9
+ # `compute_if_absent` because on 1.8 the Mutex class is itself implemented
10
+ # via `Thread.critical` and a call to `Mutex#lock` does not restore the
11
+ # previous `Thread.critical` value (thus any synchronisation clears the
12
+ # `Thread.critical` flag and we loose control). This poses a problem as the
13
+ # provided block might use synchronisation on its own.
9
14
  #
10
- # NOTE: a neat idea of writing a c-ext to manually perform atomic put_if_absent, while relying on Ruby not releasing a GVL while calling
11
- # a c-ext will not work because of the potentially Ruby implemented `#hash` and `#eql?` key methods.
15
+ # NOTE: a neat idea of writing a c-ext to manually perform atomic
16
+ # put_if_absent, while relying on Ruby not releasing a GVL while calling a
17
+ # c-ext will not work because of the potentially Ruby implemented `#hash`
18
+ # and `#eql?` key methods.
12
19
  WRITE_LOCK = Mutex.new
13
20
 
14
21
  def []=(key, value)
@@ -1,7 +1,9 @@
1
1
  module ThreadSafe
2
2
  class NonConcurrentCacheBackend
3
- # WARNING: all public methods of the class must operate on the @backend directly without calling each other. This is important
4
- # because of the SynchronizedCacheBackend which uses a non-reentrant mutex for perfomance reasons.
3
+ # WARNING: all public methods of the class must operate on the @backend
4
+ # directly without calling each other. This is important because of the
5
+ # SynchronizedCacheBackend which uses a non-reentrant mutex for perfomance
6
+ # reasons.
5
7
  def initialize(options = nil)
6
8
  @backend = {}
7
9
  end
@@ -2,7 +2,8 @@ module ThreadSafe
2
2
  class SynchronizedCacheBackend < NonConcurrentCacheBackend
3
3
  require 'mutex_m'
4
4
  include Mutex_m
5
- # WARNING: Mutex_m is a non-reentrant lock, so the synchronized methods are not allowed to call each other.
5
+ # WARNING: Mutex_m is a non-reentrant lock, so the synchronized methods are
6
+ # not allowed to call each other.
6
7
 
7
8
  def [](key)
8
9
  synchronize { super }
@@ -9,9 +9,9 @@ require 'monitor'
9
9
  # array = SynchronizedDelegator.new([]) # thread-safe
10
10
  #
11
11
  # A simple `Monitor` provides a very coarse-grained way to synchronize a given
12
- # object, in that it will cause synchronization for methods that have no
13
- # need for it, but this is a trivial way to get thread-safety where none may
14
- # exist currently on some implementations.
12
+ # object, in that it will cause synchronization for methods that have no need
13
+ # for it, but this is a trivial way to get thread-safety where none may exist
14
+ # currently on some implementations.
15
15
  #
16
16
  # This class is currently being considered for inclusion into stdlib, via
17
17
  # https://bugs.ruby-lang.org/issues/8556
@@ -1,7 +1,10 @@
1
1
  module ThreadSafe
2
2
  module Util
3
- # A Ruby port of the Doug Lea's jsr166e.LondAdder class version 1.8 available in public domain.
4
- # Original source code available here: http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/LongAdder.java?revision=1.8
3
+ # A Ruby port of the Doug Lea's jsr166e.LondAdder class version 1.8
4
+ # available in public domain.
5
+ #
6
+ # Original source code available here:
7
+ # http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/LongAdder.java?revision=1.8
5
8
  #
6
9
  # One or more variables that together maintain an initially zero
7
10
  # sum. When updates (method +add+) are contended across threads,
@@ -1,6 +1,7 @@
1
1
  module ThreadSafe
2
2
  module Util
3
- # Provides a cheapest possible (mainly in terms of memory usage) +Mutex+ with the +ConditionVariable+ bundled in.
3
+ # Provides a cheapest possible (mainly in terms of memory usage) +Mutex+
4
+ # with the +ConditionVariable+ bundled in.
4
5
  #
5
6
  # Usage:
6
7
  # class A
@@ -1,69 +1,65 @@
1
1
  module ThreadSafe
2
2
  module Util
3
- # A Ruby port of the Doug Lea's jsr166e.Striped64 class version 1.6 available in public domain.
4
- # Original source code available here: http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/Striped64.java?revision=1.6
3
+ # A Ruby port of the Doug Lea's jsr166e.Striped64 class version 1.6
4
+ # available in public domain.
5
5
  #
6
- # Class holding common representation and mechanics for classes supporting dynamic striping on 64bit values.
6
+ # Original source code available here:
7
+ # http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/Striped64.java?revision=1.6
7
8
  #
8
- # This class maintains a lazily-initialized table of atomically
9
- # updated variables, plus an extra +base+ field. The table size
10
- # is a power of two. Indexing uses masked per-thread hash codes.
11
- # Nearly all methods on this class are private, accessed directly
12
- # by subclasses.
9
+ # Class holding common representation and mechanics for classes supporting
10
+ # dynamic striping on 64bit values.
13
11
  #
14
- # Table entries are of class +Cell+; a variant of AtomicLong padded
15
- # to reduce cache contention on most processors. Padding is
16
- # overkill for most Atomics because they are usually irregularly
17
- # scattered in memory and thus don't interfere much with each
18
- # other. But Atomic objects residing in arrays will tend to be
19
- # placed adjacent to each other, and so will most often share
20
- # cache lines (with a huge negative performance impact) without
12
+ # This class maintains a lazily-initialized table of atomically updated
13
+ # variables, plus an extra +base+ field. The table size is a power of two.
14
+ # Indexing uses masked per-thread hash codes. Nearly all methods on this
15
+ # class are private, accessed directly by subclasses.
16
+ #
17
+ # Table entries are of class +Cell+; a variant of AtomicLong padded to
18
+ # reduce cache contention on most processors. Padding is overkill for most
19
+ # Atomics because they are usually irregularly scattered in memory and thus
20
+ # don't interfere much with each other. But Atomic objects residing in
21
+ # arrays will tend to be placed adjacent to each other, and so will most
22
+ # often share cache lines (with a huge negative performance impact) without
21
23
  # this precaution.
22
24
  #
23
- # In part because +Cell+s are relatively large, we avoid creating
24
- # them until they are needed. When there is no contention, all
25
- # updates are made to the +base+ field. Upon first contention (a
26
- # failed CAS on +base+ update), the table is initialized to size 2.
27
- # The table size is doubled upon further contention until
28
- # reaching the nearest power of two greater than or equal to the
29
- # number of CPUS. Table slots remain empty (+nil+) until they are
25
+ # In part because +Cell+s are relatively large, we avoid creating them until
26
+ # they are needed. When there is no contention, all updates are made to the
27
+ # +base+ field. Upon first contention (a failed CAS on +base+ update), the
28
+ # table is initialized to size 2. The table size is doubled upon further
29
+ # contention until reaching the nearest power of two greater than or equal
30
+ # to the number of CPUS. Table slots remain empty (+nil+) until they are
30
31
  # needed.
31
32
  #
32
- # A single spinlock (+busy+) is used for initializing and
33
- # resizing the table, as well as populating slots with new +Cell+s.
34
- # There is no need for a blocking lock: When the lock is not
35
- # available, threads try other slots (or the base). During these
36
- # retries, there is increased contention and reduced locality,
37
- # which is still better than alternatives.
33
+ # A single spinlock (+busy+) is used for initializing and resizing the
34
+ # table, as well as populating slots with new +Cell+s. There is no need for
35
+ # a blocking lock: When the lock is not available, threads try other slots
36
+ # (or the base). During these retries, there is increased contention and
37
+ # reduced locality, which is still better than alternatives.
38
38
  #
39
- # Per-thread hash codes are initialized to random values.
40
- # Contention and/or table collisions are indicated by failed
41
- # CASes when performing an update operation (see method
42
- # +retry_update+). Upon a collision, if the table size is less than
43
- # the capacity, it is doubled in size unless some other thread
44
- # holds the lock. If a hashed slot is empty, and lock is
45
- # available, a new +Cell+ is created. Otherwise, if the slot
46
- # exists, a CAS is tried. Retries proceed by "double hashing",
47
- # using a secondary hash (XorShift) to try to find a
48
- # free slot.
39
+ # Per-thread hash codes are initialized to random values. Contention and/or
40
+ # table collisions are indicated by failed CASes when performing an update
41
+ # operation (see method +retry_update+). Upon a collision, if the table size
42
+ # is less than the capacity, it is doubled in size unless some other thread
43
+ # holds the lock. If a hashed slot is empty, and lock is available, a new
44
+ # +Cell+ is created. Otherwise, if the slot exists, a CAS is tried. Retries
45
+ # proceed by "double hashing", using a secondary hash (XorShift) to try to
46
+ # find a free slot.
49
47
  #
50
- # The table size is capped because, when there are more threads
51
- # than CPUs, supposing that each thread were bound to a CPU,
52
- # there would exist a perfect hash function mapping threads to
53
- # slots that eliminates collisions. When we reach capacity, we
54
- # search for this mapping by randomly varying the hash codes of
55
- # colliding threads. Because search is random, and collisions
56
- # only become known via CAS failures, convergence can be slow,
57
- # and because threads are typically not bound to CPUS forever,
58
- # may not occur at all. However, despite these limitations,
59
- # observed contention rates are typically low in these cases.
48
+ # The table size is capped because, when there are more threads than CPUs,
49
+ # supposing that each thread were bound to a CPU, there would exist a
50
+ # perfect hash function mapping threads to slots that eliminates collisions.
51
+ # When we reach capacity, we search for this mapping by randomly varying the
52
+ # hash codes of colliding threads. Because search is random, and collisions
53
+ # only become known via CAS failures, convergence can be slow, and because
54
+ # threads are typically not bound to CPUS forever, may not occur at all.
55
+ # However, despite these limitations, observed contention rates are
56
+ # typically low in these cases.
60
57
  #
61
- # It is possible for a +Cell+ to become unused when threads that
62
- # once hashed to it terminate, as well as in the case where
63
- # doubling the table causes no thread to hash to it under
64
- # expanded mask. We do not try to detect or remove such cells,
65
- # under the assumption that for long-running instances, observed
66
- # contention levels will recur, so the cells will eventually be
58
+ # It is possible for a +Cell+ to become unused when threads that once hashed
59
+ # to it terminate, as well as in the case where doubling the table causes no
60
+ # thread to hash to it under expanded mask. We do not try to detect or
61
+ # remove such cells, under the assumption that for long-running instances,
62
+ # observed contention levels will recur, so the cells will eventually be
67
63
  # needed again; and for short-lived ones, it does not matter.
68
64
  class Striped64
69
65
  # Padded variant of AtomicLong supporting only raw accesses plus CAS.
@@ -85,8 +81,8 @@ module ThreadSafe
85
81
 
86
82
  extend Volatile
87
83
  attr_volatile :cells, # Table of cells. When non-null, size is a power of 2.
88
- :base, # Base value, used mainly when there is no contention, but also as a fallback during table initialization races. Updated via CAS.
89
- :busy # Spinlock (locked via CAS) used when resizing and/or creating Cells.
84
+ :base, # Base value, used mainly when there is no contention, but also as a fallback during table initialization races. Updated via CAS.
85
+ :busy # Spinlock (locked via CAS) used when resizing and/or creating Cells.
90
86
 
91
87
  alias_method :busy?, :busy
92
88
 
@@ -1,7 +1,9 @@
1
1
  module ThreadSafe
2
2
  module Util
3
3
  module Volatile
4
- # Provides +volatile+ (in the JVM's sense) attribute accessors implemented atop of the +AtomicReference+s.
4
+ # Provides +volatile+ (in the JVM's sense) attribute accessors implemented
5
+ # atop of the +AtomicReference+s.
6
+ #
5
7
  # Usage:
6
8
  # class Foo
7
9
  # extend ThreadSafe::Util::Volatile
@@ -1,7 +1,9 @@
1
1
  module ThreadSafe
2
2
  module Util
3
- # A xorshift random number (positive +Fixnum+s) generator, provides reasonably cheap way to generate thread local random numbers without contending for
4
- # the global +Kernel.rand+.
3
+ # A xorshift random number (positive +Fixnum+s) generator, provides
4
+ # reasonably cheap way to generate thread local random numbers without
5
+ # contending for the global +Kernel.rand+.
6
+ #
5
7
  # Usage:
6
8
  # x = XorShiftRandom.get # uses Kernel.rand to generate an initial seed
7
9
  # while true
@@ -1,5 +1,5 @@
1
1
  module ThreadSafe
2
- VERSION = "0.3.4"
2
+ VERSION = "0.3.5"
3
3
  end
4
4
 
5
5
  # NOTE: <= 0.2.0 used Threadsafe::VERSION
@@ -0,0 +1,45 @@
1
+ require 'yard'
2
+ YARD::Rake::YardocTask.new
3
+
4
+ root = File.expand_path File.join(File.dirname(__FILE__), '..')
5
+
6
+ namespace :yard do
7
+
8
+ cmd = lambda do |command|
9
+ puts ">> executing: #{command}"
10
+ system command or raise "#{command} failed"
11
+ end
12
+
13
+ desc 'Pushes generated documentation to github pages: http://ruby-concurrency.github.io/thread_safe/'
14
+ task :push => [:setup, :yard] do
15
+
16
+ message = Dir.chdir(root) do
17
+ `git log -n 1 --oneline`.strip
18
+ end
19
+ puts "Generating commit: #{message}"
20
+
21
+ Dir.chdir "#{root}/yardoc" do
22
+ cmd.call "git add -A"
23
+ cmd.call "git commit -m '#{message}'"
24
+ cmd.call 'git push origin gh-pages'
25
+ end
26
+
27
+ end
28
+
29
+ desc 'Setups second clone in ./yardoc dir for pushing doc to github'
30
+ task :setup do
31
+
32
+ unless File.exist? "#{root}/yardoc/.git"
33
+ cmd.call "rm -rf #{root}/yardoc" if File.exist?("#{root}/yardoc")
34
+ Dir.chdir "#{root}" do
35
+ cmd.call 'git clone --single-branch --branch gh-pages git@github.com:ruby-concurrency/thread_safe.git ./yardoc'
36
+ end
37
+ end
38
+ Dir.chdir "#{root}/yardoc" do
39
+ cmd.call 'git fetch origin'
40
+ cmd.call 'git reset --hard origin/gh-pages'
41
+ end
42
+
43
+ end
44
+
45
+ end
@@ -4,7 +4,7 @@ require File.join(File.dirname(__FILE__), "test_helper")
4
4
  class TestArray < Minitest::Test
5
5
  def test_concurrency
6
6
  ary = ThreadSafe::Array.new
7
- (1..100).map do |i|
7
+ (1..THREADS).map do |i|
8
8
  Thread.new do
9
9
  1000.times do
10
10
  ary << i
@@ -11,7 +11,7 @@ class TestCache < Minitest::Test
11
11
 
12
12
  def test_concurrency
13
13
  cache = @cache
14
- (1..100).map do |i|
14
+ (1..THREADS).map do |i|
15
15
  Thread.new do
16
16
  1000.times do |j|
17
17
  key = i*1000+j
@@ -4,7 +4,7 @@ require File.join(File.dirname(__FILE__), "test_helper")
4
4
  class TestHash < Minitest::Test
5
5
  def test_concurrency
6
6
  hsh = ThreadSafe::Hash.new
7
- (1..100).map do |i|
7
+ (1..THREADS).map do |i|
8
8
  Thread.new do
9
9
  1000.times do |j|
10
10
  hsh[i*1000+j] = i
@@ -1,14 +1,35 @@
1
- require 'thread'
2
- require 'rubygems'
3
- gem 'minitest', '>= 4'
1
+ unless defined?(JRUBY_VERSION)
2
+ require 'simplecov'
3
+ require 'coveralls'
4
+
5
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
6
+ SimpleCov::Formatter::HTMLFormatter,
7
+ Coveralls::SimpleCov::Formatter
8
+ ]
9
+
10
+ SimpleCov.start do
11
+ project_name 'thread_safe'
12
+
13
+ add_filter '/examples/'
14
+ add_filter '/pkg/'
15
+ add_filter '/test/'
16
+ add_filter '/tasks/'
17
+ add_filter '/yard-template/'
18
+ add_filter '/yardoc/'
19
+
20
+ command_name 'Mintest'
21
+ end
22
+ end
23
+
4
24
  require 'minitest/autorun'
5
25
 
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
26
+ require 'minitest/reporters'
27
+ Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new(color: true)
28
+
29
+ require 'thread'
30
+ require 'thread_safe'
31
+
32
+ THREADS = (RUBY_ENGINE == 'ruby' ? 100 : 10)
12
33
 
13
34
  if defined?(JRUBY_VERSION) && ENV['TEST_NO_UNSAFE']
14
35
  # to be used like this: rake test TEST_NO_UNSAFE=true
@@ -7,7 +7,7 @@ Gem::Specification.new do |gem|
7
7
  gem.email = ["headius@headius.com", "thedarkone2@gmail.com"]
8
8
  gem.description = %q{Thread-safe collections and utilities for Ruby}
9
9
  gem.summary = %q{A collection of data structures and utilities to make thread-safe programming in Ruby easier}
10
- gem.homepage = "https://github.com/headius/thread_safe"
10
+ gem.homepage = "https://github.com/ruby-concurrency/thread_safe"
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)
@@ -20,7 +20,7 @@ Gem::Specification.new do |gem|
20
20
  gem.version = ThreadSafe::VERSION
21
21
  gem.license = "Apache-2.0"
22
22
 
23
- gem.add_development_dependency 'atomic', ['>= 1.1.7', '< 2']
23
+ gem.add_development_dependency 'atomic', '= 1.1.16'
24
24
  gem.add_development_dependency 'rake'
25
25
  gem.add_development_dependency 'minitest', '>= 4'
26
26
  end
@@ -0,0 +1,125 @@
1
+ /* Override this file with custom rules */
2
+
3
+ body {
4
+ line-height: 18px;
5
+ }
6
+
7
+ .docstring code, .docstring .object_link a, #filecontents code {
8
+ padding: 0px 3px 1px 3px;
9
+ border: 1px solid #eef;
10
+ background: #f5f5ff;
11
+ }
12
+
13
+ #filecontents pre code, .docstring pre code {
14
+ border: none;
15
+ background: none;
16
+ padding: 0;
17
+ }
18
+
19
+ #filecontents pre.code, .docstring pre.code, .tags pre.example, .docstring code, .docstring .object_link a,
20
+ #filecontents code {
21
+ -moz-border-radius: 2px;
22
+ -webkit-border-radius: 2px;
23
+ }
24
+
25
+ /* syntax highlighting */
26
+ .source_code {
27
+ display: none;
28
+ padding: 3px 8px;
29
+ border-left: 8px solid #ddd;
30
+ margin-top: 5px;
31
+ }
32
+
33
+ #filecontents pre.code, .docstring pre.code, .source_code pre {
34
+ font-family: monospace;
35
+ }
36
+
37
+ #filecontents pre.code, .docstring pre.code {
38
+ display: block;
39
+ }
40
+
41
+ .source_code .lines {
42
+ padding-right: 12px;
43
+ color: #555;
44
+ text-align: right;
45
+ }
46
+
47
+ #filecontents pre.code, .docstring pre.code,
48
+ .tags pre.example {
49
+ padding: 5px 12px;
50
+ margin-top: 4px;
51
+ border: 1px solid #eef;
52
+ background: #f5f5ff;
53
+ }
54
+
55
+ pre.code {
56
+ color: #000;
57
+ }
58
+
59
+ pre.code .info.file {
60
+ color: #555;
61
+ }
62
+
63
+ pre.code .val {
64
+ color: #036A07;
65
+ }
66
+
67
+ pre.code .tstring_content,
68
+ pre.code .heredoc_beg, pre.code .heredoc_end,
69
+ pre.code .qwords_beg, pre.code .qwords_end,
70
+ pre.code .tstring, pre.code .dstring {
71
+ color: #036A07;
72
+ }
73
+
74
+ pre.code .fid,
75
+ pre.code .rubyid_new,
76
+ pre.code .rubyid_to_s,
77
+ pre.code .rubyid_to_sym,
78
+ pre.code .rubyid_to_f,
79
+ pre.code .rubyid_to_i,
80
+ pre.code .rubyid_each {
81
+ color: inherit;
82
+ }
83
+
84
+ pre.code .comment {
85
+ color: #777;
86
+ font-style: italic;
87
+ }
88
+
89
+ pre.code .const, pre.code .constant {
90
+ color: inherit;
91
+ font-weight: bold;
92
+ font-style: italic;
93
+ }
94
+
95
+ pre.code .label,
96
+ pre.code .symbol {
97
+ color: #C5060B;
98
+ }
99
+
100
+ pre.code .kw,
101
+ pre.code .rubyid_require,
102
+ pre.code .rubyid_extend,
103
+ pre.code .rubyid_include,
104
+ pre.code .int {
105
+ color: #0000FF;
106
+ }
107
+
108
+ pre.code .ivar {
109
+ color: #660E7A;
110
+ }
111
+
112
+ pre.code .gvar,
113
+ pre.code .rubyid_backref,
114
+ pre.code .rubyid_nth_ref {
115
+ color: #6D79DE;
116
+ }
117
+
118
+ pre.code .regexp, .dregexp {
119
+ color: #036A07;
120
+ }
121
+
122
+ pre.code a {
123
+ border-bottom: 1px dotted #bbf;
124
+ }
125
+