/* * 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; import net.openhft.chronicle.hash.locks.InterProcessLock; import net.openhft.chronicle.hash.locks.InterProcessReadWriteUpdateLock; import net.openhft.chronicle.map.MapEntryOperations; import net.openhft.chronicle.map.MapMethods; import net.openhft.chronicle.map.MapQueryContext; import net.openhft.chronicle.set.SetEntryOperations; import net.openhft.chronicle.set.SetQueryContext; import org.jetbrains.annotations.Nullable; /** * Context of {@link ChronicleHash} operations with <i>individual keys</i>. * * <p>This context provides access to {@link InterProcessReadWriteUpdateLock}, governing access * to the entry. Your have no chance to perform any actions under race with other threads, because * all required locks are acquired automatically on each operation, you should deal with locks * manually in the following cases: * <ul> * <li>You should perform some read operation, and then write. On first operation, the context * will automatically acquire the read lock; on the second, it will try to acquire the update * or write lock, but it will fail with {@link IllegalMonitorStateException}, because this is * dead lock prone. (See {@link InterProcessReadWriteUpdateLock} documentation for explanation. * So, such context code is incorrect: <pre>{@code * // INCORRECT * try (ExternalMapQueryContext<K, V, ?> q = map.queryContext(key)) { * // q.entry(), checks if the entry is present in the map, and acquires * // the read lock for that. * MapEntry<K, V> entry = q.entry(); * if (entry != null) { * // Tries to acquire the write lock to perform modification, * // but this is an illegal upgrade: read -> write, throws IllegalMonitorStateException * q.remove(entry); * } * }}</pre> * So, to workaround this, you should acquire the {@linkplain #updateLock() update lock}, * which is upgradable to the write lock, <i>before</i> performing any reading in the context: * <pre>{@code * // CORRECT * try (ExternalMapQueryContext<K, V, ?> q = map.queryContext(key)) { * q.updateLock().lock(); // acquire the update lock before checking the entry presence. * MapEntry<K, V> entry = q.entry(); * if (entry != null) * q.remove(entry); * }}</pre> * </li> * <li>You want to try to acquire some lock heuristically, in order to improve total {@code * ChronicleHash} concurrency. You should base on probabilities of making some reading or * writing operations. For example, see how {@link MapMethods#acquireUsing} might be * implemented:<pre>{@code * default void acquireUsing(MapQueryContext<K, V, R> q, ReturnValue<V> returnValue) { * // For acquireUsing(), it is assumed to be very probable, that the entry is already * // present in the map, so we will perform the whole acquireUsing() without exclusive locking * if (q.readLock().tryLock()) { * MapEntry<K, V> entry = q.entry(); * if (entry != null) { * // Entry is present, return * returnValue.returnValue(entry.value()); * return; * } * // Key is absent * // Need to unlock, to lock to update lock later. Direct upgrade is forbidden. * q.readLock().unlock(); * } * // We are here, either if we: * // 1) Failed to acquire the read lock, this means some other thread is holding the write * // lock now, in this case waiting for the update lock acquisition is no longer, than for * // the read * // 2) Seen the entry is absent under the read lock. This means we need to insert * // the default value into the map. that requires update-level access as well * q.updateLock().lock(); * MapEntry<K, V> entry = q.entry(); * if (entry != null) { * // Entry is present, return * returnValue.returnValue(entry.value()); * return; * } * // Key is absent * q.insert(q.absentEntry(), q.defaultValue(q.absentEntry())); * returnValue.returnValue(q.entry().value()); * }}</pre></li> * <li>If the default {@link InterProcessLock#lock()} policy of trying to acquire the * lock for some time, and then throw {@code RuntimeException}, or need a custom timeout: * <pre>{@code * try (ExternalHashQueryContext<K> q = hash.queryContext(key)) { * if (q.writeLock().tryLock(5, TimeUnit.SECONDS)) { * // do something * } else { * // do something else, maybe not throwing an exception * } * }}</pre></li> * </ul> * * <p>{@code HashQueryContext} defines the common pattern for working with {@code ChronicleHash} * contexts: it has a pair of methods, {@link #entry()} and {@link #absentEntry()}, at any moment * one of them returns an (absent) entry context object, another - {@code null}, depending on the * presence of the {@linkplain #queriedKey() queried key} in the {@code ChronicleHash}. Thus, * block of code that uses {@code HashQueryContext} usually has an if-else statement, * with "then" branch for dealing with the present entry, {@code else} branch for dealing with * the absent entry, or vise-versa. For example: <pre>{@code * interface Point { * double getX(); * void setX(double x); * double addX(double xAdd); * * double getY(); * void setY(double y); * double addY(double yAdd); * } * * <K> Point movePoint(ChronicleMap<K, Point> map, K key, double xMove, double yMove, * Point using) { * // Moves existing point by [xMove, yMove], if absent - assumes the default point is [0, 0]. * // Returns the resulting point * try (ExternalMapQueryContext<K, Point, ?> q = map.queryContext(key)) { * Point offHeapPoint; * q.updateLock().lock(); * MapEntry<K, Point> entry = q.entry(); * if (entry != null) { * // Key is present * offHeapPoint = entry.value().getUsing(using); * } else { * // Key is absent * q.insert(q.absentEntry(), q.defaultValue(q.absentEntry())); * offHeapPoint = q.entry().value().getUsing(using); * } * offHeapPoint.addX(xMove); * offHeapPoint.addY(yMove); * return offHeapPoint; * } * }}</pre> * * <p>{@code HashQueryContext} is the base interface defining the structure, but it has no methods * to anything "interesting" with {@code entry()} or {@code absentEntry()}. Use {@link * MapQueryContext} or {@link SetQueryContext} interfaces, which provide access to {@link * MapEntryOperations} and {@link SetEntryOperations} respectively. * * @param <K> the hash key type * @see ChronicleHash#queryContext(Object) */ public interface HashQueryContext<K> extends HashContext<K>, SegmentLock { /** * Returns the index of the accessed segment, where the queried key is located (or to which * the key is going to be put). * * <p>This index might also be used as the {@code InterProcessReadWriteUpdateLock} identifier, * because {@code ChronicleHashes} has per-segment locks. */ @Override int segmentIndex(); /** * Returns the queried key as a {@code Data}. */ Data<K> queriedKey(); /** * Returns the entry context, if the entry with the queried key is <i>present</i> * in the {@code ChronicleHash}, returns {@code null} is the entry is <i>absent</i>. * * @implNote Might acquire {@link #readLock} before searching for the key, if the context * is not locked yet. */ HashEntry<K> entry(); /** * Returns the special <i>absent entry</i> object, if the entry with the queried key * is <i>absent</i> in the hash, returns {@code null}, if the entry is <i>present</i>. * * @implNote Might acquire {@link #readLock} before searching for the key, if the context * is not locked yet. */ @Nullable HashAbsentEntry<K> absentEntry(); }