/*
* Copyright (C) 2012, 2016 higherfrequencytrading.com
* Copyright (C) 2016 Roman Leventov
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.openhft.chronicle.hash.impl.stage.hash;
import net.openhft.chronicle.map.VanillaChronicleMap;
import net.openhft.sg.Stage;
import net.openhft.sg.Staged;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
@Staged
public abstract class Chaining extends ChainingInterface {
public final List<ChainingInterface> contextChain;
public final int indexInContextChain;
/**
* First context, ever created in this thread. rootContextInThisThread === contextChain.get(0).
*/
public final ChainingInterface rootContextInThisThread;
public Chaining(VanillaChronicleMap map) {
contextChain = new ArrayList<>();
contextChain.add(this);
indexInContextChain = 0;
rootContextInThisThread = this;
initMap(map);
}
public Chaining(ChainingInterface rootContextInThisThread, VanillaChronicleMap map) {
contextChain = rootContextInThisThread.getContextChain();
indexInContextChain = contextChain.size();
contextChain.add(this);
this.rootContextInThisThread = rootContextInThisThread;
initMap(map);
}
@Override
public List<ChainingInterface> getContextChain() {
return contextChain;
}
public <T> T contextAtIndexInChain(int index) {
//noinspection unchecked
return (T) contextChain.get(index);
}
/**
* This method stores a reference to the context's owner ChronicleMap into a field of the
* context in the beginning of each usage of the context. Previously, this field was final and
* set only once during context creation. It was preventing ChronicleMap objects from becoming
* dead and collected by the GC, while any thread, from which the ChronicleMap was accessed
* (hence a thread local context created), is alive.
*
* <p>The chain of strong references:
* 1) Thread ->
* 2) ThreadLocalMap ->
* 3) Entry with ThreadLocal {@link net.openhft.chronicle.map.VanillaChronicleMap#cxt} as weak
* referent and a context (e. g. {@link net.openhft.chronicle.map.impl.CompiledMapQueryContext})
* as value (a simple field, not a weak reference!) ->
* 4) final reference to the owner {@link VanillaChronicleMap} ->
* 5) ThreadLocal {@link net.openhft.chronicle.map.VanillaChronicleMap#cxt} (a strong reference
* this time! note that this ThreadLocal is an instance field of VanillaChronicleMap)
*
* <p>So in order to break this chain at step 4), contexts store references to their owner
* ChronicleMaps only when contexts are used.
*/
public abstract void initMap(VanillaChronicleMap map);
@Stage("Used") public boolean used;
@Stage("Used") private boolean firstContextLockedInThisThread;
@Override
public boolean usedInit() {
return used;
}
/**
* Init method parameterized to avoid automatic initialization. {@code used} argument should
* always be {@code true}.
*/
@Override
public void initUsed(boolean used, VanillaChronicleMap map) {
assert used;
firstContextLockedInThisThread = rootContextInThisThread.lockContextLocally(map);
initMap(map);
this.used = true;
}
@SuppressWarnings("unused")
void closeUsed() {
used = false;
if (firstContextLockedInThisThread)
rootContextInThisThread.unlockContextLocally();
}
@Override
public <T extends ChainingInterface> T getContext(
Class<? extends T> contextClass, BiFunction<ChainingInterface,
VanillaChronicleMap, T> createChaining,
VanillaChronicleMap map) {
for (ChainingInterface context : contextChain) {
if (context.getClass() == contextClass && !context.usedInit()) {
return initUsedAndReturn(map, context);
}
}
int maxNestedContexts = 1 << 10;
if (contextChain.size() > maxNestedContexts) {
throw new IllegalStateException(map.toIdentityString() +
": More than " + maxNestedContexts + " nested ChronicleHash contexts\n" +
"are not supported. Very probable that you simply forgot to close context\n" +
"somewhere (recommended to use try-with-resources statement).\n" +
"Otherwise this is a bug, please report with this\n" +
"stack trace on https://github.com/OpenHFT/Chronicle-Map/issues");
}
//noinspection unchecked
T context = createChaining.apply(this, map);
return initUsedAndReturn(map, context);
}
private static <T extends ChainingInterface> T initUsedAndReturn(
VanillaChronicleMap map, ChainingInterface context) {
try {
context.initUsed(true, map);
//noinspection unchecked
return (T) context;
} catch (Throwable throwable) {
try {
((AutoCloseable) context).close();
} catch (Throwable t) {
throwable.addSuppressed(t);
}
throw throwable;
}
}
}