/*
* INESC-ID, Instituto de Engenharia de Sistemas e Computadores Investigação e Desevolvimento em Lisboa
* Copyright 2013 INESC-ID and/or its affiliates and other
* contributors as indicated by the @author tags. All rights reserved.
* See the copyright.txt in the distribution for a full listing of
* individual contributors.
*
* This 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.0 of
* the License, or (at your option) any later version.
*
* This software 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 software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.infinispan.container.versioning.gmu;
import org.infinispan.Cache;
import org.infinispan.cacheviews.CacheView;
import org.infinispan.commons.hash.Hash;
import org.infinispan.commons.hash.MurmurHash3;
import org.infinispan.container.versioning.EntryVersion;
import org.infinispan.container.versioning.IncrementableEntryVersion;
import org.infinispan.dataplacement.ClusterSnapshot;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.factories.annotations.Start;
import org.infinispan.remoting.rpc.RpcManager;
import org.infinispan.remoting.transport.Address;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeMap;
import static org.infinispan.container.versioning.gmu.GMUVersion.NON_EXISTING;
import static org.infinispan.transaction.gmu.GMUHelper.toGMUVersion;
/**
* // TODO: Document this
*
* @author Pedro Ruivo
* @since 5.2
*/
public class DistGMUVersionGenerator implements GMUVersionGenerator {
private static final Hash HASH = new MurmurHash3();
private static final Log log = LogFactory.getLog(DistGMUVersionGenerator.class);
private final TreeMap<Integer, ClusterSnapshot> viewIdClusterSnapshot;
private RpcManager rpcManager;
private String cacheName;
private volatile int currentViewId;
public DistGMUVersionGenerator() {
viewIdClusterSnapshot = new TreeMap<Integer, ClusterSnapshot>();
}
@Inject
public final void init(RpcManager rpcManager, Cache cache) {
this.rpcManager = rpcManager;
this.cacheName = cache.getName();
}
@Start(priority = 11) // needs to happen *after* the transport starts.
public final void setEmptyViewId() {
currentViewId = -1;
viewIdClusterSnapshot.put(-1, new ClusterSnapshot(Collections.singleton(rpcManager.getAddress()), HASH));
}
@Override
public final IncrementableEntryVersion generateNew() {
int viewId = currentViewId;
ClusterSnapshot clusterSnapshot = getClusterSnapshot(viewId);
long[] versions = create(true, clusterSnapshot.size());
versions[clusterSnapshot.indexOf(getAddress())] = 0;
return new GMUDistributedVersion(cacheName, currentViewId, this, versions);
}
@Override
public final IncrementableEntryVersion increment(IncrementableEntryVersion initialVersion) {
if (initialVersion == null) {
throw new NullPointerException("Cannot increment a null version");
}
GMUVersion gmuVersion = toGMUVersion(initialVersion);
int viewId = currentViewId;
GMUDistributedVersion incrementedVersion = new GMUDistributedVersion(cacheName, viewId, this,
increment(gmuVersion, viewId));
if (log.isTraceEnabled()) {
log.tracef("increment(%s) ==> %s", initialVersion, incrementedVersion);
}
return incrementedVersion;
}
@Override
public final GMUVersion mergeAndMax(EntryVersion... entryVersions) {
if (entryVersions == null || entryVersions.length == 0) {
throw new IllegalStateException("Cannot merge an empy list");
}
List<GMUVersion> gmuVersions = new ArrayList<GMUVersion>(entryVersions.length);
//validate the entry versions
for (EntryVersion entryVersion : entryVersions) {
if (entryVersion == null) {
log.errorf("Null version in list %s. It will be ignored", entryVersion);
} else if (entryVersion instanceof GMUVersion) {
gmuVersions.add(toGMUVersion(entryVersion));
} else {
throw new IllegalArgumentException("Expected an array of GMU entry version but it has " +
entryVersion.getClass().getSimpleName());
}
}
int viewId = currentViewId;
GMUDistributedVersion mergedVersion = new GMUDistributedVersion(cacheName, viewId, this,
mergeClustered(viewId, gmuVersions));
if (log.isTraceEnabled()) {
log.tracef("mergeAndMax(%s) ==> %s", entryVersions, mergedVersion);
}
return mergedVersion;
}
@Override
public GMUVersion mergeAndMin(EntryVersion... entryVersions) {
int viewId = currentViewId;
ClusterSnapshot clusterSnapshot = getClusterSnapshot(viewId);
long[] versions = create(true, clusterSnapshot.size());
if (entryVersions.length == 0) {
return new GMUDistributedVersion(cacheName, viewId, this, versions);
}
for (EntryVersion entryVersion : entryVersions) {
GMUVersion gmuVersion = toGMUVersion(entryVersion);
for (int i = 0; i < clusterSnapshot.size(); ++i) {
long value = gmuVersion.getVersionValue(clusterSnapshot.get(i));
if (versions[i] == NON_EXISTING) {
versions[i] = value;
} else if (value != NON_EXISTING) {
versions[i] = Math.min(versions[i], value);
}
}
}
return new GMUDistributedVersion(cacheName, viewId, this, versions);
}
@Override
public final GMUVersion calculateCommitVersion(EntryVersion prepareVersion,
Collection<Address> affectedOwners) {
int viewId = currentViewId;
GMUDistributedVersion commitVersion = new GMUDistributedVersion(cacheName, viewId, this,
calculateVersionToCommit(viewId, prepareVersion,
affectedOwners));
if (log.isTraceEnabled()) {
log.tracef("calculateCommitVersion(%s,%s) ==> %s", prepareVersion, affectedOwners, commitVersion);
}
return commitVersion;
}
@Override
public final GMUCacheEntryVersion convertVersionToWrite(EntryVersion version, int subVersion) {
GMUVersion gmuVersion = toGMUVersion(version);
GMUCacheEntryVersion cacheEntryVersion = new GMUCacheEntryVersion(cacheName, currentViewId, this,
gmuVersion.getThisNodeVersionValue(), subVersion);
if (log.isTraceEnabled()) {
log.tracef("convertVersionToWrite(%s) ==> %s", version, cacheEntryVersion);
}
return cacheEntryVersion;
}
@Override
public GMUReadVersion convertVersionToRead(EntryVersion version) {
if (version == null) {
return null;
}
GMUVersion gmuVersion = toGMUVersion(version);
return new GMUReadVersion(cacheName, currentViewId, this, gmuVersion.getThisNodeVersionValue());
}
@Override
public GMUVersion calculateMaxVersionToRead(EntryVersion transactionVersion,
Collection<Address> alreadyReadFrom) {
if (alreadyReadFrom == null || alreadyReadFrom.isEmpty()) {
if (log.isTraceEnabled()) {
log.tracef("calculateMaxVersionToRead(%s, %s) ==> null", transactionVersion, alreadyReadFrom);
}
return null;
}
//the max version is calculated with the position of the version in which this node has already read from
GMUVersion gmuVersion = toGMUVersion(transactionVersion);
int viewId = currentViewId;
ClusterSnapshot clusterSnapshot = getClusterSnapshot(viewId);
long[] versionsValues = create(true, clusterSnapshot.size());
for (Address readFrom : alreadyReadFrom) {
int index = clusterSnapshot.indexOf(readFrom);
if (index == -1) {
//does not exists in current view (is this safe? -- I think that it depends of the state transfer...)
continue;
}
versionsValues[index] = gmuVersion.getVersionValue(readFrom);
}
GMUDistributedVersion maxVersionToRead = new GMUDistributedVersion(cacheName, viewId, this, versionsValues);
if (log.isTraceEnabled()) {
log.tracef("calculateMaxVersionToRead(%s, %s) ==> %s", transactionVersion, alreadyReadFrom, maxVersionToRead);
}
return maxVersionToRead;
}
@Override
public GMUVersion calculateMinVersionToRead(EntryVersion transactionVersion,
Collection<Address> alreadyReadFrom) {
if (transactionVersion == null) {
throw new NullPointerException("Transaction Version cannot be null to calculate the min version to read");
}
if (alreadyReadFrom == null || alreadyReadFrom.isEmpty()) {
if (log.isTraceEnabled()) {
log.tracef("calculateMinVersionToRead(%s, %s) ==> %s", transactionVersion, alreadyReadFrom,
transactionVersion);
}
return updatedVersion(transactionVersion);
}
//the min version is defined by the nodes that we haven't read yet
GMUVersion gmuVersion = toGMUVersion(transactionVersion);
int viewId = currentViewId;
ClusterSnapshot clusterSnapshot = getClusterSnapshot(viewId);
long[] versionValues = create(true, clusterSnapshot.size());
for (int i = 0; i < clusterSnapshot.size(); ++i) {
Address address = clusterSnapshot.get(i);
if (alreadyReadFrom.contains(address)) {
continue;
}
versionValues[i] = gmuVersion.getVersionValue(address);
}
GMUDistributedVersion minVersionToRead = new GMUDistributedVersion(cacheName, viewId, this, versionValues);
if (log.isTraceEnabled()) {
log.tracef("calculateMinVersionToRead(%s, %s) ==> %s", transactionVersion, alreadyReadFrom, minVersionToRead);
}
return minVersionToRead;
}
@Override
public GMUVersion setNodeVersion(EntryVersion version, long value) {
GMUVersion gmuVersion = toGMUVersion(version);
int viewId = currentViewId;
ClusterSnapshot clusterSnapshot = getClusterSnapshot(viewId);
long[] versionValues = create(true, clusterSnapshot.size());
for (int i = 0; i < clusterSnapshot.size(); ++i) {
Address address = clusterSnapshot.get(i);
versionValues[i] = gmuVersion.getVersionValue(address);
}
versionValues[clusterSnapshot.indexOf(getAddress())] = value;
GMUDistributedVersion newVersion = new GMUDistributedVersion(cacheName, viewId, this, versionValues);
if (log.isTraceEnabled()) {
log.tracef("setNodeVersion(%s, %s) ==> %s", version, value, newVersion);
}
return newVersion;
}
@Override
public GMUVersion updatedVersion(EntryVersion entryVersion) {
if (entryVersion instanceof GMUReplicatedVersion) {
return new GMUReplicatedVersion(cacheName, currentViewId, this,
((GMUReplicatedVersion) entryVersion).getThisNodeVersionValue());
} else if (entryVersion instanceof GMUDistributedVersion) {
int viewId = currentViewId;
ClusterSnapshot clusterSnapshot = getClusterSnapshot(viewId);
long[] newVersions = new long[clusterSnapshot.size()];
for (int i = 0; i < clusterSnapshot.size(); ++i) {
newVersions[i] = ((GMUDistributedVersion) entryVersion).getVersionValue(clusterSnapshot.get(i));
}
return new GMUDistributedVersion(cacheName, viewId, this, newVersions);
}
throw new IllegalArgumentException("Cannot handle " + entryVersion);
}
@Override
public synchronized final ClusterSnapshot getClusterSnapshot(int viewId) {
if (viewId < viewIdClusterSnapshot.firstKey()) {
//we don't have this view id anymore
return null;
}
while (!viewIdClusterSnapshot.containsKey(viewId)) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
return viewIdClusterSnapshot.get(viewId);
}
@Override
public final Address getAddress() {
return rpcManager.getAddress();
}
@Override
public synchronized void updateViewHistory(List<CacheView> viewHistory) {
//this is a little overkill =(
for (CacheView cacheView : viewHistory) {
addCacheView(cacheView);
}
}
@Override
public synchronized void addCacheView(CacheView cacheView) {
int viewId = cacheView.getViewId();
if (viewIdClusterSnapshot.containsKey(cacheView.getViewId())) {
//can this happen??
if (log.isTraceEnabled()) {
log.tracef("Update members to view Id %s. But it already exists.", viewId);
}
return;
}
Collection<Address> addresses = cacheView.getMembers();
if (log.isTraceEnabled()) {
log.tracef("Update members to view Id %s. Members are %s", viewId, addresses);
}
currentViewId = viewId;
viewIdClusterSnapshot.put(viewId, new ClusterSnapshot(addresses, HASH));
notifyAll();
}
@Override
public synchronized void gcCacheView(int minViewId) {
viewIdClusterSnapshot.headMap(minViewId).clear();
}
@Override
public synchronized int getViewHistorySize() {
return viewIdClusterSnapshot.size();
}
private long[] create(boolean fill, int size) {
long[] versions = new long[size];
if (fill) {
Arrays.fill(versions, NON_EXISTING);
}
return versions;
}
private long[] increment(GMUVersion initialVersion, int viewId) {
ClusterSnapshot clusterSnapshot = getClusterSnapshot(viewId);
long[] versions = create(false, clusterSnapshot.size());
for (int index = 0; index < clusterSnapshot.size(); ++index) {
versions[index] = initialVersion.getVersionValue(clusterSnapshot.get(index));
}
int myIndex = clusterSnapshot.indexOf(getAddress());
versions[myIndex]++;
return versions;
}
private long[] mergeClustered(int viewId, Collection<GMUVersion> entryVersions) {
ClusterSnapshot clusterSnapshot = getClusterSnapshot(viewId);
long[] versions = create(true, clusterSnapshot.size());
for (GMUVersion entryVersion : entryVersions) {
for (int index = 0; index < clusterSnapshot.size(); ++index) {
versions[index] = Math.max(versions[index], entryVersion.getVersionValue(clusterSnapshot.get(index)));
}
}
return versions;
}
private long[] calculateVersionToCommit(int newViewId, EntryVersion version, Collection<Address> addresses) {
GMUVersion gmuVersion = toGMUVersion(version);
if (addresses == null) {
int oldViewId = gmuVersion.getViewId();
ClusterSnapshot oldClusterSnapshot = getClusterSnapshot(oldViewId);
long commitValue = 0;
for (int i = 0; i < oldClusterSnapshot.size(); ++i) {
commitValue = Math.max(commitValue, gmuVersion.getVersionValue(i));
}
ClusterSnapshot clusterSnapshot = getClusterSnapshot(newViewId);
long[] versions = create(true, clusterSnapshot.size());
for (int i = 0; i < clusterSnapshot.size(); ++i) {
versions[i] = commitValue;
}
return versions;
}
ClusterSnapshot clusterSnapshot = getClusterSnapshot(newViewId);
long[] versions = create(true, clusterSnapshot.size());
List<Integer> ownersIndex = new LinkedList<Integer>();
long commitValue = 0;
for (Address owner : addresses) {
int index = clusterSnapshot.indexOf(owner);
if (index < 0) {
continue;
}
commitValue = Math.max(commitValue, gmuVersion.getVersionValue(owner));
ownersIndex.add(index);
}
for (int index = 0; index < clusterSnapshot.size(); ++index) {
if (ownersIndex.contains(index)) {
versions[index] = commitValue;
} else {
versions[index] = gmuVersion.getVersionValue(clusterSnapshot.get(index));
}
}
return versions;
}
}