thread_safe 0.1.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +144 -0
  5. data/README.md +34 -0
  6. data/Rakefile +36 -0
  7. data/examples/bench_cache.rb +35 -0
  8. data/ext/org/jruby/ext/thread_safe/JRubyCacheBackendLibrary.java +200 -0
  9. data/ext/org/jruby/ext/thread_safe/jsr166e/ConcurrentHashMapV8.java +3842 -0
  10. data/ext/org/jruby/ext/thread_safe/jsr166e/LongAdder.java +204 -0
  11. data/ext/org/jruby/ext/thread_safe/jsr166e/Striped64.java +342 -0
  12. data/ext/org/jruby/ext/thread_safe/jsr166y/ThreadLocalRandom.java +199 -0
  13. data/ext/thread_safe/JrubyCacheBackendService.java +15 -0
  14. data/lib/thread_safe.rb +65 -0
  15. data/lib/thread_safe/atomic_reference_cache_backend.rb +922 -0
  16. data/lib/thread_safe/cache.rb +137 -0
  17. data/lib/thread_safe/mri_cache_backend.rb +62 -0
  18. data/lib/thread_safe/non_concurrent_cache_backend.rb +133 -0
  19. data/lib/thread_safe/synchronized_cache_backend.rb +76 -0
  20. data/lib/thread_safe/synchronized_delegator.rb +35 -0
  21. data/lib/thread_safe/util.rb +16 -0
  22. data/lib/thread_safe/util/adder.rb +59 -0
  23. data/lib/thread_safe/util/atomic_reference.rb +12 -0
  24. data/lib/thread_safe/util/cheap_lockable.rb +105 -0
  25. data/lib/thread_safe/util/power_of_two_tuple.rb +26 -0
  26. data/lib/thread_safe/util/striped64.rb +226 -0
  27. data/lib/thread_safe/util/volatile.rb +62 -0
  28. data/lib/thread_safe/util/volatile_tuple.rb +46 -0
  29. data/lib/thread_safe/util/xor_shift_random.rb +39 -0
  30. data/lib/thread_safe/version.rb +3 -0
  31. data/test/test_array.rb +20 -0
  32. data/test/test_cache.rb +792 -0
  33. data/test/test_cache_loops.rb +453 -0
  34. data/test/test_hash.rb +20 -0
  35. data/test/test_helper.rb +73 -0
  36. data/test/test_synchronized_delegator.rb +42 -0
  37. data/thread_safe.gemspec +21 -0
  38. metadata +100 -0
@@ -0,0 +1,199 @@
1
+ /*
2
+ * Written by Doug Lea with assistance from members of JCP JSR-166
3
+ * Expert Group and released to the public domain, as explained at
4
+ * http://creativecommons.org/publicdomain/zero/1.0/
5
+ */
6
+
7
+ // This is based on 1.16 version
8
+
9
+ package org.jruby.ext.thread_safe.jsr166y;
10
+
11
+ import java.util.Random;
12
+
13
+ /**
14
+ * A random number generator isolated to the current thread. Like the
15
+ * global {@link java.util.Random} generator used by the {@link
16
+ * java.lang.Math} class, a {@code ThreadLocalRandom} is initialized
17
+ * with an internally generated seed that may not otherwise be
18
+ * modified. When applicable, use of {@code ThreadLocalRandom} rather
19
+ * than shared {@code Random} objects in concurrent programs will
20
+ * typically encounter much less overhead and contention. Use of
21
+ * {@code ThreadLocalRandom} is particularly appropriate when multiple
22
+ * tasks (for example, each a {@link ForkJoinTask}) use random numbers
23
+ * in parallel in thread pools.
24
+ *
25
+ * <p>Usages of this class should typically be of the form:
26
+ * {@code ThreadLocalRandom.current().nextX(...)} (where
27
+ * {@code X} is {@code Int}, {@code Long}, etc).
28
+ * When all usages are of this form, it is never possible to
29
+ * accidently share a {@code ThreadLocalRandom} across multiple threads.
30
+ *
31
+ * <p>This class also provides additional commonly used bounded random
32
+ * generation methods.
33
+ *
34
+ * @since 1.7
35
+ * @author Doug Lea
36
+ */
37
+ public class ThreadLocalRandom extends Random {
38
+ // same constants as Random, but must be redeclared because private
39
+ private static final long multiplier = 0x5DEECE66DL;
40
+ private static final long addend = 0xBL;
41
+ private static final long mask = (1L << 48) - 1;
42
+
43
+ /**
44
+ * The random seed. We can't use super.seed.
45
+ */
46
+ private long rnd;
47
+
48
+ /**
49
+ * Initialization flag to permit calls to setSeed to succeed only
50
+ * while executing the Random constructor. We can't allow others
51
+ * since it would cause setting seed in one part of a program to
52
+ * unintentionally impact other usages by the thread.
53
+ */
54
+ boolean initialized;
55
+
56
+ // Padding to help avoid memory contention among seed updates in
57
+ // different TLRs in the common case that they are located near
58
+ // each other.
59
+ private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
60
+
61
+ /**
62
+ * The actual ThreadLocal
63
+ */
64
+ private static final ThreadLocal<ThreadLocalRandom> localRandom =
65
+ new ThreadLocal<ThreadLocalRandom>() {
66
+ protected ThreadLocalRandom initialValue() {
67
+ return new ThreadLocalRandom();
68
+ }
69
+ };
70
+
71
+
72
+ /**
73
+ * Constructor called only by localRandom.initialValue.
74
+ */
75
+ ThreadLocalRandom() {
76
+ super();
77
+ initialized = true;
78
+ }
79
+
80
+ /**
81
+ * Returns the current thread's {@code ThreadLocalRandom}.
82
+ *
83
+ * @return the current thread's {@code ThreadLocalRandom}
84
+ */
85
+ public static ThreadLocalRandom current() {
86
+ return localRandom.get();
87
+ }
88
+
89
+ /**
90
+ * Throws {@code UnsupportedOperationException}. Setting seeds in
91
+ * this generator is not supported.
92
+ *
93
+ * @throws UnsupportedOperationException always
94
+ */
95
+ public void setSeed(long seed) {
96
+ if (initialized)
97
+ throw new UnsupportedOperationException();
98
+ rnd = (seed ^ multiplier) & mask;
99
+ }
100
+
101
+ protected int next(int bits) {
102
+ rnd = (rnd * multiplier + addend) & mask;
103
+ return (int) (rnd >>> (48-bits));
104
+ }
105
+
106
+ /**
107
+ * Returns a pseudorandom, uniformly distributed value between the
108
+ * given least value (inclusive) and bound (exclusive).
109
+ *
110
+ * @param least the least value returned
111
+ * @param bound the upper bound (exclusive)
112
+ * @throws IllegalArgumentException if least greater than or equal
113
+ * to bound
114
+ * @return the next value
115
+ */
116
+ public int nextInt(int least, int bound) {
117
+ if (least >= bound)
118
+ throw new IllegalArgumentException();
119
+ return nextInt(bound - least) + least;
120
+ }
121
+
122
+ /**
123
+ * Returns a pseudorandom, uniformly distributed value
124
+ * between 0 (inclusive) and the specified value (exclusive).
125
+ *
126
+ * @param n the bound on the random number to be returned. Must be
127
+ * positive.
128
+ * @return the next value
129
+ * @throws IllegalArgumentException if n is not positive
130
+ */
131
+ public long nextLong(long n) {
132
+ if (n <= 0)
133
+ throw new IllegalArgumentException("n must be positive");
134
+ // Divide n by two until small enough for nextInt. On each
135
+ // iteration (at most 31 of them but usually much less),
136
+ // randomly choose both whether to include high bit in result
137
+ // (offset) and whether to continue with the lower vs upper
138
+ // half (which makes a difference only if odd).
139
+ long offset = 0;
140
+ while (n >= Integer.MAX_VALUE) {
141
+ int bits = next(2);
142
+ long half = n >>> 1;
143
+ long nextn = ((bits & 2) == 0) ? half : n - half;
144
+ if ((bits & 1) == 0)
145
+ offset += n - nextn;
146
+ n = nextn;
147
+ }
148
+ return offset + nextInt((int) n);
149
+ }
150
+
151
+ /**
152
+ * Returns a pseudorandom, uniformly distributed value between the
153
+ * given least value (inclusive) and bound (exclusive).
154
+ *
155
+ * @param least the least value returned
156
+ * @param bound the upper bound (exclusive)
157
+ * @return the next value
158
+ * @throws IllegalArgumentException if least greater than or equal
159
+ * to bound
160
+ */
161
+ public long nextLong(long least, long bound) {
162
+ if (least >= bound)
163
+ throw new IllegalArgumentException();
164
+ return nextLong(bound - least) + least;
165
+ }
166
+
167
+ /**
168
+ * Returns a pseudorandom, uniformly distributed {@code double} value
169
+ * between 0 (inclusive) and the specified value (exclusive).
170
+ *
171
+ * @param n the bound on the random number to be returned. Must be
172
+ * positive.
173
+ * @return the next value
174
+ * @throws IllegalArgumentException if n is not positive
175
+ */
176
+ public double nextDouble(double n) {
177
+ if (n <= 0)
178
+ throw new IllegalArgumentException("n must be positive");
179
+ return nextDouble() * n;
180
+ }
181
+
182
+ /**
183
+ * Returns a pseudorandom, uniformly distributed value between the
184
+ * given least value (inclusive) and bound (exclusive).
185
+ *
186
+ * @param least the least value returned
187
+ * @param bound the upper bound (exclusive)
188
+ * @return the next value
189
+ * @throws IllegalArgumentException if least greater than or equal
190
+ * to bound
191
+ */
192
+ public double nextDouble(double least, double bound) {
193
+ if (least >= bound)
194
+ throw new IllegalArgumentException();
195
+ return nextDouble() * (bound - least) + least;
196
+ }
197
+
198
+ private static final long serialVersionUID = -5851777807851030925L;
199
+ }
@@ -0,0 +1,15 @@
1
+ package thread_safe;
2
+
3
+ import java.io.IOException;
4
+
5
+ import org.jruby.Ruby;
6
+ import org.jruby.ext.thread_safe.JRubyCacheBackendLibrary;
7
+ import org.jruby.runtime.load.BasicLibraryService;
8
+
9
+ // can't name this JRubyCacheBackendService or else JRuby doesn't pick this up
10
+ public class JrubyCacheBackendService implements BasicLibraryService {
11
+ public boolean basicLoad(final Ruby runtime) throws IOException {
12
+ new JRubyCacheBackendLibrary().load(runtime, false);
13
+ return true;
14
+ }
15
+ }
@@ -0,0 +1,65 @@
1
+ require 'thread_safe/version'
2
+ require 'thread_safe/synchronized_delegator'
3
+
4
+ module ThreadSafe
5
+ autoload :Cache, 'thread_safe/cache'
6
+ autoload :Util, 'thread_safe/util'
7
+
8
+ # Various classes within allows for +nil+ values to be stored, so a special +NULL+ token is required to indicate the "nil-ness".
9
+ NULL = Object.new
10
+
11
+ if defined?(JRUBY_VERSION)
12
+ require 'jruby/synchronized'
13
+
14
+ # A thread-safe subclass of Array. This version locks
15
+ # against the object itself for every method call,
16
+ # ensuring only one thread can be reading or writing
17
+ # at a time. This includes iteration methods like
18
+ # #each.
19
+ class Array < ::Array
20
+ include JRuby::Synchronized
21
+ end
22
+
23
+ # A thread-safe subclass of Hash. This version locks
24
+ # against the object itself for every method call,
25
+ # ensuring only one thread can be reading or writing
26
+ # at a time. This includes iteration methods like
27
+ # #each.
28
+ class Hash < ::Hash
29
+ include JRuby::Synchronized
30
+ end
31
+ elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby'
32
+ # Because MRI never runs code in parallel, the existing
33
+ # non-thread-safe structures should usually work fine.
34
+ Array = ::Array
35
+ Hash = ::Hash
36
+ elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
37
+ require 'monitor'
38
+
39
+ class Hash < ::Hash; end
40
+ class Array < ::Array; end
41
+
42
+ [Hash, Array].each do |klass|
43
+ klass.class_eval do
44
+ private
45
+ def _mon_initialize
46
+ @_monitor = Monitor.new unless @_monitor # avoid double initialisation
47
+ end
48
+
49
+ def self.allocate
50
+ obj = super
51
+ obj.send(:_mon_initialize)
52
+ obj
53
+ end
54
+ end
55
+
56
+ klass.superclass.instance_methods(false).each do |method|
57
+ klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
58
+ def #{method}(*args)
59
+ @_monitor.synchronize { super }
60
+ end
61
+ RUBY_EVAL
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,922 @@
1
+ module ThreadSafe
2
+ # A Ruby port of the Doug Lea's jsr166e.ConcurrentHashMapV8 class version 1.59 available in public domain.
3
+ # Original source code available here: http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/ConcurrentHashMapV8.java?revision=1.59
4
+ #
5
+ # The Ruby port skips out the +TreeBin+ (red-black trees for use in bins
6
+ # whose size exceeds a threshold).
7
+ #
8
+ # A hash table supporting full concurrency of retrievals and
9
+ # high expected concurrency for updates. However, even though all
10
+ # operations are thread-safe, retrieval operations do _not_ entail locking,
11
+ # and there is _not_ any support for locking the entire table
12
+ # in a way that prevents all access.
13
+ #
14
+ # Retrieval operations generally do not block, so may overlap with
15
+ # update operations. Retrievals reflect the results of the most
16
+ # recently _completed_ update operations holding upon their
17
+ # onset. (More formally, an update operation for a given key bears a
18
+ # _happens-before_ relation with any (non +nil+) retrieval for
19
+ # that key reporting the updated value.) For aggregate operations
20
+ # such as +clear()+, concurrent retrievals may reflect insertion or removal
21
+ # of only some entries. Similarly, the +each_pair+ iterator yields elements
22
+ # reflecting the state of the hash table at some point at or since
23
+ # the start of the +each_pair+. Bear in mind that the results of
24
+ # aggregate status methods including +size()+ and +empty?+} are typically
25
+ # useful only when a map is not undergoing concurrent updates in other
26
+ # threads. Otherwise the results of these methods reflect transient
27
+ # states that may be adequate for monitoring or estimation purposes, but not
28
+ # for program control.
29
+ #
30
+ # The table is dynamically expanded when there are too many
31
+ # collisions (i.e., keys that have distinct hash codes but fall into
32
+ # the same slot modulo the table size), with the expected average
33
+ # effect of maintaining roughly two bins per mapping (corresponding
34
+ # to a 0.75 load factor threshold for resizing). There may be much
35
+ # variance around this average as mappings are added and removed, but
36
+ # overall, this maintains a commonly accepted time/space tradeoff for
37
+ # hash tables. However, resizing this or any other kind of hash
38
+ # table may be a relatively slow operation. When possible, it is a
39
+ # good idea to provide a size estimate as an optional :initial_capacity
40
+ # initializer argument. An additional optional :load_factor constructor
41
+ # argument provides a further means of customizing initial table capacity
42
+ # by specifying the table density to be used in calculating the amount of
43
+ # space to allocate for the given number of elements. Note that using
44
+ # many keys with exactly the same +hash+ is a sure way to slow down
45
+ # performance of any hash table.
46
+ #
47
+ # == Design overview
48
+ #
49
+ # The primary design goal of this hash table is to maintain
50
+ # concurrent readability (typically method +[]+, but also
51
+ # iteration and related methods) while minimizing update
52
+ # contention. Secondary goals are to keep space consumption about
53
+ # the same or better than plain +Hash+, and to support high
54
+ # initial insertion rates on an empty table by many threads.
55
+ #
56
+ # Each key-value mapping is held in a +Node+. The validation-based
57
+ # approach explained below leads to a lot of code sprawl because
58
+ # retry-control precludes factoring into smaller methods.
59
+ #
60
+ # The table is lazily initialized to a power-of-two size upon the
61
+ # first insertion. Each bin in the table normally contains a
62
+ # list of +Node+s (most often, the list has only zero or one +Node+).
63
+ # Table accesses require volatile/atomic reads, writes, and
64
+ # CASes. The lists of nodes within bins are always accurately traversable
65
+ # under volatile reads, so long as lookups check hash code
66
+ # and non-nullness of value before checking key equality.
67
+ #
68
+ # We use the top two bits of +Node+ hash fields for control
69
+ # purposes -- they are available anyway because of addressing
70
+ # constraints. As explained further below, these top bits are
71
+ # used as follows:
72
+ # 00 - Normal
73
+ # 01 - Locked
74
+ # 11 - Locked and may have a thread waiting for lock
75
+ # 10 - +Node+ is a forwarding node
76
+ #
77
+ # The lower 28 bits of each +Node+'s hash field contain a
78
+ # the key's hash code, except for forwarding nodes, for which
79
+ # the lower bits are zero (and so always have hash field == +MOVED+).
80
+ #
81
+ # Insertion (via +[]=+ or its variants) of the first node in an
82
+ # empty bin is performed by just CASing it to the bin. This is
83
+ # by far the most common case for put operations under most
84
+ # key/hash distributions. Other update operations (insert,
85
+ # delete, and replace) require locks. We do not want to waste
86
+ # the space required to associate a distinct lock object with
87
+ # each bin, so instead use the first node of a bin list itself as
88
+ # a lock. Blocking support for these locks relies +Util::CheapLockable.
89
+ # However, we also need a +try_lock+ construction, so we overlay
90
+ # these by using bits of the +Node+ hash field for lock control (see above),
91
+ # and so normally use builtin monitors only for blocking and signalling using
92
+ # +cheap_wait+/+cheap_broadcast+ constructions. See +Node#try_await_lock+.
93
+ #
94
+ # Using the first node of a list as a lock does not by itself
95
+ # suffice though: When a node is locked, any update must first
96
+ # validate that it is still the first node after locking it, and
97
+ # retry if not. Because new nodes are always appended to lists,
98
+ # once a node is first in a bin, it remains first until deleted
99
+ # or the bin becomes invalidated (upon resizing). However,
100
+ # operations that only conditionally update may inspect nodes
101
+ # until the point of update. This is a converse of sorts to the
102
+ # lazy locking technique described by Herlihy & Shavit.
103
+ #
104
+ # The main disadvantage of per-bin locks is that other update
105
+ # operations on other nodes in a bin list protected by the same
106
+ # lock can stall, for example when user +eql?+ or mapping
107
+ # functions take a long time. However, statistically, under
108
+ # random hash codes, this is not a common problem. Ideally, the
109
+ # frequency of nodes in bins follows a Poisson distribution
110
+ # (http://en.wikipedia.org/wiki/Poisson_distribution) with a
111
+ # parameter of about 0.5 on average, given the resizing threshold
112
+ # of 0.75, although with a large variance because of resizing
113
+ # granularity. Ignoring variance, the expected occurrences of
114
+ # list size k are (exp(-0.5) * pow(0.5, k) / factorial(k)). The
115
+ # first values are:
116
+ #
117
+ # 0: 0.60653066
118
+ # 1: 0.30326533
119
+ # 2: 0.07581633
120
+ # 3: 0.01263606
121
+ # 4: 0.00157952
122
+ # 5: 0.00015795
123
+ # 6: 0.00001316
124
+ # 7: 0.00000094
125
+ # 8: 0.00000006
126
+ # more: less than 1 in ten million
127
+ #
128
+ # Lock contention probability for two threads accessing distinct
129
+ # elements is roughly 1 / (8 * #elements) under random hashes.
130
+ #
131
+ # The table is resized when occupancy exceeds a percentage
132
+ # threshold (nominally, 0.75, but see below). Only a single
133
+ # thread performs the resize (using field +size_control+, to arrange
134
+ # exclusion), but the table otherwise remains usable for reads
135
+ # and updates. Resizing proceeds by transferring bins, one by
136
+ # one, from the table to the next table. Because we are using
137
+ # power-of-two expansion, the elements from each bin must either
138
+ # stay at same index, or move with a power of two offset. We
139
+ # eliminate unnecessary node creation by catching cases where old
140
+ # nodes can be reused because their next fields won't change. On
141
+ # average, only about one-sixth of them need cloning when a table
142
+ # doubles. The nodes they replace will be garbage collectable as
143
+ # soon as they are no longer referenced by any reader thread that
144
+ # may be in the midst of concurrently traversing table. Upon
145
+ # transfer, the old table bin contains only a special forwarding
146
+ # node (with hash field +MOVED+) that contains the next table as
147
+ # its key. On encountering a forwarding node, access and update
148
+ # operations restart, using the new table.
149
+ #
150
+ # Each bin transfer requires its bin lock. However, unlike other
151
+ # cases, a transfer can skip a bin if it fails to acquire its
152
+ # lock, and revisit it later. Method +rebuild+ maintains a buffer of
153
+ # TRANSFER_BUFFER_SIZE bins that have been skipped because of failure
154
+ # to acquire a lock, and blocks only if none are available
155
+ # (i.e., only very rarely). The transfer operation must also ensure
156
+ # that all accessible bins in both the old and new table are usable by
157
+ # any traversal. When there are no lock acquisition failures, this is
158
+ # arranged simply by proceeding from the last bin (+table.size - 1+) up
159
+ # towards the first. Upon seeing a forwarding node, traversals arrange
160
+ # to move to the new table without revisiting nodes. However, when any
161
+ # node is skipped during a transfer, all earlier table bins may have
162
+ # become visible, so are initialized with a reverse-forwarding node back
163
+ # to the old table until the new ones are established. (This
164
+ # sometimes requires transiently locking a forwarding node, which
165
+ # is possible under the above encoding.) These more expensive
166
+ # mechanics trigger only when necessary.
167
+ #
168
+ # The traversal scheme also applies to partial traversals of
169
+ # ranges of bins (via an alternate Traverser constructor)
170
+ # to support partitioned aggregate operations. Also, read-only
171
+ # operations give up if ever forwarded to a null table, which
172
+ # provides support for shutdown-style clearing, which is also not
173
+ # currently implemented.
174
+ #
175
+ # Lazy table initialization minimizes footprint until first use.
176
+ #
177
+ # The element count is maintained using a +ThreadSafe::Util::Adder+,
178
+ # which avoids contention on updates but can encounter cache thrashing
179
+ # if read too frequently during concurrent access. To avoid reading so
180
+ # often, resizing is attempted either when a bin lock is
181
+ # contended, or upon adding to a bin already holding two or more
182
+ # nodes (checked before adding in the +x_if_absent+ methods, after
183
+ # adding in others). Under uniform hash distributions, the
184
+ # probability of this occurring at threshold is around 13%,
185
+ # meaning that only about 1 in 8 puts check threshold (and after
186
+ # resizing, many fewer do so). But this approximation has high
187
+ # variance for small table sizes, so we check on any collision
188
+ # for sizes <= 64. The bulk putAll operation further reduces
189
+ # contention by only committing count updates upon these size
190
+ # checks.
191
+ class AtomicReferenceCacheBackend
192
+ class Table < Util::PowerOfTwoTuple
193
+ def cas_new_node(i, hash, key, value)
194
+ cas(i, nil, Node.new(hash, key, value))
195
+ end
196
+
197
+ def try_to_cas_in_computed(i, hash, key)
198
+ succeeded = false
199
+ new_value = nil
200
+ new_node = Node.new(locked_hash = hash | LOCKED, key, NULL)
201
+ if cas(i, nil, new_node)
202
+ begin
203
+ if NULL == (new_value = yield(NULL))
204
+ was_null = true
205
+ else
206
+ new_node.value = new_value
207
+ end
208
+ succeeded = true
209
+ ensure
210
+ volatile_set(i, nil) if !succeeded || was_null
211
+ new_node.unlock_via_hash(locked_hash, hash)
212
+ end
213
+ end
214
+ return succeeded, new_value
215
+ end
216
+
217
+ def try_lock_via_hash(i, node, node_hash)
218
+ node.try_lock_via_hash(node_hash) do
219
+ yield if volatile_get(i) == node
220
+ end
221
+ end
222
+
223
+ def delete_node_at(i, node, predecessor_node)
224
+ if predecessor_node
225
+ predecessor_node.next = node.next
226
+ else
227
+ volatile_set(i, node.next)
228
+ end
229
+ end
230
+ end
231
+
232
+ # Key-value entry. Nodes with a hash field of +MOVED+ are special,
233
+ # and do not contain user keys or values. Otherwise, keys are never +nil+,
234
+ # and +NULL+ +value+ fields indicate that a node is in the process
235
+ # of being deleted or created. For purposes of read-only access, a key may be read
236
+ # before a value, but can only be used after checking value to be +!= NULL+.
237
+ class Node
238
+ extend Util::Volatile
239
+ attr_volatile :hash, :value, :next
240
+
241
+ include Util::CheapLockable
242
+
243
+ bit_shift = Util::FIXNUM_BIT_SIZE - 2 # need 2 bits for ourselves
244
+ # Encodings for special uses of Node hash fields. See above for explanation.
245
+ MOVED = ('10' << ('0' * bit_shift)).to_i(2) # hash field for forwarding nodes
246
+ LOCKED = ('01' << ('0' * bit_shift)).to_i(2) # set/tested only as a bit
247
+ WAITING = ('11' << ('0' * bit_shift)).to_i(2) # both bits set/tested together
248
+ HASH_BITS = ('00' << ('1' * bit_shift)).to_i(2) # usable bits of normal node hash
249
+
250
+ SPIN_LOCK_ATTEMPTS = Util::CPU_COUNT > 1 ? Util::CPU_COUNT * 2 : 0
251
+
252
+ attr_reader :key
253
+
254
+ def initialize(hash, key, value, next_node = nil)
255
+ super()
256
+ @key = key
257
+ self.lazy_set_hash(hash)
258
+ self.lazy_set_value(value)
259
+ self.next = next_node
260
+ end
261
+
262
+ # Spins a while if +LOCKED+ bit set and this node is the first
263
+ # of its bin, and then sets +WAITING+ bits on hash field and
264
+ # blocks (once) if they are still set. It is OK for this
265
+ # method to return even if lock is not available upon exit,
266
+ # which enables these simple single-wait mechanics.
267
+ #
268
+ # The corresponding signalling operation is performed within
269
+ # callers: Upon detecting that +WAITING+ has been set when
270
+ # unlocking lock (via a failed CAS from non-waiting +LOCKED+
271
+ # state), unlockers acquire the +cheap_synchronize+ lock and
272
+ # perform a +cheap_broadcast+.
273
+ def try_await_lock(table, i)
274
+ if table && i >= 0 && i < table.size # bounds check, TODO: why are we bounds checking?
275
+ spins = SPIN_LOCK_ATTEMPTS
276
+ randomizer = base_randomizer = Util::XorShiftRandom.get
277
+ while equal?(table.volatile_get(i)) && self.class.locked_hash?(my_hash = hash)
278
+ if spins >= 0
279
+ if (randomizer = (randomizer >> 1)).even? # spin at random
280
+ if (spins -= 1) == 0
281
+ Thread.pass # yield before blocking
282
+ else
283
+ randomizer = base_randomizer = Util::XorShiftRandom.xorshift(base_randomizer) if randomizer.zero?
284
+ end
285
+ end
286
+ elsif cas_hash(my_hash, my_hash | WAITING)
287
+ force_aquire_lock(table, i)
288
+ break
289
+ end
290
+ end
291
+ end
292
+ end
293
+
294
+ def key?(key)
295
+ @key.eql?(key)
296
+ end
297
+
298
+ def matches?(key, hash)
299
+ pure_hash == hash && key?(key)
300
+ end
301
+
302
+ def pure_hash
303
+ hash & HASH_BITS
304
+ end
305
+
306
+ def try_lock_via_hash(node_hash = hash)
307
+ if cas_hash(node_hash, locked_hash = node_hash | LOCKED)
308
+ begin
309
+ yield
310
+ ensure
311
+ unlock_via_hash(locked_hash, node_hash)
312
+ end
313
+ end
314
+ end
315
+
316
+ def locked?
317
+ self.class.locked_hash?(hash)
318
+ end
319
+
320
+ def unlock_via_hash(locked_hash, node_hash)
321
+ unless cas_hash(locked_hash, node_hash)
322
+ self.hash = node_hash
323
+ cheap_synchronize { cheap_broadcast }
324
+ end
325
+ end
326
+
327
+ private
328
+ def force_aquire_lock(table, i)
329
+ cheap_synchronize do
330
+ if equal?(table.volatile_get(i)) && (hash & WAITING) == WAITING
331
+ cheap_wait
332
+ else
333
+ cheap_broadcast # possibly won race vs signaller
334
+ end
335
+ end
336
+ end
337
+
338
+ class << self
339
+ def locked_hash?(hash)
340
+ (hash & LOCKED) != 0
341
+ end
342
+ end
343
+ end
344
+
345
+ # shorthands
346
+ MOVED = Node::MOVED
347
+ LOCKED = Node::LOCKED
348
+ WAITING = Node::WAITING
349
+ HASH_BITS = Node::HASH_BITS
350
+
351
+ NOW_RESIZING = -1
352
+ DEFAULT_CAPACITY = 16
353
+ MAX_CAPACITY = Util::MAX_INT
354
+
355
+ # The buffer size for skipped bins during transfers. The
356
+ # value is arbitrary but should be large enough to avoid
357
+ # most locking stalls during resizes.
358
+ TRANSFER_BUFFER_SIZE = 32
359
+
360
+ extend Util::Volatile
361
+ attr_volatile :table, # The array of bins. Lazily initialized upon first insertion. Size is always a power of two.
362
+
363
+ # Table initialization and resizing control. When negative, the
364
+ # table is being initialized or resized. Otherwise, when table is
365
+ # null, holds the initial table size to use upon creation, or 0
366
+ # for default. After initialization, holds the next element count
367
+ # value upon which to resize the table.
368
+ :size_control
369
+
370
+ def initialize(options = nil)
371
+ super()
372
+ @counter = Util::Adder.new
373
+ initial_capacity = options && options[:initial_capacity] || DEFAULT_CAPACITY
374
+ self.size_control = (capacity = table_size_for(initial_capacity)) > MAX_CAPACITY ? MAX_CAPACITY : capacity
375
+ end
376
+
377
+ def get_or_default(key, else_value = nil)
378
+ hash = key_hash(key)
379
+ current_table = table
380
+ while current_table
381
+ node = current_table.volatile_get_by_hash(hash)
382
+ current_table =
383
+ while node
384
+ if (node_hash = node.hash) == MOVED
385
+ break node.key
386
+ elsif (node_hash & HASH_BITS) == hash && node.key?(key) && NULL != (value = node.value)
387
+ return value
388
+ end
389
+ node = node.next
390
+ end
391
+ end
392
+ else_value
393
+ end
394
+
395
+ def [](key)
396
+ get_or_default(key)
397
+ end
398
+
399
+ def key?(key)
400
+ get_or_default(key, NULL) != NULL
401
+ end
402
+
403
+ def []=(key, value)
404
+ get_and_set(key, value)
405
+ value
406
+ end
407
+
408
+ def compute_if_absent(key)
409
+ hash = key_hash(key)
410
+ current_table = table || initialize_table
411
+ while true
412
+ if !(node = current_table.volatile_get(i = current_table.hash_to_index(hash)))
413
+ succeeded, new_value = current_table.try_to_cas_in_computed(i, hash, key) { yield }
414
+ if succeeded
415
+ increment_size
416
+ return new_value
417
+ end
418
+ elsif (node_hash = node.hash) == MOVED
419
+ current_table = node.key
420
+ elsif NULL != (current_value = find_value_in_node_list(node, key, hash, node_hash & HASH_BITS))
421
+ return current_value
422
+ elsif Node.locked_hash?(node_hash)
423
+ try_await_lock(current_table, i, node)
424
+ else
425
+ succeeded, value = attempt_internal_compute_if_absent(key, hash, current_table, i, node, node_hash) { yield }
426
+ return value if succeeded
427
+ end
428
+ end
429
+ end
430
+
431
+ def compute_if_present(key)
432
+ new_value = nil
433
+ internal_replace(key) do |old_value|
434
+ if (new_value = yield(NULL == old_value ? nil : old_value)).nil?
435
+ NULL
436
+ else
437
+ new_value
438
+ end
439
+ end
440
+ new_value
441
+ end
442
+
443
+ def compute(key)
444
+ internal_compute(key) do |old_value|
445
+ if (new_value = yield(NULL == old_value ? nil : old_value)).nil?
446
+ NULL
447
+ else
448
+ new_value
449
+ end
450
+ end
451
+ end
452
+
453
+ def merge_pair(key, value)
454
+ internal_compute(key) do |old_value|
455
+ if NULL == old_value || !(value = yield(old_value)).nil?
456
+ value
457
+ else
458
+ NULL
459
+ end
460
+ end
461
+ end
462
+
463
+ def replace_pair(key, old_value, new_value)
464
+ NULL != internal_replace(key, old_value) { new_value }
465
+ end
466
+
467
+ def replace_if_exists(key, new_value)
468
+ if (result = internal_replace(key) { new_value }) && NULL != result
469
+ result
470
+ end
471
+ end
472
+
473
+ def get_and_set(key, value) # internalPut in the original CHMV8
474
+ hash = key_hash(key)
475
+ current_table = table || initialize_table
476
+ while true
477
+ if !(node = current_table.volatile_get(i = current_table.hash_to_index(hash)))
478
+ if current_table.cas_new_node(i, hash, key, value)
479
+ increment_size
480
+ break
481
+ end
482
+ elsif (node_hash = node.hash) == MOVED
483
+ current_table = node.key
484
+ elsif Node.locked_hash?(node_hash)
485
+ try_await_lock(current_table, i, node)
486
+ else
487
+ succeeded, old_value = attempt_get_and_set(key, value, hash, current_table, i, node, node_hash)
488
+ break old_value if succeeded
489
+ end
490
+ end
491
+ end
492
+
493
+ def delete(key)
494
+ replace_if_exists(key, NULL)
495
+ end
496
+
497
+ def delete_pair(key, value)
498
+ result = internal_replace(key, value) { NULL }
499
+ if result && NULL != result
500
+ !!result
501
+ else
502
+ false
503
+ end
504
+ end
505
+
506
+ def each_pair
507
+ return self unless current_table = table
508
+ current_table_size = base_size = current_table.size
509
+ i = base_index = 0
510
+ while base_index < base_size
511
+ if node = current_table.volatile_get(i)
512
+ if node.hash == MOVED
513
+ current_table = node.key
514
+ current_table_size = current_table.size
515
+ else
516
+ begin
517
+ if NULL != (value = node.value) # skip deleted or special nodes
518
+ yield node.key, value
519
+ end
520
+ end while node = node.next
521
+ end
522
+ end
523
+
524
+ if (i_with_base = i + base_size) < current_table_size
525
+ i = i_with_base # visit upper slots if present
526
+ else
527
+ i = base_index += 1
528
+ end
529
+ end
530
+ self
531
+ end
532
+
533
+ def size
534
+ (sum = @counter.sum) < 0 ? 0 : sum # ignore transient negative values
535
+ end
536
+
537
+ def empty?
538
+ size == 0
539
+ end
540
+
541
+ # Implementation for clear. Steps through each bin, removing all nodes.
542
+ def clear
543
+ return self unless current_table = table
544
+ current_table_size = current_table.size
545
+ deleted_count = i = 0
546
+ while i < current_table_size
547
+ if !(node = current_table.volatile_get(i))
548
+ i += 1
549
+ elsif (node_hash = node.hash) == MOVED
550
+ current_table = node.key
551
+ current_table_size = current_table.size
552
+ elsif Node.locked_hash?(node_hash)
553
+ decrement_size(deleted_count) # opportunistically update count
554
+ deleted_count = 0
555
+ node.try_await_lock(current_table, i)
556
+ else
557
+ current_table.try_lock_via_hash(i, node, node_hash) do
558
+ begin
559
+ deleted_count += 1 if NULL != node.value # recheck under lock
560
+ node.value = nil
561
+ end while node = node.next
562
+ current_table.volatile_set(i, nil)
563
+ i += 1
564
+ end
565
+ end
566
+ end
567
+ decrement_size(deleted_count)
568
+ self
569
+ end
570
+
571
+ private
572
+ # Internal versions of the insertion methods, each a
573
+ # little more complicated than the last. All have
574
+ # the same basic structure:
575
+ # 1. If table uninitialized, create
576
+ # 2. If bin empty, try to CAS new node
577
+ # 3. If bin stale, use new table
578
+ # 4. Lock and validate; if valid, scan and add or update
579
+ #
580
+ # The others interweave other checks and/or alternative actions:
581
+ # * Plain +get_and_set+ checks for and performs resize after insertion.
582
+ # * compute_if_absent prescans for mapping without lock (and fails to add
583
+ # if present), which also makes pre-emptive resize checks worthwhile.
584
+ #
585
+ # Someday when details settle down a bit more, it might be worth
586
+ # some factoring to reduce sprawl.
587
+ def internal_replace(key, expected_old_value = NULL, &block)
588
+ hash = key_hash(key)
589
+ current_table = table
590
+ while current_table
591
+ if !(node = current_table.volatile_get(i = current_table.hash_to_index(hash)))
592
+ break
593
+ elsif (node_hash = node.hash) == MOVED
594
+ current_table = node.key
595
+ elsif (node_hash & HASH_BITS) != hash && !node.next # precheck
596
+ break # rules out possible existence
597
+ elsif Node.locked_hash?(node_hash)
598
+ try_await_lock(current_table, i, node)
599
+ else
600
+ succeeded, old_value = attempt_internal_replace(key, expected_old_value, hash, current_table, i, node, node_hash, &block)
601
+ return old_value if succeeded
602
+ end
603
+ end
604
+ NULL
605
+ end
606
+
607
+ def attempt_internal_replace(key, expected_old_value, hash, current_table, i, node, node_hash)
608
+ current_table.try_lock_via_hash(i, node, node_hash) do
609
+ predecessor_node = nil
610
+ old_value = NULL
611
+ begin
612
+ if node.matches?(key, hash) && NULL != (current_value = node.value)
613
+ if NULL == expected_old_value || expected_old_value == current_value # NULL == expected_old_value means whatever value
614
+ old_value = current_value
615
+ if NULL == (node.value = yield(old_value))
616
+ current_table.delete_node_at(i, node, predecessor_node)
617
+ decrement_size
618
+ end
619
+ end
620
+ break
621
+ end
622
+
623
+ predecessor_node = node
624
+ end while node = node.next
625
+
626
+ return true, old_value
627
+ end
628
+ end
629
+
630
+ def find_value_in_node_list(node, key, hash, pure_hash)
631
+ do_check_for_resize = false
632
+ while true
633
+ if pure_hash == hash && node.key?(key) && NULL != (value = node.value)
634
+ return value
635
+ elsif node = node.next
636
+ do_check_for_resize = true # at least 2 nodes -> check for resize
637
+ pure_hash = node.pure_hash
638
+ else
639
+ return NULL
640
+ end
641
+ end
642
+ ensure
643
+ check_for_resize if do_check_for_resize
644
+ end
645
+
646
+ def internal_compute(key, &block)
647
+ hash = key_hash(key)
648
+ current_table = table || initialize_table
649
+ while true
650
+ if !(node = current_table.volatile_get(i = current_table.hash_to_index(hash)))
651
+ succeeded, new_value = current_table.try_to_cas_in_computed(i, hash, key, &block)
652
+ if succeeded
653
+ if NULL == new_value
654
+ break nil
655
+ else
656
+ increment_size
657
+ break new_value
658
+ end
659
+ end
660
+ elsif (node_hash = node.hash) == MOVED
661
+ current_table = node.key
662
+ elsif Node.locked_hash?(node_hash)
663
+ try_await_lock(current_table, i, node)
664
+ else
665
+ succeeded, new_value = attempt_compute(key, hash, current_table, i, node, node_hash, &block)
666
+ break new_value if succeeded
667
+ end
668
+ end
669
+ end
670
+
671
+ def attempt_internal_compute_if_absent(key, hash, current_table, i, node, node_hash)
672
+ added = false
673
+ current_table.try_lock_via_hash(i, node, node_hash) do
674
+ while true
675
+ if node.matches?(key, hash) && NULL != (value = node.value)
676
+ return true, value
677
+ end
678
+ last = node
679
+ unless node = node.next
680
+ last.next = Node.new(hash, key, value = yield)
681
+ added = true
682
+ increment_size
683
+ return true, value
684
+ end
685
+ end
686
+ end
687
+ ensure
688
+ check_for_resize if added
689
+ end
690
+
691
+ def attempt_compute(key, hash, current_table, i, node, node_hash)
692
+ added = false
693
+ current_table.try_lock_via_hash(i, node, node_hash) do
694
+ predecessor_node = nil
695
+ while true
696
+ if node.matches?(key, hash) && NULL != (value = node.value)
697
+ if NULL == (node.value = value = yield(value))
698
+ current_table.delete_node_at(i, node, predecessor_node)
699
+ decrement_size
700
+ value = nil
701
+ end
702
+ return true, value
703
+ end
704
+ predecessor_node = node
705
+ unless node = node.next
706
+ if NULL == (value = yield(NULL))
707
+ value = nil
708
+ else
709
+ predecessor_node.next = Node.new(hash, key, value)
710
+ added = true
711
+ increment_size
712
+ end
713
+ return true, value
714
+ end
715
+ end
716
+ end
717
+ ensure
718
+ check_for_resize if added
719
+ end
720
+
721
+ def attempt_get_and_set(key, value, hash, current_table, i, node, node_hash)
722
+ node_nesting = nil
723
+ current_table.try_lock_via_hash(i, node, node_hash) do
724
+ node_nesting = 1
725
+ old_value = nil
726
+ found_old_value = false
727
+ while node
728
+ if node.matches?(key, hash) && NULL != (old_value = node.value)
729
+ found_old_value = true
730
+ node.value = value
731
+ break
732
+ end
733
+ last = node
734
+ unless node = node.next
735
+ last.next = Node.new(hash, key, value)
736
+ break
737
+ end
738
+ node_nesting += 1
739
+ end
740
+
741
+ return true, old_value if found_old_value
742
+ increment_size
743
+ true
744
+ end
745
+ ensure
746
+ check_for_resize if node_nesting && (node_nesting > 1 || current_table.size <= 64)
747
+ end
748
+
749
+ def initialize_copy(other)
750
+ super
751
+ @counter = Util::Adder.new
752
+ self.table = nil
753
+ self.size_control = (other_table = other.table) ? other_table.size : DEFAULT_CAPACITY
754
+ self
755
+ end
756
+
757
+ def try_await_lock(current_table, i, node)
758
+ check_for_resize # try resizing if can't get lock
759
+ node.try_await_lock(current_table, i)
760
+ end
761
+
762
+ def key_hash(key)
763
+ key.hash & HASH_BITS
764
+ end
765
+
766
+ # Returns a power of two table size for the given desired capacity.
767
+ def table_size_for(entry_count)
768
+ size = 2
769
+ size <<= 1 while size < entry_count
770
+ size
771
+ end
772
+
773
+ # Initializes table, using the size recorded in +size_control+.
774
+ def initialize_table
775
+ until current_table ||= table
776
+ if (size_ctrl = size_control) == NOW_RESIZING
777
+ Thread.pass # lost initialization race; just spin
778
+ else
779
+ try_in_resize_lock(current_table, size_ctrl) do
780
+ initial_size = size_ctrl > 0 ? size_ctrl : DEFAULT_CAPACITY
781
+ current_table = self.table = Table.new(initial_size)
782
+ initial_size - (initial_size >> 2) # 75% load factor
783
+ end
784
+ end
785
+ end
786
+ current_table
787
+ end
788
+
789
+ # If table is too small and not already resizing, creates next
790
+ # table and transfers bins. Rechecks occupancy after a transfer
791
+ # to see if another resize is already needed because resizings
792
+ # are lagging additions.
793
+ def check_for_resize
794
+ while (current_table = table) && MAX_CAPACITY > (table_size = current_table.size) && NOW_RESIZING != (size_ctrl = size_control) && size_ctrl < @counter.sum
795
+ try_in_resize_lock(current_table, size_ctrl) do
796
+ self.table = rebuild(current_table)
797
+ (table_size << 1) - (table_size >> 1) # 75% load factor
798
+ end
799
+ end
800
+ end
801
+
802
+ def try_in_resize_lock(current_table, size_ctrl)
803
+ if cas_size_control(size_ctrl, NOW_RESIZING)
804
+ begin
805
+ if current_table == table # recheck under lock
806
+ size_ctrl = yield # get new size_control
807
+ end
808
+ ensure
809
+ self.size_control = size_ctrl
810
+ end
811
+ end
812
+ end
813
+
814
+ # Moves and/or copies the nodes in each bin to new table. See above for explanation.
815
+ def rebuild(table)
816
+ old_table_size = table.size
817
+ new_table = table.next_in_size_table
818
+ # puts "#{old_table_size} -> #{new_table.size}"
819
+ forwarder = Node.new(MOVED, new_table, NULL)
820
+ rev_forwarder = nil
821
+ locked_indexes = nil # holds bins to revisit; nil until needed
822
+ locked_arr_idx = 0
823
+ bin = old_table_size - 1
824
+ i = bin
825
+ while true
826
+ if !(node = table.volatile_get(i))
827
+ # no lock needed (or available) if bin >= 0, because we're not popping values from locked_indexes until we've run through the whole table
828
+ redo unless (bin >= 0 ? table.cas(i, nil, forwarder) : lock_and_clean_up_reverse_forwarders(table, old_table_size, new_table, i, forwarder))
829
+ elsif Node.locked_hash?(node_hash = node.hash)
830
+ locked_indexes ||= Array.new
831
+ if bin < 0 && locked_arr_idx > 0
832
+ locked_arr_idx -= 1
833
+ i, locked_indexes[locked_arr_idx] = locked_indexes[locked_arr_idx], i # swap with another bin
834
+ redo
835
+ end
836
+ if bin < 0 || locked_indexes.size >= TRANSFER_BUFFER_SIZE
837
+ node.try_await_lock(table, i) # no other options -- block
838
+ redo
839
+ end
840
+ rev_forwarder ||= Node.new(MOVED, table, NULL)
841
+ redo unless table.volatile_get(i) == node && node.locked? # recheck before adding to list
842
+ locked_indexes << i
843
+ new_table.volatile_set(i, rev_forwarder)
844
+ new_table.volatile_set(i + old_table_size, rev_forwarder)
845
+ else
846
+ redo unless split_old_bin(table, new_table, i, node, node_hash, forwarder)
847
+ end
848
+
849
+ if bin > 0
850
+ i = (bin -= 1)
851
+ elsif locked_indexes && !locked_indexes.empty?
852
+ bin = -1
853
+ i = locked_indexes.pop
854
+ locked_arr_idx = locked_indexes.size - 1
855
+ else
856
+ return new_table
857
+ end
858
+ end
859
+ end
860
+
861
+ def lock_and_clean_up_reverse_forwarders(old_table, old_table_size, new_table, i, forwarder)
862
+ # transiently use a locked forwarding node
863
+ locked_forwarder = Node.new(moved_locked_hash = MOVED | LOCKED, new_table, NULL)
864
+ if old_table.cas(i, nil, locked_forwarder)
865
+ new_table.volatile_set(i, nil) # kill the potential reverse forwarders
866
+ new_table.volatile_set(i + old_table_size, nil) # kill the potential reverse forwarders
867
+ old_table.volatile_set(i, forwarder)
868
+ locked_forwarder.unlock_via_hash(moved_locked_hash, MOVED)
869
+ true
870
+ end
871
+ end
872
+
873
+ # Splits a normal bin with list headed by e into lo and hi parts; installs in given table.
874
+ def split_old_bin(table, new_table, i, node, node_hash, forwarder)
875
+ table.try_lock_via_hash(i, node, node_hash) do
876
+ split_bin(new_table, i, node, node_hash)
877
+ table.volatile_set(i, forwarder)
878
+ end
879
+ end
880
+
881
+ def split_bin(new_table, i, node, node_hash)
882
+ bit = new_table.size >> 1 # bit to split on
883
+ run_bit = node_hash & bit
884
+ last_run = nil
885
+ low = nil
886
+ high = nil
887
+ current_node = node
888
+ # this optimises for the lowest amount of volatile writes and objects created
889
+ while current_node = current_node.next
890
+ unless (b = current_node.hash & bit) == run_bit
891
+ run_bit = b
892
+ last_run = current_node
893
+ end
894
+ end
895
+ if run_bit == 0
896
+ low = last_run
897
+ else
898
+ high = last_run
899
+ end
900
+ current_node = node
901
+ until current_node == last_run
902
+ pure_hash = current_node.pure_hash
903
+ if (pure_hash & bit) == 0
904
+ low = Node.new(pure_hash, current_node.key, current_node.value, low)
905
+ else
906
+ high = Node.new(pure_hash, current_node.key, current_node.value, high)
907
+ end
908
+ current_node = current_node.next
909
+ end
910
+ new_table.volatile_set(i, low)
911
+ new_table.volatile_set(i + bit, high)
912
+ end
913
+
914
+ def increment_size
915
+ @counter.increment
916
+ end
917
+
918
+ def decrement_size(by = 1)
919
+ @counter.add(-by)
920
+ end
921
+ end
922
+ end