/*
* Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved.
*
* 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 com.hazelcast.internal.nearcache.impl.invalidation;
import com.hazelcast.internal.nearcache.NearCache;
import com.hazelcast.logging.ILogger;
import com.hazelcast.nio.serialization.Data;
import java.util.Collection;
import java.util.Iterator;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReferenceArray;
import static java.lang.String.format;
/**
* Handler used on Near Cache side. Observes local and remote invalidations and registers relevant
* data to {@link MetaDataContainer}s.
*
* Used to repair Near Cache in the event of missed invalidation events or partition uuid changes.
* Here repairing is done by making relevant Near Cache data unreachable.
* To make stale data unreachable {@link StaleReadDetectorImpl} is used.
*
* An instance of this class is created per Near Cache and can concurrently be used by many threads.
*
* @see StaleReadDetectorImpl
*/
public final class RepairingHandler {
private final ILogger logger;
private final String localUuid;
private final String name;
private final NearCache nearCache;
private final MinimalPartitionService partitionService;
private final int partitionCount;
private final MetaDataContainer[] metaDataContainers;
public RepairingHandler(ILogger logger, String localUuid, String name, NearCache nearCache,
MinimalPartitionService partitionService) {
this.logger = logger;
this.localUuid = localUuid;
this.name = name;
this.nearCache = nearCache;
this.partitionService = partitionService;
this.partitionCount = partitionService.getPartitionCount();
this.metaDataContainers = createMetadataContainers(partitionCount);
}
private static MetaDataContainer[] createMetadataContainers(int partitionCount) {
MetaDataContainer[] metaData = new MetaDataContainer[partitionCount];
for (int partition = 0; partition < partitionCount; partition++) {
metaData[partition] = new MetaDataContainer();
}
return metaData;
}
public void initUnknownUuids(AtomicReferenceArray<UUID> partitionUuids) {
for (int partition = 0; partition < partitionCount; partition++) {
metaDataContainers[partition].casUuid(null, partitionUuids.get(partition));
}
}
public MetaDataContainer getMetaDataContainer(int partition) {
return metaDataContainers[partition];
}
/**
* Handles a single invalidation
*/
public void handle(Data key, String sourceUuid, UUID partitionUuid, long sequence) {
// apply invalidation if it's not originated by local member/client (because local
// Near Caches are invalidated immediately there is no need to invalidate them twice)
if (!localUuid.equals(sourceUuid)) {
// sourceUuid is allowed to be `null`
if (key == null) {
nearCache.clear();
} else {
nearCache.remove(key);
}
}
int partitionId = getPartitionIdOrDefault(key);
checkOrRepairUuid(partitionId, partitionUuid);
checkOrRepairSequence(partitionId, sequence, false);
}
private int getPartitionIdOrDefault(Data key) {
if (key == null) {
// `name` is used to determine partition-id of map-wide events like clear()
// since key is `null`, we are using `name` to find the partition-id
return partitionService.getPartitionId(name);
}
return partitionService.getPartitionId(key);
}
/**
* Handles batch invalidations
*/
public void handle(Collection<Data> keys, Collection<String> sourceUuids,
Collection<UUID> partitionUuids, Collection<Long> sequences) {
Iterator<Data> keyIterator = keys.iterator();
Iterator<Long> sequenceIterator = sequences.iterator();
Iterator<UUID> partitionUuidIterator = partitionUuids.iterator();
Iterator<String> sourceUuidsIterator = sourceUuids.iterator();
while (keyIterator.hasNext() && sourceUuidsIterator.hasNext()
&& partitionUuidIterator.hasNext() && sequenceIterator.hasNext()) {
handle(keyIterator.next(), sourceUuidsIterator.next(), partitionUuidIterator.next(), sequenceIterator.next());
}
}
public String getName() {
return name;
}
// TODO: really need to pass partition-id?
public void updateLastKnownStaleSequence(MetaDataContainer metaData, int partition) {
long lastReceivedSequence;
long lastKnownStaleSequence;
do {
lastReceivedSequence = metaData.getSequence();
lastKnownStaleSequence = metaData.getStaleSequence();
if (lastKnownStaleSequence >= lastReceivedSequence) {
break;
}
} while (!metaData.casStaleSequence(lastKnownStaleSequence, lastReceivedSequence));
if (logger.isFinestEnabled()) {
logger.finest(format("%s:[map=%s,partition=%d,lowerSequencesStaleThan=%d,lastReceivedSequence=%d]",
"Stale sequences updated", name, partition, metaData.getStaleSequence(), metaData.getSequence()));
}
}
// multiple threads can concurrently call this method: one is anti-entropy, other one is event service thread
public void checkOrRepairUuid(final int partition, final UUID newUuid) {
assert newUuid != null;
MetaDataContainer metaData = getMetaDataContainer(partition);
while (true) {
UUID prevUuid = metaData.getUuid();
if (prevUuid != null && prevUuid.equals(newUuid)) {
break;
}
if (metaData.casUuid(prevUuid, newUuid)) {
metaData.resetSequence();
metaData.resetStaleSequence();
if (logger.isFinestEnabled()) {
logger.finest(format("%s:[name=%s,partition=%d,prevUuid=%s,newUuid=%s]",
"Invalid uuid, lost remote partition data unexpectedly", name, partition, prevUuid, newUuid));
}
break;
}
}
}
/**
* Checks {@code nextSequence} against current one. And updates current sequence if next one is bigger.
*/
// multiple threads can concurrently call this method: one is anti-entropy, other one is event service thread
public void checkOrRepairSequence(final int partition, final long nextSequence, final boolean viaAntiEntropy) {
assert nextSequence > 0;
MetaDataContainer metaData = getMetaDataContainer(partition);
while (true) {
final long currentSequence = metaData.getSequence();
if (currentSequence >= nextSequence) {
break;
}
if (metaData.casSequence(currentSequence, nextSequence)) {
final long sequenceDiff = nextSequence - currentSequence;
if (viaAntiEntropy || sequenceDiff > 1L) {
// we have found at least one missing sequence between current and next sequences. if miss is detected by
// anti-entropy, number of missed sequences will be `miss = next - current`, otherwise it means miss is
// detected by observing received invalidation event sequence numbers and number of missed sequences will be
// `miss = next - current - 1`.
final long missCount = viaAntiEntropy ? sequenceDiff : sequenceDiff - 1;
final long totalMissCount = metaData.addAndGetMissedSequenceCount(missCount);
if (logger.isFinestEnabled()) {
logger.finest(format("%s:[map=%s,partition=%d,currentSequence=%d,nextSequence=%d,totalMissCount=%d]",
"Invalid sequence", name, partition, currentSequence, nextSequence, totalMissCount));
}
}
break;
}
}
}
@Override
public String toString() {
return "RepairingHandler{"
+ "name='" + name + '\''
+ ", localUuid='" + localUuid + '\''
+ '}';
}
}