/*
* Fenix Framework, a framework to develop Java Enterprise Applications.
*
* Copyright (C) 2013 Fenix Framework Team and/or its affiliates and other contributors as indicated by the @author tags.
*
* This file is part of the Fenix Framework. Read the file COPYRIGHT.TXT for more copyright and licensing information.
*/
package pt.ist.fenixframework.backend.jvstm.lf;
import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pt.ist.fenixframework.backend.jvstm.pstm.CommitOnlyTransaction;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IAtomicLong;
import com.hazelcast.core.ITopic;
import com.hazelcast.core.Message;
import com.hazelcast.core.MessageListener;
public class LockFreeClusterUtils {
private static final Logger logger = LoggerFactory.getLogger(LockFreeClusterUtils.class);
// private static final String FF_GLOBAL_LOCK_NAME = "ff.hzl.global.lock";
// private static final String FF_GLOBAL_LOCK_NUMBER_NAME = "ff.hzl.global.lock.number";
// private static final long FF_GLOBAL_LOCK_LOCKED_VALUE = -1;
private static final String FF_COMMIT_TOPIC_NAME = "ff.hzl.commits";
private static HazelcastInstance HAZELCAST_INSTANCE;
// commit requests that have not been applied yet
private static final AtomicReference<CommitRequest> commitRequestsHead = new AtomicReference<CommitRequest>();
// this avoids iterating from the head every time a commit request arrives. Is only used by the (single) thread that enqueues requests
private static CommitRequest commitRequestsTail = null;
// // where to append commit requests. may be outdated due to concurrency, so we need to be careful when updating this reference
// private static volatile AtomicReference<CommitRequest> commitRequestTail = new AtomicReference<CommitRequest>(null);
// private static final ConcurrentLinkedQueue<CommitRequest> COMMIT_REQUESTS = new ConcurrentLinkedQueue<CommitRequest>();
private LockFreeClusterUtils() {
}
public static void initializeGroupCommunication(JvstmLockFreeConfig thisConfig) {
commitRequestsHead.set(CommitRequest.makeSentinelRequest());
commitRequestsTail = getCommitRequestAtHead();
com.hazelcast.config.Config hzlCfg = thisConfig.getHazelcastConfig();
HAZELCAST_INSTANCE = Hazelcast.newHazelcastInstance(hzlCfg);
// register listener for commit requests
registerListenerForCommitRequests();
}
// private static final ReentrantLock ENQUEUE_LOCK = new ReentrantLock(true);
private static void registerListenerForCommitRequests() {
ITopic<CommitRequest> topic = getHazelcastInstance().getTopic(FF_COMMIT_TOPIC_NAME);
topic.addMessageListener(new MessageListener<CommitRequest>() {
@Override
public final void onMessage(Message<CommitRequest> message) {
// ENQUEUE_LOCK.lock();
// try {
CommitRequest commitRequest = message.getMessageObject();
logger.debug("Received commit request message. id={}, serverId={}", commitRequest.getId(),
commitRequest.getServerId());
commitRequest.assignTransaction();
enqueueCommitRequest(commitRequest);
// } finally {
// ENQUEUE_LOCK.unlock();
// }
}
private final void enqueueCommitRequest(CommitRequest commitRequest) {
// synchronized (LockFreeClusterUtils.FF_COMMIT_TOPIC_NAME) { // TODO REMOVE THIS !!!
CommitRequest last = commitRequestsTail;
// according to Hazelcast, onMessage() runs on a single thread, so this CAS should never fail
if (!last.setNext(commitRequest)) {
enqueueFailed();
}
// update last known tail
commitRequestsTail = commitRequest;
// }
}
private void enqueueFailed() throws AssertionError {
String message = "Impossible condition: failed to enqueue commit request";
logger.error(message);
throw new AssertionError(message);
}
});
}
// public static void initGlobalCommittedNumber(int value) {
// AtomicNumber lockNumber = getHazelcastInstance().getAtomicNumber(FF_GLOBAL_LOCK_NUMBER_NAME);
// lockNumber.compareAndSet(0, value);
// }
// the instance should have been initialized in a single thread within the
// FenixFramework static initializer's lock (via the invocation of the method
// initializeGroupCommunication.
private static HazelcastInstance getHazelcastInstance() {
return HAZELCAST_INSTANCE;
}
public static void notifyStartupComplete() {
logger.info("Notify other nodes that startup completed");
IAtomicLong initMarker = getHazelcastInstance().getAtomicLong("initMarker");
initMarker.incrementAndGet();
}
public static void waitForStartupFromFirstNode() {
logger.info("Waiting for startup from first node");
// check initMarker in AtomicNumber (value 1)
IAtomicLong initMarker = getHazelcastInstance().getAtomicLong("initMarker");
while (initMarker.get() == 0) {
logger.debug("Waiting for first node to startup...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// ignore
}
}
logger.debug("First node startup is complete. We can proceed.");
}
public static int obtainNewServerId() {
/* currently does not reuse the server Id value while any server is up.
This can be changed if needed. However, we currently depend on the first
server getting the AtomicNumber 0 to know that it is the first member
to appear. By reusing numbers with the cluster alive, we either don't
reuse 0 or change the algorithm that detects the first member */
IAtomicLong serverIdGenerator = getHazelcastInstance().getAtomicLong("serverId");
long longId = serverIdGenerator.getAndAdd(1L);
logger.info("Got (long) serverId: {}", longId);
int intId = (int) longId;
if (intId != longId) {
throw new Error("Failed to obtain a valid id");
}
return intId;
}
public static void sendCommitRequest(CommitRequest commitRequest) {
// test for debug, because computing commitRequest.toString() is expensive
if (logger.isDebugEnabled()) {
logger.debug("Send commit request to others: {}", commitRequest);
}
ITopic<CommitRequest> topic = getHazelcastInstance().getTopic(FF_COMMIT_TOPIC_NAME);
topic.publish(commitRequest);
}
/**
* Get the first element in the commit requests queue.
*
* @return The {@link CommitRequest} at the head of the queue.
*/
public static CommitRequest getCommitRequestAtHead() {
return commitRequestsHead.get();
}
public static CommitRequest getCommitRequestsTail() {
return commitRequestsTail;
}
/**
* Clears the given commit request from the head of the remote commits queue if: (1) there is a next one; AND (2) the head is
* still the given request. This method should only be invoked when the commit request to remove is already handled (either
* committed or marked as invalid).
*
* @param commitRequest The commitRequest to remove from head
* @return The commit request left at the head. This can be either: (1) The commit request given as argument (if it could not
* be removed); (2) the commit request following the one given in the argument; or (3) any other commit request (if
* the one given as argument was no longer at the head (which means it had been removed already by another thread.
*/
public static CommitRequest tryToRemoveCommitRequest(CommitRequest commitRequest) {
CommitRequest next = commitRequest.getNext();
if (next == null) {
logger.debug("Commit request {} has no next yet. Must remain at the head", commitRequest.getId());
return commitRequest;
}
if (commitRequestsHead.compareAndSet(commitRequest, next)) {
logger.debug("Removed commit request {} from the head.", commitRequest.getId());
return next;
} else {
logger.debug("Commit request {} was no longer at the head.", commitRequest.getId());
return commitRequestsHead.get();
}
}
public static void shutdown() {
HazelcastInstance hzl = getHazelcastInstance();
hzl.getTopic(FF_COMMIT_TOPIC_NAME).destroy();
hzl.getLifecycleService().shutdown();
/* strangely this is here. Perhaps we should move these clear()s
elsewhere. They are needed when the classes are reused via
FenixFramework.shutdown()/initialize(). I guess these maps should be
in this class not in COTx */
CommitOnlyTransaction.txVersionToCommitIdMap.clear();
CommitOnlyTransaction.commitsMap.clear();
}
/**
* Get the number of members in the cluster.
*
* @return The number of members in the cluster or <code>-1</code> if the information is not available
*/
public static int getNumMembers() {
if (!getHazelcastInstance().getLifecycleService().isRunning()) {
return -1;
} else {
return getHazelcastInstance().getCluster().getMembers().size();
}
}
//////////////// TO DELETE BELOW THIS ///////////////////////
//////////////// TO DELETE BELOW THIS ///////////////////////
//////////////// TO DELETE BELOW THIS ///////////////////////
//////////////// TO DELETE BELOW THIS ///////////////////////
//////////////// TO DELETE BELOW THIS ///////////////////////
// public static int globalLock() {
// logger.debug("Will get global cluster lock...");
//
// try {
// AtomicNumber lockNumber = getHazelcastInstance().getAtomicNumber(FF_GLOBAL_LOCK_NUMBER_NAME);
//
//// int ourLockValue = 0 - DomainClassInfo.getServerId();
// do {
// long currentValue = lockNumber.get();
// boolean unlocked = currentValue != FF_GLOBAL_LOCK_LOCKED_VALUE;
//
// if (unlocked && lockNumber.compareAndSet(currentValue, FF_GLOBAL_LOCK_LOCKED_VALUE)) {
// logger.debug("Acquired global cluster lock. ({} -> {})", currentValue, FF_GLOBAL_LOCK_LOCKED_VALUE);
//
// return (int) currentValue; // transaction counters fit into an integer
// } else {
// logger.debug("Global lock taken. Retrying...");
//
// globalLockIsNotYetAvailable();
// }
// } while (true);
// } catch (RuntimeException e) {
// logger.error("Failed to acquire global lock");
// throw new TransactionError(e);
// }
// }
//
// /* We'll retry later. Several mechanisms can be used here. Which is the
// best? For now we try to help a little by spending time checking if the
// COMMIT_REQUESTS queue requires any processing. Also, this may help other
// transactions that are starting to go ahead */
// private static void globalLockIsNotYetAvailable() {
// // first naive version. just burn cpu
// Thread.yield();
// }
//
// public static void globalUnlock(int txNum) {
// logger.debug("Will release global cluster lock ( -> {})", txNum);
// try {
// AtomicNumber lockNumber = getHazelcastInstance().getAtomicNumber(FF_GLOBAL_LOCK_NUMBER_NAME);
// lockNumber.set(txNum);
// } catch (RuntimeException e) {
// logger.error("Failed to release global lock");
// throw new TransactionError(e);
// }
// }
}