thread_safe 0.1.1-java

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.
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