All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.openhft.chronicle.hash.HashQueryContext Maven / Gradle / Ivy

/*
 * Copyright 2012-2018 Chronicle Map Contributors
 *
 * 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.
 */

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 individual keys.
 * 

*

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: *

    *
  • 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:
    {@code
     * // INCORRECT
     * try (ExternalMapQueryContext q = map.queryContext(key)) {
     *     // q.entry(), checks if the entry is present in the map, and acquires
     *     // the read lock for that.
     *     MapEntry 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);
     *     }
     * }}
    * So, to workaround this, you should acquire the {@linkplain #updateLock() update lock}, * which is upgradable to the write lock, before performing any reading in the context: *
    {@code
     * // CORRECT
     * try (ExternalMapQueryContext q = map.queryContext(key)) {
     *     q.updateLock().lock(); // acquire the update lock before checking the entry presence.
     *     MapEntry entry = q.entry();
     *     if (entry != null)
     *         q.remove(entry);
     * }}
    *
  • *
  • 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:
    {@code
     * default void acquireUsing(MapQueryContext q, ReturnValue 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 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 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());
     * }}
  • *
  • 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: *
    {@code
     * try (ExternalHashQueryContext q = hash.queryContext(key)) {
     *     if (q.writeLock().tryLock(5, TimeUnit.SECONDS)) {
     *         // do something
     *     } else {
     *         // do something else, maybe not throwing an exception
     *     }
     * }}
  • *
* *

{@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:

{@code
 * interface Point {
 *     double getX();
 *     void setX(double x);
 *     double addX(double xAdd);
 *
 *     double getY();
 *     void setY(double y);
 *     double addY(double yAdd);
 * }
 *
 *  Point movePoint(ChronicleMap 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 q = map.queryContext(key)) {
 *         Point offHeapPoint;
 *         q.updateLock().lock();
 *         MapEntry 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;
 *     }
 * }}
*

*

{@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 the hash key type * @see ChronicleHash#queryContext(Object) */ public interface HashQueryContext extends HashContext, SegmentLock { /** * Returns the index of the accessed segment, where the queried key is located (or to which * the key is going to be put). *

*

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 queriedKey(); /** * Returns the entry context, if the entry with the queried key is present * in the {@code ChronicleHash}, returns {@code null} is the entry is absent. * * @implNote Might acquire {@link #readLock} before searching for the key, if the context * is not locked yet. */ HashEntry entry(); /** * Returns the special absent entry object, if the entry with the queried key * is absent in the hash, returns {@code null}, if the entry is present. * * @implNote Might acquire {@link #readLock} before searching for the key, if the context * is not locked yet. */ @Nullable HashAbsentEntry absentEntry(); }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy