com.bigdata.ha.FutureTaskInvariantMon Maven / Gradle / Ivy
/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
[email protected]
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.bigdata.ha;
import java.io.Serializable;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import org.apache.log4j.Logger;
import com.bigdata.concurrent.FutureTaskMon;
import com.bigdata.quorum.Quorum;
import com.bigdata.quorum.QuorumEvent;
import com.bigdata.quorum.QuorumEventEnum;
import com.bigdata.quorum.QuorumListener;
import com.bigdata.util.StackInfoReport;
/**
* A {@link Future} that allows you to cancel a computation if an invariant is
* violated. This class is specifically designed to monitor quorum related
* invariants for HA.
*
* Once an invariant is established, listening for the relevant quorum events
* commences and a check is made to verify that the invariant holds on entry.
* This pattern ensures there is no possibility of a missed event.
*
* This {@link FutureTask} wrapper will return the value of the {@link Callable}
* (or the specified result for the {@link Runnable}) iff the task completes
* successfully without the invariant being violated.
*
* @author Bryan Thompson
* @param
* The generic type of the future.
*/
public abstract class FutureTaskInvariantMon extends FutureTaskMon
implements QuorumListener {
private static final Logger log = Logger.getLogger(FutureTaskInvariantMon.class);
private final Quorum> m_quorum;
/**
* The quorum token on entry.
*/
private final long token;
private final List m_triggers = new CopyOnWriteArrayList();
public FutureTaskInvariantMon(final Callable callable,
final Quorum> quorum) {
super(callable);
if (quorum == null)
throw new IllegalArgumentException();
m_quorum = quorum;
token = quorum.token();
}
public FutureTaskInvariantMon(final Runnable runnable, final T result,
Quorum> quorum) {
super(runnable, result);
if (quorum == null)
throw new IllegalArgumentException();
m_quorum = quorum;
token = quorum.token();
}
/**
* Concrete implementations use this callback hook to establish the
* invariants to be monitored.
*/
abstract protected void establishInvariants();
/**
* {@inheritDoc}
*
* Hook to manage listener registration and establish invariants.
*/
@Override
public void run() {
boolean didStart = false;
m_quorum.addListener(this);
try {
establishInvariants();
didStart = true;
super.run();
} finally {
m_quorum.removeListener(this);
if (!didStart) {
/*
* Guarantee cancelled unless run() invoked.
*/
cancel(true/* mayInterruptIfRunning */);
}
}
}
/**
* Establish an invariant that the specified service is a member of the
* quorum.
*
* @param serviceId
* The service.
*/
public void assertMember(final UUID serviceId) {
m_triggers.add(new QuorumEventInvariant(QuorumEventEnum.MEMBER_REMOVE,
serviceId));
// now check that already a member and break if not
assertMembership(m_quorum.getMembers(), serviceId);
}
/**
* Establish an invariant that the specified service is joined with the met
* quorum.
*
* @param serviceId
* The service.
*/
public void assertJoined(final UUID serviceId) {
m_triggers.add(new QuorumEventInvariant(QuorumEventEnum.SERVICE_LEAVE,
serviceId));
// now check that already joined and break if not
assertMembership(m_quorum.getJoined(), serviceId);
}
/**
* Establish an invariant that the specified service is a not joined with
* the met quorum.
*
* @param serviceId
* The service.
*/
public void assertNotJoined(final UUID serviceId) {
m_triggers.add(new QuorumEventInvariant(QuorumEventEnum.SERVICE_JOIN,
serviceId));
// now check not already joined and break if it is
if (isMember(m_quorum.getJoined(), serviceId))
broken();
}
/**
* Establish an invariant that the specified service is in the quorum
* pipeline.
*
* @param serviceId
* The service.
*/
public void assertInPipeline(final UUID serviceId) {
m_triggers.add(new QuorumEventInvariant(
QuorumEventEnum.PIPELINE_REMOVE, serviceId));
// now check that already in pipeline and break if not
assertMembership(m_quorum.getPipeline(), serviceId);
}
/**
* Establish an invariant that the quorum is met and remains met on the same
* token (the one specified to the constructor).
*/
public void assertQuorumMet() {
m_triggers.add(new QuorumEventInvariant(QuorumEventEnum.QUORUM_BROKE,
null/* serviceId */));
// now check that quorum is met and break if not
if (!m_quorum.isQuorumMet())
broken();
if (m_quorum.token() != token)
broken();
}
/**
* Establish an invariant that the quorum is fully met.
*/
public void assertQuorumFullyMet() {
// no-one must leave!
m_triggers.add(new QuorumEventInvariant(QuorumEventEnum.SERVICE_LEAVE,
null/* serviceId */));
// now check that quorum is fully met on the current token and break if not
if (!m_quorum.isQuorumFullyMet(m_quorum.token()))
broken();
}
private void assertMembership(final UUID[] members, final UUID serviceId) {
if (isMember(members, serviceId))
return;
broken();
}
private boolean isMember(final UUID[] members, final UUID serviceId) {
for (UUID member : members) {
if (member.equals(serviceId))
return true;
}
return false;
}
/**
* Any QuorumEvent must be checked to see if it matches an Invariant trigger
*/
@Override
public void notify(final QuorumEvent e) {
boolean interrupt = false;
for (QuorumEventInvariant inv : m_triggers) {
if (inv.matches(e)) {
interrupt = true;
break;
}
}
// interrupt the call thread
if (interrupt) {
broken();
} else {
if (log.isDebugEnabled())
log.debug("Ignoring event: " + e);
}
}
private void broken() {
log.warn("BROKEN", new StackInfoReport());
cancel(true/*mayInterruptIfRunning*/);
}
@SuppressWarnings("serial")
private class QuorumEventInvariant implements QuorumEvent, Serializable {
private final QuorumEventEnum m_qe;
private final UUID m_sid;
/**
*
* @param qe
* The {@link QuorumEvent} type (required).
* @param sid
* The service {@link UUID} (optional). When null
* the {@link QuorumEventEnum} will be matched for ANY service
* {@link UUID}.
*/
public QuorumEventInvariant(final QuorumEventEnum qe, final UUID sid) {
if (qe == null)
throw new IllegalArgumentException();
m_qe = qe;
m_sid = sid;
}
@Override
public QuorumEventEnum getEventType() {
return m_qe;
}
@Override
public long lastValidToken() {
throw new UnsupportedOperationException();
}
@Override
public long token() {
throw new UnsupportedOperationException();
}
@Override
public UUID getServiceId() {
return m_sid;
}
@Override
public long lastCommitTime() {
throw new UnsupportedOperationException();
}
public boolean matches(final QuorumEvent qe) {
return qe.getEventType() == m_qe
&& (m_sid == null || m_sid.equals(qe.getServiceId()));
}
}
}