/*
* JBoss, Home of Professional Open Source
* Copyright 2011 Red Hat Inc. 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 copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* 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,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package org.infinispan.statetransfer;
import org.infinispan.config.Configuration;
import org.infinispan.container.DataContainer;
import org.infinispan.container.entries.InternalCacheEntry;
import org.infinispan.distribution.ch.ConsistentHash;
import org.infinispan.loaders.CacheLoaderException;
import org.infinispan.loaders.CacheStore;
import org.infinispan.notifications.cachelistener.CacheNotifier;
import org.infinispan.remoting.MembershipArithmetic;
import org.infinispan.remoting.rpc.RpcManager;
import org.infinispan.remoting.transport.Address;
import org.infinispan.util.ByRef;
import org.infinispan.util.ReadOnlyDataContainerBackedKeySet;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
/**
* Task which pushes keys to new nodes during join.
* <p/>To reduce the duration of the state transfer, all the existing members participate in the state transfer.
* <p/>We use a <code>ConsistentHash</code> to decide which existing node is supposed to push a certain key.
* <p/> Example:<p/>
* <pre>
* - The membership is {A,B,C,D}
* - The new view is {A,B,C,D,E,F}
* - For K, the old CH is A,B and the new CH is A,C
* - A (since it is K's owner) now pushes K to C
* - For K2, the old CH is A,B and the new CH is B,C
* - B (since it is the backup owner and A left) pushes K2 to C
* </pre>
*
* @author Bela Ban
* @author Dan Berindei <dan@infinispan.org>
* @author Pedro Ruivo
* @since 4.2
*/
public class ReplicatedStateTransferTask extends BaseStateTransferTask {
private static final Log log = LogFactory.getLog(ReplicatedStateTransferTask.class);
private final ReplicatedStateTransferManagerImpl stateTransferManager;
public ReplicatedStateTransferTask(RpcManager rpcManager, Configuration configuration, DataContainer dataContainer,
ReplicatedStateTransferManagerImpl stateTransferManager, StateTransferLock stateTransferLock,
CacheNotifier cacheNotifier, int newViewId, Collection<Address> members,
ConsistentHash chOld, ConsistentHash chNew, boolean initialView) {
super(stateTransferManager, rpcManager, stateTransferLock, cacheNotifier, configuration, dataContainer, members, newViewId, chNew, chOld, initialView);
this.stateTransferManager = stateTransferManager;
}
@Override
public void doPerformStateTransfer() throws Exception {
if (!stateTransferManager.startStateTransfer(newViewId, members, initialView))
return;
if (log.isDebugEnabled())
log.debugf("Commencing state transfer %d on node: %s. Before start, data container had %d entries",
newViewId, self, dataContainer.size(null));
// Don't need to log anything, all transactions will be blocked
//distributionManager.getTransactionLogger().enable();
stateTransferLock.blockNewTransactions(newViewId);
Set<Address> joiners = chOld != null ? MembershipArithmetic.getMembersJoined(chOld.getCaches(), chNew.getCaches()) : chNew.getCaches();
if (joiners.isEmpty()) {
log.tracef("No joiners in view %s, skipping replication", newViewId);
} else {
log.tracef("Replicating: chOld = %s, chNew = %s", chOld, chNew);
if (configuration.isStateTransferEnabled() && !initialView) {
// Contains the state to be pushed to the joiners. The state is a collection of cache entries
// We're keeping it inside a ByRef so that the replicate() method can reset it when it pushes a chunk
// The transfer is on a separate thread so we can't modify the collection itself
final ByRef<Collection<InternalCacheEntry>> state = new ByRef<Collection<InternalCacheEntry>>(new ArrayList<InternalCacheEntry>());
//for total order
beforeStartPushing();
for (InternalCacheEntry ice : dataContainer) {
replicate(ice.getKey(), ice, chOld, joiners, null, state);
}
// Only fetch the data from the cache store if the cache store is not shared
CacheStore cacheStore = stateTransferManager.getCacheStoreForStateTransfer();
if (cacheStore != null) {
for (Object key : cacheStore.loadAllKeys(new ReadOnlyDataContainerBackedKeySet(dataContainer))) {
replicate(key, null, chOld, joiners, cacheStore, state);
}
} else {
if (trace) log.trace("No cache store or cache store is shared, not replicating stored keys");
}
// Push any remaining state chunks
pushPartialState(joiners, state.get(), null);
// And wait for all the push RPCs to end
finishPushingState();
} else {
if (!initialView) log.trace("State transfer not enabled, so not pushing state");
}
}
}
/**
* Computes the list of old and new servers for a given key K and value V. Adds (K, V) to the <code>states</code> map
* if K should be pushed to other servers. Adds K to the <code>keysToRemove</code> list if this node is no longer an
* owner for K.
*
* @param key The key
* @param value The value; <code>null</code> if the value is not in the data container
* @param chOld The old (current) consistent hash
* @param joiners The new members of the cache
* @param cacheStore If the value is <code>null</code>, try to load it from this cache store
* @param stateRef The result collection of entries to be pushed to the joiners
*/
private void replicate(Object key, InternalCacheEntry value, ConsistentHash chOld, Collection<Address> joiners,
CacheStore cacheStore, ByRef<Collection<InternalCacheEntry>> stateRef) throws StateTransferCancelledException {
// 1. Get the old primary owner for key K
// That node will be the "pushing owner" for key K
final Address pushingOwner = chOld.primaryLocation(key);
if (trace) log.tracef("Replicating key %s, pushing owner is %s",
key, pushingOwner);
// 2. Push K to all the new nodes
if (self.equals(pushingOwner)) {
if (value == null) {
try {
value = cacheStore.load(key);
} catch (CacheLoaderException e) {
log.failedLoadingValueFromCacheStore(key);
}
}
Collection<InternalCacheEntry> state = stateRef.get();
if (value != null)
state.add(value);
// if we have a full chunk, start pushing it to the joiners
if (state.size() >= stateTransferChunkSize) {
pushPartialState(joiners, state, null);
stateRef.set(new ArrayList());
}
}
}
}