package org.cache2k.benchmark.impl2015; /* * #%L * Benchmarks: implementation variants * %% * Copyright (C) 2013 - 2017 headissue GmbH, Munich * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import java.util.NoSuchElementException; /** * Iterator over all hash table entries of two hashes. * * <p>The hash has a usage/reference counter for iterations * to suspend expand until the iteration finished. This is needed * for correctness of the iteration, if an expand is done during * the iteration process, the iterations returns duplicate * entries or not all entries. * * <p>Failing to operate the increment/decrement in balance will * mean that the hash table expands are blocked forever, which is a * serious error condition. Typical problems arise by thrown * exceptions during an iteration. Since there is a measure for the * hash quality, a problem like this may be detected. * * <p>Rationale: We need to keep track of the entries/keys iterated. * The cache may remove and insert entries from hash to refreshHash, * also the application may do a remove and insert. A removed entry * has always e.another kept intact, so traversal of the collision list * will always work. If a iteration is going on, this means a removed * entry needs to be cloned to reassign the e.another pointer. * * <p>Rationale: The iteration just works on the hash data structure. * However, it needs to be checked, whether the cache is closed * meanwhile. To signal this, the hash is closed also. This is * a little complex, but safes the dependency on the cache here. * * @author Jens Wilke; created: 2013-12-21 */ public class ClosableConcurrentHashEntryIterator<E extends Entry> implements ClosableIterator<E> { int iteratedCountLastRun; Entry lastEntry = null; Entry nextEntry = null; Hash<E> hashCtl; Hash<E> hashCtl2; Entry[] hash; Entry[] hash2; Hash<E> hashCtlCopy; Hash<E> hashCtl2Copy; Entry[] hashCopy; Entry[] hash2Copy; Hash<Entry> iteratedCtl = new Hash<Entry>(); Entry[] iterated; boolean keepIterated = false; boolean stopOnClear = true; public ClosableConcurrentHashEntryIterator( Hash<E> _hashCtl, E[] _hash, Hash<E> _hashCtl2, E[] _hash2) { hashCopy = hash = _hash; hash2Copy = hash2 = _hash2; hashCtlCopy = hashCtl = _hashCtl; hashCtl2Copy = hashCtl2 = _hashCtl2; _hashCtl.incrementSuppressExpandCount(); iterated = iteratedCtl.init(Entry.class); } private Entry nextEntry() { Entry e; if (hash == null) { return null; } if (hashCtl.shouldAbort()) { if (checkForClearAndAbort()) { return null; } if (stopOnClear && hashCtl.isCleared()) { throw new CacheClosedException(); } } int idx = 0; if (lastEntry != null) { e = lastEntry.another; if (e != null) { e = checkIteratedOrNext((E) e); if (e != null) { lastEntry = e; return e; } } idx = Hash.index(hash, lastEntry.hashCode) + 1; } for (;;) { if (idx >= hash.length) { if (switchAndCheckAbort()) { return null; } idx = 0; } e = hash[idx]; if (e != null) { e = checkIteratedOrNext((E) e); if (e != null) { lastEntry = e; return e; } } idx++; } } protected E checkIteratedOrNext(E e) { do { boolean _notYetIterated = !Hash.contains(iterated, e.key, e.hashCode); if (_notYetIterated) { Entry _newEntryIterated = new Entry(); _newEntryIterated.key = e.key; _newEntryIterated.hashCode = e.hashCode; iterated = iteratedCtl.insert(iterated, _newEntryIterated); return e; } e = (E) e.another; } while (e != null); return null; } protected boolean switchAndCheckAbort() { hashCtl.decrementSuppressExpandCount(); hash = hash2; hashCtl = hashCtl2; hash2 = null; hashCtl2 = null; if (hash == null) { lastEntry = null; if (iteratedCountLastRun == iteratedCtl.size) { close(); return true; } iteratedCountLastRun = iteratedCtl.size; hash = hashCopy; hash2 = hash2Copy; hashCtl = hashCtlCopy; hashCtl2 = hashCtl2Copy; } hashCtl.incrementSuppressExpandCount(); return false; } /** * Check if hash was cleared and we need to abort. Returns true * if we should abort. */ protected boolean checkForClearAndAbort() { if (hashCtl.isCleared()) { close(); return true; } return false; } @Override public boolean hasNext() { return (nextEntry = nextEntry()) != null; } @Override public E next() { if (nextEntry != null) { E e = (E) nextEntry; nextEntry = null; return e; } E e = (E) nextEntry(); if (e == null) { throw new NoSuchElementException("not available"); } return e; } @Override public void remove() { throw new UnsupportedOperationException(); } @Override public void close() { if (hashCtl != null) { hashCtl.decrementSuppressExpandCount(); hashCtl = hashCtl2 = null; hash = hash2 = null; hashCtlCopy = hashCtl2Copy = null; hashCopy = hash2Copy = null; lastEntry = null; if (!keepIterated) { iterated = null; iteratedCtl = null; } } } @Override protected void finalize() throws Throwable { super.finalize(); close(); } /** * Keep hash of iterated items, needed for storage iteration. */ public void setKeepIterated(boolean keepIterated) { this.keepIterated = keepIterated; } /** * Iterations stops when storage is cleared, default is true. */ public void setStopOnClear(boolean stopOnClear) { this.stopOnClear = stopOnClear; } }