/* * 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; } } }