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

net.openhft.chronicle.map.replication.MapRemoteOperations 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.map.replication;

import net.openhft.chronicle.hash.Data;
import net.openhft.chronicle.hash.replication.DefaultEventualConsistencyStrategy;
import net.openhft.chronicle.hash.replication.ReplicableEntry;
import net.openhft.chronicle.map.ChronicleMap;
import net.openhft.chronicle.map.MapAbsentEntry;
import net.openhft.chronicle.map.MapEntryOperations;

import static net.openhft.chronicle.hash.replication.DefaultEventualConsistencyStrategy.AcceptanceDecision.ACCEPT;
import static net.openhft.chronicle.hash.replication.DefaultEventualConsistencyStrategy.AcceptanceDecision.DISCARD;
import static net.openhft.chronicle.hash.replication.DefaultEventualConsistencyStrategy.decideOnRemoteModification;

/**
 * SPI strategy of performing remote calls and apply replication events for {@link ChronicleMap}.
 * 

*

Example: Grow-only set values CRDT:


 * class GrowOnlySetValuedMapEntryOperations<K, E>
 *         implements MapEntryOperations<K, Set<E>, Void> {
 *     @Override
 *     public Void remove(@NotNull MapEntry<K, Set<E>> entry) {
 *         throw new UnsupportedOperationException("Map with grow-only set values " +
 *                 "doesn't support map value removals");
 *     }
 * }
 * 

* class GrowOnlySetValuedMapRemoteOperations<K, E> * implements MapRemoteOperations<K, Set<E>, Void> { * @Override * public void put(MapRemoteQueryContext<K, Set<E>, Void> q, * Data<Set<E>, ?> newValue) { * MapReplicableEntry<K, Set<E>> entry = q.entry(); * if (entry != null) { * Set<E> merged = new HashSet<>(entry.value().get()); * merged.addAll(newValue.get()); * q.replaceValue(entry, q.wrapValueAsData(merged)); * } else { * q.insert(q.absentEntry(), newValue); * q.entry().updateOrigin(q.remoteIdentifier(), q.remoteTimestamp()); * } * } *

* @Override * public void remove(MapRemoteQueryContext<K, Set<E>, Void> q) { * throw new UnsupportedOperationException(); * } * }

* * @param the map key type * @param the map value type * @param the return type of {@link MapEntryOperations} specified fro the queried map * @see DefaultEventualConsistencyStrategy * @see net.openhft.chronicle.map.ChronicleMapBuilderPrivateAPI#remoteOperations(MapRemoteOperations) */ public interface MapRemoteOperations { /** * Handle remote {@code remove} call and {@code remove} replication event, i. e. when the entry * with the query key ({@code q.queriedKey()}) was removed on some {@code ChronicleMap} node. * * @param q the remote operation context */ default void remove(MapRemoteQueryContext q) { MapReplicableEntry entry = q.entry(); if (entry != null) { if (decideOnRemoteModification(entry, q) == ACCEPT) { q.remove(entry); ReplicableEntry replicableAbsentEntry = (ReplicableEntry) q.absentEntry(); assert replicableAbsentEntry != null; replicableAbsentEntry.updateOrigin(q.remoteIdentifier(), q.remoteTimestamp()); // (*) if (q.remoteIdentifier() == q.currentNodeIdentifier()) { // The entry with origin (replicatedIdentifier() is remote entry's origin), // equal to the current node, should be lost on on this node (or this node is // a different Chronicle Map instance, because the previous was lost, just using // the same identifier), if it happens we should propagate this recovered // event again, to other nodes replicableAbsentEntry.raiseChangedForAllExcept(q.remoteNodeIdentifier()); replicableAbsentEntry.dropChangedFor(q.remoteNodeIdentifier()); } else { // We accepted a replication event, suppress further event propagation replicableAbsentEntry.dropChanged(); } } } else { MapAbsentEntry absentEntry = q.absentEntry(); assert absentEntry != null; ReplicableEntry replicableAbsentEntry; if (!(absentEntry instanceof ReplicableEntry)) { // Note in the two following lines dummy value is inserted and removed using direct // entry.doXxx calls, not q.xxx(entry). The intention is to avoid calling possibly // overridden MapEntryOperations, because this is technical procedure of making // "truly absent" entry "deleted", not actual insertion and removal. absentEntry.doInsert(q.dummyZeroValue()); entry = q.entry(); assert entry != null; entry.doRemove(); replicableAbsentEntry = (ReplicableEntry) q.absentEntry(); assert replicableAbsentEntry != null; } else { replicableAbsentEntry = (ReplicableEntry) absentEntry; if (decideOnRemoteModification(replicableAbsentEntry, q) == DISCARD) return; } replicableAbsentEntry.updateOrigin(q.remoteIdentifier(), q.remoteTimestamp()); // For explanation see similar block above (*) if (q.remoteIdentifier() == q.currentNodeIdentifier()) { replicableAbsentEntry.raiseChangedForAllExcept(q.remoteNodeIdentifier()); replicableAbsentEntry.dropChangedFor(q.remoteNodeIdentifier()); } else { replicableAbsentEntry.dropChanged(); } } } /** * Handle remote {@code put} call or replication event, i. e. when the entry with the queried * key ({@code q.queriedKey()}) was changed on some remote {@code ChronicleMap} node, with the * given {@code newValue}. * * @param q the remote operation context * @param newValue the new value to put */ default void put(MapRemoteQueryContext q, Data newValue) { MapReplicableEntry entry = q.entry(); if (entry != null) { if (decideOnRemoteModification(entry, q) == ACCEPT) { q.replaceValue(entry, newValue); entry.updateOrigin(q.remoteIdentifier(), q.remoteTimestamp()); // For explanation see similar block in remove() method (*) if (q.remoteIdentifier() == q.currentNodeIdentifier()) { // This differs from action under the same conditions in remove() method, // to let the item 10 in (**) work! entry.raiseChanged(); } else { entry.dropChanged(); } } } else { MapAbsentEntry absentEntry = q.absentEntry(); assert absentEntry != null; if (!(absentEntry instanceof ReplicableEntry) || decideOnRemoteModification((ReplicableEntry) absentEntry, q) == ACCEPT) { q.insert(absentEntry, newValue); entry = q.entry(); assert entry != null; entry.updateOrigin(q.remoteIdentifier(), q.remoteTimestamp()); // For explanation see similar block in remove() method (*) if (q.remoteIdentifier() == q.currentNodeIdentifier()) { // This differs from action under the same conditions in remove() method, // to let the item 10 in (**) work! entry.raiseChanged(); } else { entry.dropChanged(); } } else { // (**) // In case of old deleted entries cleanup + network disconnections and // bootstrapping, it is possible that the entry ends being removed on the remote // node and present on the origin node: // // 1. Entry is added on node 1 // 2. This change is replicated and arrived to node 2 // 3. Entry is removed on node 1, it is scheduled for replication again // 4. Network issues, disconnection, bootstrapping // 5. Node 2 schedules the subject entry (currently present on node 2) // for bootstrapping to the node 1 // 6. Node 2 sends the replication event to node 1 (in-flight) // 7. The entry removal is replicated from node 1 to node 2, it is accepted, finally // the entry is removed on node 2 // 8. The entry is cleaned up (erased completely) on node 1, because it is already // replicated to everywhere it should be, and timeout (if set very short) // is expired // 9. Replication event from node 2 (with present entry) arrives to node 1, as the // entry is already erased from node 1, this change is accepted, and finally // the entry is present on node 1. // 10. The entry is bootstrapped again on node 1, by (*) blocks // 11. The "present" entry from node 1 is discarded on node 2, because it sees that // this entry was removed later. // // The following block captures the condition from item 11, and sends the event // again back, to give the chance to be finally removed on node 1 (in the above // example). if (((ReplicableEntry) absentEntry).originIdentifier() == q.remoteIdentifier() && q.remoteIdentifier() != q.currentNodeIdentifier()) { // If this change will arrive to the origin node and accepted, it will be // propagated to all other nodes, by (*) blocks ((ReplicableEntry) absentEntry).raiseChangedFor(q.remoteIdentifier()); } } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy