Merge lp:~pbeaman/akiban-persistit/tree-visitor-prototype into lp:akiban-persistit
- tree-visitor-prototype
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Peter Beaman |
Approved revision: | 417 |
Merged at revision: | 411 |
Proposed branch: | lp:~pbeaman/akiban-persistit/tree-visitor-prototype |
Merge into: | lp:akiban-persistit |
Diff against target: |
615 lines (+407/-17) 4 files modified
src/main/java/com/persistit/Exchange.java (+158/-16) src/main/java/com/persistit/ReadOnlyExchange.java (+82/-0) src/test/java/com/persistit/unit/ExchangeTest.java (+1/-1) src/test/java/com/persistit/unit/TraverseVisitorTest.java (+166/-0) |
To merge this branch: | bzr merge lp:~pbeaman/akiban-persistit/tree-visitor-prototype |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Akiban Build User | Needs Fixing | ||
Nathan Williams | Needs Information | ||
Review via email: mp+140559@code.launchpad.net |
Commit message
Description of the change
Adds new Exchange#traverse methods and a new interface Exchange.
A simple benchmark loop showed that the TraverseVisitor produces key-value pairs almost twice as fast as a regular traverse operations.
Fortunately, the implementation turned out to be extremely simple so very little delicate Exchange code needed to be disturbed.
This is a proposal is intended to help review the design. Probably the best way to evaluate it is to look at the diff and experiment with it as a prototype. I expect we'll go back and forth on things a bit before final approval into trunk.
Peter Beaman (pbeaman) wrote : | # |
Peter Beaman (pbeaman) wrote : | # |
Thanks for the suggestion. I have modified the merge proposal to include a new ReadOnlyExchange interface that supplied only "safe" methods. The instance passed to visit is the original Exchange, so a perverse program could cast it. But the documentation and intent are clear, I believe.
I also added tests to prove that the methods provided by ReadOnlyExchange are indeed safe. It turns out that the visitor can indeed modify the key without causing an unwanted side-effects, so that restriction is removed. This may actually be a desirable operation for a visitor. For example, it might be useful to do a deep traversal of a group, but to skip over unwanted branches. A TraverseVisitor could detect that an unwanted branch has been entered and modify the key to skip past it.
There's no really good way to add safety checks to prevent code using this method from deadlocking, so I think we continue to need a warning The issue is that any call to modify any Tree from any Exchange is susceptible to the problem. We already spend a fair amount of CPU cycles checking for incorrect code patterns; in this case I think a warning is sufficient.
Nathan Williams (nwilliams) wrote : | # |
Sorry for the review slowness.
There is extremely little to actually do the visitor -- nice!
However, I'm not so sure that all the methods on ReadOnlyExchange are "safe". They are truly read only, but they do modify internal state. A nested traverse, due to calling one of the has* methods, would bork the _spareKey1, I think.
My only other nit is that the ReadOnlyExchange is an interface so the abstract clauses on each method are superfluous. Persistit has interface styles with both, but my 'druthers would be new ones without.
Peter Beaman (pbeaman) wrote : | # |
Good catch on the calls to traverse. I removed the hasXXX methods from ReadOnlyExchange. I had to do something ugly to the unit test that actually used them, and would gladly accept a suggestion for something better.
Re the "abstract" clauses, my 'druthers too. They came from the Eclipse refactor operation I did to create the class in the the first place and I didn't notice them. Overall, using refactoring to create this interface resulted in more problems them it solved - it was an educational experience.
Nathan Williams (nwilliams) wrote : | # |
I think isValueDefined() is also a hidden traverse, as it is actually isKeyDefined().
The unit test doesn't bother me much. It's simple and easy to change if ever needed.
Looks good otherwise.
Peter Beaman (pbeaman) wrote : | # |
Yes, you're right. Removed.
Peter Beaman (pbeaman) wrote : | # |
A-pproving, per discussion with Tim and Nathan.
Akiban Build User (build-akiban) wrote : | # |
There were 2 failures during build/test:
* job server-packages failed at build number 2960: http://
* view must-pass failed: server-packages is red
Peter Beaman (pbeaman) wrote : | # |
Packaging error. Will re A-pprove this.
Preview Diff
1 | === modified file 'src/main/java/com/persistit/Exchange.java' |
2 | --- src/main/java/com/persistit/Exchange.java 2012-11-28 19:07:46 +0000 |
3 | +++ src/main/java/com/persistit/Exchange.java 2013-01-07 23:12:21 +0000 |
4 | @@ -132,7 +132,7 @@ |
5 | * |
6 | * @version 1.0 |
7 | */ |
8 | -public class Exchange { |
9 | +public class Exchange implements ReadOnlyExchange { |
10 | |
11 | public enum Sequence { |
12 | NONE, FORWARD, REVERSE |
13 | @@ -639,6 +639,44 @@ |
14 | } |
15 | |
16 | /** |
17 | + * A visitor used with the |
18 | + * {@link Exchange#traverse(Key.Direction, boolean, int, TraverseVisitor)} |
19 | + * The {@link #visit(ReadOnlyExchange)} method is called once for each |
20 | + * <code>Key</code> traversed by the <code>traverse</code> method. |
21 | + */ |
22 | + public interface TraverseVisitor { |
23 | + /** |
24 | + * Receive an Exchange having <code>Key</code> and <code>Value</code> |
25 | + * values set by |
26 | + * {@link Exchange#traverse(Key.Direction, boolean, int, TraverseVisitor)} |
27 | + * . This method will be called once for each key encountered in the |
28 | + * traversal. This method may return <code>false</code> to stop |
29 | + * traversing additional keys. </p> |
30 | + * <p> |
31 | + * The implementation of this method: |
32 | + * <ul> |
33 | + * <li>Must return quickly, especially in a multi-threaded environment, |
34 | + * to avoid blocking other threads that may attempt to update records in |
35 | + * the same <code>Buffer</code>, |
36 | + * <li>Must not perform update operations on any <codeExchange</code>, |
37 | + * especially in a multi-threaded environment, to prevent deadlocks, |
38 | + * <li>May read and modify the <code>Key</code> and <code>Value</code> |
39 | + * fields of the supplied <code>ReadOnlyExchange</code>. Note, however, |
40 | + * that modifying the <code>Key</code> affects the results of subsequent |
41 | + * traversal operations. |
42 | + * </ul> |
43 | + * |
44 | + * @param ex |
45 | + * a {@link ReadOnlyExchange} from which the current |
46 | + * <code>Key</code> and <code>Value</code> may be read |
47 | + * @return <code>true</code> to continue traversing keys, or |
48 | + * <code>false</code> to stop |
49 | + * @throws PersistitException |
50 | + */ |
51 | + public boolean visit(final ReadOnlyExchange ex) throws PersistitException; |
52 | + } |
53 | + |
54 | + /** |
55 | * Delegate to {@link Key#reset} on the associated <code>Key</code> object. |
56 | * |
57 | * @return This <code>Exchange</code> to permit method call chaining. |
58 | @@ -893,6 +931,7 @@ |
59 | * |
60 | * @return This <code>Key</code>. |
61 | */ |
62 | + @Override |
63 | public Key getKey() { |
64 | assertCorrectThread(true); |
65 | return _key; |
66 | @@ -903,6 +942,7 @@ |
67 | * |
68 | * @return The <code>Value</code>. |
69 | */ |
70 | + @Override |
71 | public Value getValue() { |
72 | assertCorrectThread(true); |
73 | return _value; |
74 | @@ -918,6 +958,7 @@ |
75 | * |
76 | * @return The <code>Volume</code>. |
77 | */ |
78 | + @Override |
79 | public Volume getVolume() { |
80 | assertCorrectThread(true); |
81 | return _volume; |
82 | @@ -928,6 +969,7 @@ |
83 | * |
84 | * @return The <code>Tree</code> |
85 | */ |
86 | + @Override |
87 | public Tree getTree() { |
88 | assertCorrectThread(true); |
89 | return _tree; |
90 | @@ -938,6 +980,7 @@ |
91 | * |
92 | * @return The <code>Persistit</code> instance. |
93 | */ |
94 | + @Override |
95 | public Persistit getPersistitInstance() { |
96 | assertCorrectThread(true); |
97 | return _persistit; |
98 | @@ -952,6 +995,7 @@ |
99 | * |
100 | * @return The change count |
101 | */ |
102 | + @Override |
103 | public long getChangeCount() { |
104 | assertCorrectThread(true); |
105 | return _tree.getChangeCount(); |
106 | @@ -2010,7 +2054,7 @@ |
107 | */ |
108 | public boolean traverse(final Direction direction, final boolean deep, final int minimumBytes) |
109 | throws PersistitException { |
110 | - return traverse(direction, deep, minimumBytes, 0, 0); |
111 | + return traverse(direction, deep, minimumBytes, 0, 0, null); |
112 | } |
113 | |
114 | /** |
115 | @@ -2027,10 +2071,9 @@ |
116 | * visibility</i>, <code>false</code> is immediately returned. |
117 | */ |
118 | private boolean traverse(final Direction direction, final boolean deep, final int minimumBytes, |
119 | - final int minKeyDepth, final int matchUpToIndex) throws PersistitException { |
120 | + final int minKeyDepth, final int matchUpToIndex, final TraverseVisitor visitor) throws PersistitException { |
121 | assertCorrectThread(true); |
122 | _persistit.checkClosed(); |
123 | - |
124 | final Key spareKey = _spareKey1; |
125 | final boolean doFetch = minimumBytes > 0; |
126 | final boolean doModify = minimumBytes >= 0; |
127 | @@ -2038,8 +2081,9 @@ |
128 | final Value outValue = doFetch ? _value : _spareValue; |
129 | outValue.clear(); |
130 | |
131 | + Direction dir = direction; |
132 | Buffer buffer = null; |
133 | - boolean edge = direction == EQ || direction == GTEQ || direction == LTEQ; |
134 | + boolean edge = dir == EQ || dir == GTEQ || dir == LTEQ; |
135 | boolean nudged = false; |
136 | |
137 | if (_key.getEncodedSize() == 0) { |
138 | @@ -2121,11 +2165,11 @@ |
139 | matches = true; |
140 | } else if (edge && !deep && Buffer.decodeDepth(foundAt) == index) { |
141 | matches = true; |
142 | - } else if (direction == EQ) { |
143 | + } else if (dir == EQ) { |
144 | matches = false; |
145 | } else { |
146 | edge = false; |
147 | - foundAt = buffer.traverse(_key, direction, foundAt); |
148 | + foundAt = buffer.traverse(_key, dir, foundAt); |
149 | if (buffer.isAfterRightEdge(foundAt)) { |
150 | final long rightSiblingPage = buffer.getRightSibling(); |
151 | |
152 | @@ -2139,7 +2183,7 @@ |
153 | // |
154 | buffer = rightSibling; |
155 | checkPageType(buffer, PAGE_TYPE_DATA, false); |
156 | - foundAt = buffer.traverse(_key, direction, buffer.toKeyBlock(0)); |
157 | + foundAt = buffer.traverse(_key, dir, buffer.toKeyBlock(0)); |
158 | matches = !buffer.isAfterRightEdge(foundAt); |
159 | } else { |
160 | matches = false; |
161 | @@ -2189,14 +2233,14 @@ |
162 | matches = false; |
163 | } else { |
164 | if (deep) { |
165 | - matches |= direction != EQ; |
166 | + matches |= dir != EQ; |
167 | index = _key.getEncodedSize(); |
168 | |
169 | if (matches) { |
170 | matches = fetchFromBufferInternal(buffer, outValue, foundAt, minimumBytes); |
171 | - if (!matches && direction != EQ) { |
172 | + if (!matches && dir != EQ) { |
173 | nudged = false; |
174 | - nudgeForMVCC = (direction == GTEQ || direction == LTEQ); |
175 | + nudgeForMVCC = (dir == GTEQ || dir == LTEQ); |
176 | buffer.release(); |
177 | buffer = null; |
178 | continue; |
179 | @@ -2225,10 +2269,10 @@ |
180 | nudged = false; |
181 | buffer.release(); |
182 | buffer = null; |
183 | - if (direction == EQ) { |
184 | + if (dir == EQ) { |
185 | matches = false; |
186 | } else { |
187 | - nudgeForMVCC = (direction == GTEQ || direction == LTEQ); |
188 | + nudgeForMVCC = (dir == GTEQ || dir == LTEQ); |
189 | continue; |
190 | } |
191 | } |
192 | @@ -2284,6 +2328,18 @@ |
193 | // Done |
194 | _volume.getStatistics().bumpTraverseCounter(); |
195 | _tree.getStatistics().bumpTraverseCounter(); |
196 | + if (matches && visitor != null && visitor.visit(this)) { |
197 | + nudged = false; |
198 | + edge = false; |
199 | + if (dir == GTEQ) { |
200 | + dir = GT; |
201 | + } else if (dir == LTEQ) { |
202 | + dir = LT; |
203 | + } else if (dir == EQ) { |
204 | + return false; |
205 | + } |
206 | + continue; |
207 | + } |
208 | return matches; |
209 | } |
210 | } finally { |
211 | @@ -2296,6 +2352,91 @@ |
212 | |
213 | /** |
214 | * <p> |
215 | + * Performs generalized tree traversal using a {@link TraverseVisitor}. The |
216 | + * direction value indicates whether to traverse forward or backward in |
217 | + * collation sequence and whether the key being sought must be strictly |
218 | + * greater than or less than the supplied key. |
219 | + * </p> |
220 | + * <p> |
221 | + * Unlike {@link #traverse(Key.Direction, boolean, int)}, this method does |
222 | + * not return each time a new key is encountered in the traversal. Instead, |
223 | + * the {@link TraverseVisitor#visit(ReadOnlyExchange)} method is called once |
224 | + * for each key. This method avoids performing initial verification of the |
225 | + * key value and usually avoids locking a <code>Buffer</code> for every |
226 | + * record returned. It may offer better performance in circumstances where a |
227 | + * long sequence of keys is being examined. Note that |
228 | + * <code>ReadOnlyExchange</code> is an interface implemented by this class |
229 | + * which supplies the subset of methods that may be used safely within the |
230 | + * visitor. |
231 | + * </p> |
232 | + * <p> |
233 | + * During the call the {@link Buffer} containing the key is locked with a |
234 | + * non-exclusive claim, and any thread attempting to update records in the |
235 | + * same <code>Buffer</code> will block. Therefore the <code>visit</code> |
236 | + * method must be written carefully. See |
237 | + * {@link TraverseVisitor#visit(ReadOnlyExchange)} for guidelines. |
238 | + * </p> |
239 | + * <p> |
240 | + * This method normally modifies both the <code>Key</code> and |
241 | + * <code>Value</code> fields of this <code>Exchange</code>: the |
242 | + * <code>Key</code> is modified to reflect the key found through traversal, |
243 | + * and the <code>Value</code> field is modified to contain the value |
244 | + * associated with that key. However, this behavior can be modified by the |
245 | + * <code>minimumBytes</code> parameter. If <code>minimumBytes</code> is less |
246 | + * than or equal to zero then only the <code>Key</code> is modified. If it |
247 | + * is greater than zero, then the traverse method may choose to populate |
248 | + * only the specified number of bytes of the <code>Value</code>. |
249 | + * </p> |
250 | + * <p> |
251 | + * The <code>direction</code> value must be one of: |
252 | + * <dl> |
253 | + * <dt>Key.GT:</dt> |
254 | + * <dd>Find the next key that is strictly greater than the supplied key. If |
255 | + * there is none, return false.</dd> |
256 | + * <dt>Key.GTEQ:</dt> |
257 | + * <dd>If the supplied key exists in the database, return that key; |
258 | + * otherwise find the next greater key and return it.</dd> |
259 | + * <dt>Key.EQ:</dt> |
260 | + * <dd>Return <code>true</code> iff the specified key exists in the |
261 | + * database. Does not update the Key.</dd> |
262 | + * <dt>Key.LT:</dt> |
263 | + * <dd>Find the next key that is strictly less than the supplied key. If |
264 | + * there is none, return false.</dd> |
265 | + * <dt>Key.LTEQ:</dt> |
266 | + * <dd>If the supplied key exists in the database, return that key; |
267 | + * otherwise find the next smaller key and return it.</dd> |
268 | + * </dl> |
269 | + * </p> |
270 | + * |
271 | + * @param direction |
272 | + * One of Key.GT, Key.GTEQ, Key.EQ, Key.LT or Key.LTEQ. |
273 | + * |
274 | + * @param deep |
275 | + * Determines whether the result should represent the next (or |
276 | + * previous) physical key in the <code>Tree</code> or should be |
277 | + * restricted to just the logical siblings of the current key. |
278 | + * (See <a href="Key.html#_keyChildren">Logical Key Children and |
279 | + * Siblings</a>). |
280 | + * |
281 | + * @param minimumBytes |
282 | + * The minimum number of bytes to fetch. See {@link #fetch(int)}. |
283 | + * |
284 | + * @param visitor |
285 | + * The application-supplied <code>TraverseVisitor</code>. |
286 | + * |
287 | + * @return <code>true</code> if additional keys remaining in the traversal |
288 | + * set, or <code>false</code> to indicate that keys are exhausted. |
289 | + * |
290 | + * @throws PersistitException |
291 | + */ |
292 | + |
293 | + public boolean traverse(final Direction direction, final boolean deep, final int minimumBytes, |
294 | + final TraverseVisitor visitor) throws PersistitException { |
295 | + return traverse(direction, deep, Math.max(0, minimumBytes), 0, 0, visitor); |
296 | + } |
297 | + |
298 | + /** |
299 | + * <p> |
300 | * Performs generalized tree traversal constrained by a supplied |
301 | * {@link KeyFilter}. The direction value indicates whether to traverse |
302 | * forward or backward in collation sequence, and whether the key being |
303 | @@ -2370,7 +2511,7 @@ |
304 | } |
305 | if (keyFilter.isKeyPrefixFilter()) { |
306 | return traverse(direction, true, minBytes, keyFilter.getMinimumDepth(), |
307 | - keyFilter.getKeyPrefixByteCount()); |
308 | + keyFilter.getKeyPrefixByteCount(), null); |
309 | } |
310 | final boolean matched = traverse(direction, true, minBytes); |
311 | totalVisited += _keysVisitedDuringTraverse; |
312 | @@ -2929,7 +3070,7 @@ |
313 | assertCorrectThread(true); |
314 | _key.copyTo(_spareKey2); |
315 | final int size = _key.getEncodedSize(); |
316 | - final boolean result = traverse(GT, true, 0, _key.getDepth() + 1, size); |
317 | + final boolean result = traverse(GT, true, 0, _key.getDepth() + 1, size, null); |
318 | _spareKey2.copyTo(_key); |
319 | return result; |
320 | } |
321 | @@ -3834,7 +3975,7 @@ |
322 | _value.setPointerPageType(buffer.getPageType()); |
323 | buffer.release(); |
324 | buffer = null; |
325 | - storeInternal(_spareKey2, _value, level + 1, Exchange.StoreOptions.NONE); |
326 | + storeInternal(_spareKey2, _value, level + 1, StoreOptions.NONE); |
327 | return true; |
328 | } finally { |
329 | _treeHolder.release(); |
330 | @@ -3887,6 +4028,7 @@ |
331 | * |
332 | * @return The <code>Transaction</code> context for this thread. |
333 | */ |
334 | + @Override |
335 | public Transaction getTransaction() { |
336 | assertCorrectThread(true); |
337 | return _transaction; |
338 | |
339 | === added file 'src/main/java/com/persistit/ReadOnlyExchange.java' |
340 | --- src/main/java/com/persistit/ReadOnlyExchange.java 1970-01-01 00:00:00 +0000 |
341 | +++ src/main/java/com/persistit/ReadOnlyExchange.java 2013-01-07 23:12:21 +0000 |
342 | @@ -0,0 +1,82 @@ |
343 | +/** |
344 | + * Copyright © 2012 Akiban Technologies, Inc. All rights reserved. |
345 | + * |
346 | + * This program and the accompanying materials are made available |
347 | + * under the terms of the Eclipse Public License v1.0 which |
348 | + * accompanies this distribution, and is available at |
349 | + * http://www.eclipse.org/legal/epl-v10.html |
350 | + * |
351 | + * This program may also be available under different license terms. |
352 | + * For more information, see www.akiban.com or contact licensing@akiban.com. |
353 | + * |
354 | + * Contributors: |
355 | + * Akiban Technologies, Inc. |
356 | + */ |
357 | + |
358 | +package com.persistit; |
359 | + |
360 | +/** |
361 | + * Methods of the {@link Exchange} class that are safe to use within a |
362 | + * {@link Exchange.TraverseVisitor}. |
363 | + * |
364 | + * @author peter |
365 | + */ |
366 | +public interface ReadOnlyExchange { |
367 | + |
368 | + /** |
369 | + * Return the {@link Key} associated with this <code>Exchange</code>. |
370 | + * |
371 | + * @return This <code>Key</code>. |
372 | + */ |
373 | + public Key getKey(); |
374 | + |
375 | + /** |
376 | + * Return the {@link Value} associated with this <code>Exchange</code>. |
377 | + * |
378 | + * @return The <code>Value</code>. |
379 | + */ |
380 | + public Value getValue(); |
381 | + |
382 | + /** |
383 | + * Return the {@link Volume} containing the data accessed by this |
384 | + * <code>Exchange</code>. |
385 | + * |
386 | + * @return The <code>Volume</code>. |
387 | + */ |
388 | + public Volume getVolume(); |
389 | + |
390 | + /** |
391 | + * Return the {@link Tree} on which this <code>Exchange</code> operates. |
392 | + * |
393 | + * @return The <code>Tree</code> |
394 | + */ |
395 | + public Tree getTree(); |
396 | + |
397 | + /** |
398 | + * Return the Persistit instance from which this Exchange was created. |
399 | + * |
400 | + * @return The <code>Persistit</code> instance. |
401 | + */ |
402 | + public Persistit getPersistitInstance(); |
403 | + |
404 | + /** |
405 | + * Return the count of structural changes committed to the {@link Tree} on |
406 | + * which this <code>Exchange</code> operates. This count includes changes |
407 | + * committed by all Threads, including the current one. A structural change |
408 | + * is one in which at least one key is inserted or deleted. Replacement of |
409 | + * an existing value is not counted. |
410 | + * |
411 | + * @return The change count |
412 | + */ |
413 | + public long getChangeCount(); |
414 | + |
415 | + /** |
416 | + * The transaction context for this Exchange. By default, this is the |
417 | + * transaction context of the current thread, and by default, all |
418 | + * <code>Exchange</code>s created by a thread share the same transaction |
419 | + * context. |
420 | + * |
421 | + * @return The <code>Transaction</code> context for this thread. |
422 | + */ |
423 | + public Transaction getTransaction(); |
424 | +} |
425 | |
426 | === modified file 'src/test/java/com/persistit/unit/ExchangeTest.java' |
427 | --- src/test/java/com/persistit/unit/ExchangeTest.java 2012-10-15 18:08:24 +0000 |
428 | +++ src/test/java/com/persistit/unit/ExchangeTest.java 2013-01-07 23:12:21 +0000 |
429 | @@ -216,6 +216,7 @@ |
430 | assertEquals(false, ex.traverse(Key.GTEQ, kf, 4)); |
431 | assertEquals(false, ex.traverse(Key.LT, kf, 4)); |
432 | assertEquals(false, ex.traverse(Key.LTEQ, kf, 4)); |
433 | + |
434 | } |
435 | |
436 | @Test |
437 | @@ -489,7 +490,6 @@ |
438 | keyCheck(ex, deep ? "{1,2}" : "{1}"); |
439 | assertEquals("Should be true", true, ex.traverse(Key.EQ, deep, 0)); |
440 | keyCheck(ex, deep ? "{1,2}" : "{1}"); |
441 | - |
442 | } |
443 | |
444 | private void keyCheck(final Exchange ex, final String expected) { |
445 | |
446 | === added file 'src/test/java/com/persistit/unit/TraverseVisitorTest.java' |
447 | --- src/test/java/com/persistit/unit/TraverseVisitorTest.java 1970-01-01 00:00:00 +0000 |
448 | +++ src/test/java/com/persistit/unit/TraverseVisitorTest.java 2013-01-07 23:12:21 +0000 |
449 | @@ -0,0 +1,166 @@ |
450 | +/** |
451 | + * Copyright © 2011-2012 Akiban Technologies, Inc. All rights reserved. |
452 | + * |
453 | + * This program and the accompanying materials are made available |
454 | + * under the terms of the Eclipse Public License v1.0 which |
455 | + * accompanies this distribution, and is available at |
456 | + * http://www.eclipse.org/legal/epl-v10.html |
457 | + * |
458 | + * This program may also be available under different license terms. |
459 | + * For more information, see www.akiban.com or contact licensing@akiban.com. |
460 | + * |
461 | + * Contributors: |
462 | + * Akiban Technologies, Inc. |
463 | + */ |
464 | + |
465 | +package com.persistit.unit; |
466 | + |
467 | +import static org.junit.Assert.assertEquals; |
468 | + |
469 | +import java.util.concurrent.atomic.AtomicBoolean; |
470 | +import java.util.concurrent.atomic.AtomicInteger; |
471 | + |
472 | +import org.junit.Test; |
473 | + |
474 | +import com.persistit.Exchange; |
475 | +import com.persistit.Exchange.TraverseVisitor; |
476 | +import com.persistit.Key; |
477 | +import com.persistit.PersistitUnitTestCase; |
478 | +import com.persistit.ReadOnlyExchange; |
479 | +import com.persistit.exception.PersistitException; |
480 | + |
481 | +public class TraverseVisitorTest extends PersistitUnitTestCase { |
482 | + |
483 | + private final AtomicInteger visited = new AtomicInteger(); |
484 | + private final AtomicInteger visitLimit = new AtomicInteger(Integer.MAX_VALUE); |
485 | + private final AtomicBoolean reverse = new AtomicBoolean(); |
486 | + |
487 | + @Test |
488 | + public void simpleTraverseVisitor() throws PersistitException { |
489 | + final TraverseVisitor tv = new Exchange.TraverseVisitor() { |
490 | + |
491 | + @Override |
492 | + public boolean visit(final ReadOnlyExchange ex) { |
493 | + visited.incrementAndGet(); |
494 | + return visited.get() < visitLimit.get(); |
495 | + } |
496 | + }; |
497 | + doCombo(tv); |
498 | + |
499 | + } |
500 | + |
501 | + @Test |
502 | + public void traverseVisitorNonMutatingTraverseMethods() throws Exception { |
503 | + |
504 | + final TraverseVisitor tv = new Exchange.TraverseVisitor() { |
505 | + |
506 | + @Override |
507 | + public boolean visit(final ReadOnlyExchange ex) throws PersistitException { |
508 | + visited.incrementAndGet(); |
509 | + final int v = visited.get(); |
510 | + return v < visitLimit.get(); |
511 | + } |
512 | + }; |
513 | + |
514 | + doCombo(tv); |
515 | + } |
516 | + |
517 | + /** |
518 | + * Demonstrates that a TraverseVisitor can (carefully) modify the Key of a |
519 | + * supplied ReadOnlyExchange. |
520 | + * |
521 | + * @throws Exception |
522 | + */ |
523 | + @Test |
524 | + public void testMutateKey() throws Exception { |
525 | + final TraverseVisitor tv = new Exchange.TraverseVisitor() { |
526 | + |
527 | + @Override |
528 | + public boolean visit(final ReadOnlyExchange ex) throws PersistitException { |
529 | + visited.incrementAndGet(); |
530 | + final int v = visited.get(); |
531 | + if (reverse.get()) { |
532 | + final int k = ex.getKey().reset().decodeInt(); |
533 | + ex.getKey().clear().append(k - 1).append(0); |
534 | + } else { |
535 | + ex.getKey().append("a"); |
536 | + } |
537 | + return v < visitLimit.get(); |
538 | + } |
539 | + }; |
540 | + |
541 | + doCombo(tv); |
542 | + |
543 | + } |
544 | + |
545 | + private void doCombo(final TraverseVisitor tv) throws PersistitException { |
546 | + final Exchange ex = _persistit.getExchange("persistit", "gogo", true); |
547 | + final String mockValue = createString(64); |
548 | + for (int i = 0; i < 1000; i++) { |
549 | + ex.clear().append(i); |
550 | + ex.getValue().put(mockValue); |
551 | + ex.store(); |
552 | + } |
553 | + final int max = Integer.MAX_VALUE; |
554 | + doTraverse(ex, tv, false, Key.GT, max, 1000); |
555 | + doTraverse(ex, tv, false, Key.GT, max, 1000); |
556 | + doTraverse(ex, tv, false, Key.LT, max, 1000); |
557 | + doTraverse(ex, tv, false, Key.LT, max, 1000); |
558 | + doTraverse(ex, tv, false, Key.GT, 1, 1); |
559 | + doTraverse(ex, tv, false, Key.GT, 1, 1); |
560 | + doTraverse(ex, tv, false, Key.GT, 10, 10); |
561 | + doTraverse(ex, tv, false, Key.GT, 10, 10); |
562 | + doTraverse(ex, tv, false, Key.LT, 1, 1); |
563 | + doTraverse(ex, tv, false, Key.LT, 1, 1); |
564 | + doTraverse(ex, tv, false, Key.LT, 10, 10); |
565 | + doTraverse(ex, tv, false, Key.LT, 10, 10); |
566 | + |
567 | + doTraverse(ex, tv, true, Key.GT, max, 1000); |
568 | + doTraverse(ex, tv, true, Key.GT, max, 1000); |
569 | + doTraverse(ex, tv, true, Key.LT, max, 1000); |
570 | + doTraverse(ex, tv, true, Key.LT, max, 1000); |
571 | + doTraverse(ex, tv, true, Key.GT, 1, 1); |
572 | + doTraverse(ex, tv, true, Key.GT, 1, 1); |
573 | + doTraverse(ex, tv, true, Key.GT, 10, 10); |
574 | + doTraverse(ex, tv, true, Key.GT, 10, 10); |
575 | + doTraverse(ex, tv, true, Key.LT, 1, 1); |
576 | + doTraverse(ex, tv, true, Key.LT, 1, 1); |
577 | + doTraverse(ex, tv, true, Key.LT, 10, 10); |
578 | + doTraverse(ex, tv, true, Key.LT, 10, 10); |
579 | + |
580 | + doTraverse(ex, tv, false, Key.GTEQ, max, 1000); |
581 | + doTraverse(ex, tv, false, Key.GTEQ, max, 1000); |
582 | + doTraverse(ex, tv, false, Key.LTEQ, max, 1000); |
583 | + doTraverse(ex, tv, false, Key.LTEQ, max, 1000); |
584 | + |
585 | + doTraverseFrom(1, ex, tv, false, Key.GT, max, 998); |
586 | + doTraverseFrom(1, ex, tv, false, Key.GTEQ, max, 999); |
587 | + doTraverseFrom(998, ex, tv, false, Key.LT, max, 998); |
588 | + doTraverseFrom(998, ex, tv, false, Key.LTEQ, max, 999); |
589 | + |
590 | + doTraverseFrom(500, ex, tv, false, Key.EQ, max, 1); |
591 | + doTraverseFrom(1001, ex, tv, false, Key.EQ, max, 0); |
592 | + } |
593 | + |
594 | + private void doTraverse(final Exchange ex, final TraverseVisitor tv, final boolean deep, |
595 | + final Key.Direction direction, final int limit, final int expected) throws PersistitException { |
596 | + ex.clear().append(direction == Key.LT || direction == Key.LTEQ ? Key.AFTER : Key.BEFORE); |
597 | + doTraverse0(ex, tv, deep, direction, limit, expected); |
598 | + } |
599 | + |
600 | + private void doTraverseFrom(final int from, final Exchange ex, final TraverseVisitor tv, final boolean deep, |
601 | + final Key.Direction direction, final int limit, final int expected) throws PersistitException { |
602 | + ex.clear().append(from); |
603 | + doTraverse0(ex, tv, deep, direction, limit, expected); |
604 | + } |
605 | + |
606 | + private void doTraverse0(final Exchange ex, final TraverseVisitor tv, final boolean deep, |
607 | + final Key.Direction direction, final int limit, final int expected) throws PersistitException { |
608 | + reverse.set(direction == Key.LT || direction == Key.LTEQ); |
609 | + visited.set(0); |
610 | + visitLimit.set(limit); |
611 | + assertEquals(limit < 1000, ex.traverse(direction, deep, Integer.MAX_VALUE, tv)); |
612 | + assertEquals(expected, visited.get()); |
613 | + } |
614 | + |
615 | +} |
Comments from email by Yuval:
On Tue, Dec 18, 2012 at 4:58 PM, Peter Beaman <email address hidden> wrote: getValue( ). However, the key value itself should not be modified.
Must not modify the state of the Exchange instance. The visit method may use any accessor method on the Exchange, including Exchange.getKey() and Exchange.
It would be nice if we could get at this via the type system. Specifically, we could create interfaces for Exchange, Key and Values which contain only the read-only methods for each of those classes, and have TraverseVisitor .visit take a ReadExchange instead of an Exchange
Must not perform update operations affecting the Tree on which this Exchange operates.
What happens if the visitor breaks this constraint (or the one above, if we don't do the interface idea -- or if we do, but don't use a delegate pattern so that the visitor can just downcast its ReadExchange to Exchange)? It'd be nice if we could put some fail-fast check in there. Since the javadoc implies that there's some sort of Tree-grained (or larger?) lock taken during the visit, it'd be easy to add a "boolean visitHappening" and check this during all modification operations. It wouldn't even need to be volatile, since the lock will provide the HB edge.