org.jgroups.protocols.pbcast.StreamingStateTransfer Maven / Gradle / Ivy
package org.jgroups.protocols.pbcast;
import org.jgroups.*;
import org.jgroups.annotations.*;
import org.jgroups.stack.IpAddress;
import org.jgroups.stack.Protocol;
import org.jgroups.stack.StateTransferInfo;
import org.jgroups.util.*;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
* Base class for state transfer protocols which use streaming (or chunking) to transfer state between two members.
*
*
* The major advantage of this approach is that transferring application state to a
* joining member of a group does not entail loading of the complete application
* state into memory. The application state, for example, might be located entirely
* on some form of disk based storage. The default STATE_TRANSFER
protocol
* requires this state to be loaded entirely into memory before being
* transferred to a group member while the streaming state transfer protocols do not.
* Thus the streaming state transfer protocols are able to
* transfer application state that is very large (>1Gb) without a likelihood of the
* such transfer resulting in OutOfMemoryException.
*
* Note that prior to 3.0, there was only 1 streaming protocol: STATE. In 3.0 the functionality
* was split between STATE and STATE_SOCK, and common functionality moved up
* into StreamingStateTransfer.
* @author Bela Ban
* @author Vladimir Blagojevic
* @see STATE_TRANSFER
* @see STATE
* @see STATE_SOCK
* @since 3.0
*/
@MBean(description="Streaming state transfer protocol base class")
public abstract class StreamingStateTransfer extends Protocol implements ProcessingQueue.Handler {
/*
* ----------------------------------------------Properties -----------------------------------
*/
@Property(description="Size (in bytes) of the state transfer buffer")
protected int buffer_size=8 * 1024;
@Property(description="Maximum number of pool threads serving state requests")
protected int max_pool=5;
@Property(description="Keep alive for pool threads serving state requests")
protected long pool_thread_keep_alive=20 * 1000;
/*
* --------------------------------------------- JMX statistics -------------------------------
*/
protected final AtomicInteger num_state_reqs=new AtomicInteger(0);
protected final AtomicLong num_bytes_sent=new AtomicLong(0);
protected double avg_state_size;
/*
* --------------------------------------------- Fields ---------------------------------------
*/
protected Address local_addr;
protected volatile Address state_provider;
@GuardedBy("members")
protected final List members=new ArrayList();
/* Set to true if the FLUSH protocol is detected in the protocol stack */
protected volatile boolean flushProtocolInStack=false;
/** Thread pool (configured with {@link #max_pool} and {@link #pool_thread_keep_alive}) to run
* {@link StreamingStateTransfer.StateGetter} threads on */
protected ThreadPoolExecutor thread_pool;
/** List of members requesting state. Only a single state request is handled at any time */
protected final ProcessingQueue state_requesters=new ProcessingQueue().setHandler(this);
@ManagedAttribute public int getNumberOfStateRequests() {return num_state_reqs.get();}
@ManagedAttribute public long getNumberOfStateBytesSent() {return num_bytes_sent.get();}
@ManagedAttribute public double getAverageStateSize() {return avg_state_size;}
@ManagedAttribute public int getThreadPoolSize() {return thread_pool.getPoolSize();}
@ManagedAttribute public long getThreadPoolCompletedTasks() {return thread_pool.getCompletedTaskCount();}
public List requiredDownServices() {
List retval=new ArrayList(2);
retval.add(Event.GET_DIGEST);
retval.add(Event.OVERWRITE_DIGEST);
return retval;
}
public void resetStats() {
super.resetStats();
num_state_reqs.set(0);
num_bytes_sent.set(0);
avg_state_size=0;
}
public void init() throws Exception {
super.init();
thread_pool=createThreadPool();
}
public void destroy() {
thread_pool.shutdown();
super.destroy();
}
public void start() throws Exception {
Map map=new HashMap();
map.put("state_transfer", true);
map.put("protocol_class", getClass().getName());
up_prot.up(new Event(Event.CONFIG, map));
if(buffer_size <= 0)
throw new IllegalArgumentException("buffer_size has to be > 0");
}
public void stop() {
super.stop();
}
public Object down(Event evt) {
switch(evt.getType()) {
case Event.VIEW_CHANGE:
handleViewChange((View)evt.getArg());
break;
case Event.GET_STATE:
StateTransferInfo info=(StateTransferInfo)evt.getArg();
Address target=info.target;
if(target != null && target.equals(local_addr)) {
log.error("%s: cannot fetch state from myself", local_addr);
target=null;
}
if(target == null)
target=determineCoordinator();
if(target == null) {
log.debug("%s: first member (no state)", local_addr);
up_prot.up(new Event(Event.STATE_TRANSFER_INPUTSTREAM_CLOSED, new StateTransferResult()));
}
else {
state_provider=target;
Message state_req=new Message(target).putHeader(this.id, new StateHeader(StateHeader.STATE_REQ))
.setFlag(Message.Flag.SKIP_BARRIER, Message.Flag.DONT_BUNDLE, Message.Flag.OOB);
log.debug("%s: asking %s for state", local_addr, target);
down_prot.down(new Event(Event.MSG, state_req));
}
return null; // don't pass down any further !
case Event.CONFIG:
handleConfig((Map)evt.getArg());
break;
case Event.SET_LOCAL_ADDRESS:
local_addr=(Address)evt.getArg();
break;
}
return down_prot.down(evt); // pass on to the layer below us
}
public Object up(Event evt) {
switch(evt.getType()) {
case Event.MSG:
Message msg=(Message)evt.getArg();
StateHeader hdr=(StateHeader)msg.getHeader(this.id);
if(hdr != null) {
Address sender=msg.getSrc();
switch(hdr.type) {
case StateHeader.STATE_REQ:
state_requesters.add(msg.getSrc());
break;
case StateHeader.STATE_RSP:
handleStateRsp(sender, hdr);
break;
case StateHeader.STATE_PART:
handleStateChunk(sender, msg.getRawBuffer(), msg.getOffset(), msg.getLength());
break;
case StateHeader.STATE_EOF:
log.trace("%s <-- EOF <-- %s", local_addr, sender);
handleEOF(sender);
break;
case StateHeader.STATE_EX:
handleException((Throwable)msg.getObject());
break;
default:
log.error("%s: type %d not known in StateHeader", local_addr, hdr.type);
break;
}
return null;
}
break;
case Event.TMP_VIEW:
case Event.VIEW_CHANGE:
handleViewChange((View)evt.getArg());
break;
case Event.CONFIG:
handleConfig((Map)evt.getArg());
break;
}
return up_prot.up(evt);
}
/*
* --------------------------- Private Methods ------------------------------------------------
*/
/**
* When FLUSH is used we do not need to pass digests between members (see JGroups/doc/design/FLUSH.txt)
* @return true if use of digests is required, false otherwise
*/
protected boolean isDigestNeeded() {
return !flushProtocolInStack;
}
protected void handleConfig(Map config) {
if(config != null && config.containsKey("flush_supported"))
flushProtocolInStack=true;
if(config != null && config.containsKey("state_transfer"))
throw new IllegalArgumentException("Protocol stack must have only one state transfer protocol");
}
protected void handleStateChunk(Address sender, byte[] buffer, int offset, int length) {
}
protected void handleEOF(Address sender) {
state_provider=null;
down_prot.down(new Event(Event.GET_VIEW_FROM_COORD)); // https://issues.jboss.org/browse/JGRP-1751
}
protected void handleException(Throwable exception) {
state_provider=null; // ??
up_prot.up(new Event(Event.STATE_TRANSFER_INPUTSTREAM_CLOSED, new StateTransferResult(exception)));
}
protected void getStateFromApplication(Address requester, OutputStream out, boolean use_separate_thread) {
if(out == null || requester == null)
throw new IllegalArgumentException("output stream and requester's address have to be non-null");
StateGetter state_getter=new StateGetter(requester, out);
if(use_separate_thread)
thread_pool.execute(state_getter);
else
state_getter.run();
}
protected void setStateInApplication(InputStream in, Object resource, final Address provider) {
log.debug("%s: setting the state in the aplication", local_addr);
try {
up_prot.up(new Event(Event.STATE_TRANSFER_INPUTSTREAM, in));
up_prot.up(new Event(Event.STATE_TRANSFER_INPUTSTREAM_CLOSED, new StateTransferResult()));
down_prot.down(new Event(Event.GET_VIEW_FROM_COORD)); // https://issues.jboss.org/browse/JGRP-1751
}
catch(Throwable t) {
handleException(t);
}
finally {
Util.close(in);
close(resource);
if(isDigestNeeded()) {
openBarrierAndResumeStable();
closeHoleFor(provider);
}
}
}
@ManagedOperation(description="Closes BARRIER and suspends STABLE")
public void closeBarrierAndSuspendStable() {
if(!isDigestNeeded())
return;
log.trace("%s: sending down CLOSE_BARRIER and SUSPEND_STABLE", local_addr);
down_prot.down(new Event(Event.CLOSE_BARRIER));
down_prot.down(new Event(Event.SUSPEND_STABLE));
}
@ManagedOperation(description="Opens BARRIER and resumes STABLE")
public void openBarrierAndResumeStable() {
if(!isDigestNeeded())
return;
log.trace("%s: sending down OPEN_BARRIER and RESUME_STABLE", local_addr);
openBarrier();
resumeStable();
}
protected void openBarrier() {
down_prot.down(new Event(Event.OPEN_BARRIER));
}
protected void resumeStable() {
down_prot.down(new Event(Event.RESUME_STABLE));
}
protected void sendEof(Address requester) {
try {
Message eof_msg=new Message(requester).putHeader(getId(), new StateHeader(StateHeader.STATE_EOF));
log.trace("%s --> EOF --> %s", local_addr, requester);
down(new Event(Event.MSG, eof_msg));
}
catch(Throwable t) {
log.error("%s: failed sending EOF to %s", local_addr, requester);
}
}
protected void sendException(Address requester, Throwable exception) {
try {
Message ex_msg=new Message(requester, null, exception).putHeader(getId(), new StateHeader(StateHeader.STATE_EX));
down(new Event(Event.MSG, ex_msg));
}
catch(Throwable t) {
log.error("%s: failed sending exception %s to %s", local_addr, exception.toString(), requester);
}
}
protected ThreadPoolExecutor createThreadPool() {
ThreadPoolExecutor threadPool=new ThreadPoolExecutor(0, max_pool, pool_thread_keep_alive,
TimeUnit.MILLISECONDS, new SynchronousQueue());
ThreadFactory factory=new ThreadFactory() {
private final AtomicInteger thread_id=new AtomicInteger(1);
public Thread newThread(final Runnable command) {
return getThreadFactory().newThread(command, "StreamingStateTransfer-sender-" + thread_id.getAndIncrement());
}
};
threadPool.setRejectedExecutionHandler(new ShutdownRejectedExecutionHandler(threadPool.getRejectedExecutionHandler()));
threadPool.setThreadFactory(factory);
return threadPool;
}
protected Address determineCoordinator() {
synchronized(members) {
for(Address member : members) {
if(!local_addr.equals(member))
return member;
}
}
return null;
}
protected void handleViewChange(View v) {
List new_members=v.getMembers();
synchronized(members) {
members.clear();
members.addAll(new_members);
}
state_requesters.retainAll(new_members); // remove non members from list of members requesting state
}
public void handle(Address state_requester) {
handleStateReq(state_requester);
}
protected void handleStateReq(Address requester) {
if(requester == null)
return;
log.debug("%s: received state request from %s", local_addr, requester);
// 1. Get the digest (if needed)
Digest digest=null;
if(isDigestNeeded()) {
try {
punchHoleFor(requester);
closeBarrierAndSuspendStable();
digest=(Digest)down_prot.down(Event.GET_DIGEST_EVT);
}
catch(Throwable t) {
sendException(requester, t);
resumeStable();
closeHoleFor(requester);
return;
}
finally {
openBarrier();
}
}
// 2. Send the STATE_RSP message to the requester
StateHeader hdr=new StateHeader(StateHeader.STATE_RSP, null, digest);
// gives subclasses a chance to modify this header, e.g. STATE_SOCK adds the server socket's address
modifyStateResponseHeader(hdr);
Message state_rsp=new Message(requester).putHeader(this.id, hdr);
log.debug("%s: responding to state requester %s", local_addr, requester);
down_prot.down(new Event(Event.MSG, state_rsp));
if(stats)
num_state_reqs.incrementAndGet();
try {
createStreamToRequester(requester);
}
catch(Throwable t) {
sendException(requester, t);
}
}
/** Creates an OutputStream to the state requester to write the state */
protected void createStreamToRequester(Address requester) {}
/** Creates an InputStream to the state provider to read the state. Return the input stream and a handback
* object as a tuple. The handback object is handed back to the subclass when done, or in case of an error
* (e.g. to clean up resources) */
protected abstract Tuple createStreamToProvider(Address provider, StateHeader hdr) throws Exception;
protected void close(Object resource) {}
protected boolean useAsyncStateDelivery() {return false;}
protected void modifyStateResponseHeader(StateHeader hdr) {}
protected void handleStateRsp(final Address provider, StateHeader hdr) {
// 1: set the digest if needed
if(isDigestNeeded()) {
try {
punchHoleFor(provider);
closeBarrierAndSuspendStable(); // fix for https://jira.jboss.org/jira/browse/JGRP-1013
down_prot.down(new Event(Event.OVERWRITE_DIGEST, hdr.getDigest())); // set the digest (e.g. in NAKACK)
}
catch(Throwable t) {
handleException(t);
openBarrierAndResumeStable();
closeHoleFor(provider);
return;
}
}
// 2: establish the connection to the provider (to fetch the state)
InputStream in=null;
Object resource=null;
try {
Tuple tuple=createStreamToProvider(provider, hdr); // tuple's 2nd arg is a handback object
in=tuple.getVal1();
resource=tuple.getVal2();
}
catch(Throwable t) {
handleException(t);
Util.close(in);
close(resource);
if(isDigestNeeded()) {
openBarrierAndResumeStable();
closeHoleFor(provider);
}
return;
}
// 3: set the state in the application
if(useAsyncStateDelivery()) {
final InputStream input=in;
final Object res=resource;
// use another thread to read state because the state requester has to receive state chunks from the state provider
Thread t=getThreadFactory().newThread(new Runnable() {
public void run() {
setStateInApplication(input, res, provider);
}
}, "STATE state reader");
t.start();
}
else
setStateInApplication(in, resource, provider);
}
protected void punchHoleFor(Address member) {
down_prot.down(new Event(Event.PUNCH_HOLE,member));
}
protected void closeHoleFor(Address member) {
down_prot.down(new Event(Event.CLOSE_HOLE, member));
}
public static class StateHeader extends Header {
public static final byte STATE_REQ = 1;
public static final byte STATE_RSP = 2;
public static final byte STATE_PART = 3;
public static final byte STATE_EOF = 4;
public static final byte STATE_EX = 5;
protected byte type=0;
protected Digest digest; // digest of sender (if type is STATE_RSP)
protected IpAddress bind_addr;
public StateHeader() {
} // for externalization
public StateHeader(byte type) {
this.type=type;
}
public StateHeader(byte type, Digest digest) {
this.type=type;
this.digest=digest;
}
public StateHeader(byte type, IpAddress bind_addr, Digest digest) {
this.type=type;
this.digest=digest;
this.bind_addr=bind_addr;
}
public int getType() {
return type;
}
public Digest getDigest() {
return digest;
}
public String toString() {
StringBuilder sb=new StringBuilder();
sb.append("type=").append(type2Str(type));
if(digest != null)
sb.append(", digest=").append(digest);
if(bind_addr != null)
sb.append(", bind_addr=" + bind_addr);
return sb.toString();
}
static String type2Str(int t) {
switch(t) {
case STATE_REQ: return "STATE_REQ";
case STATE_RSP: return "STATE_RSP";
case STATE_PART: return "STATE_PART";
case STATE_EOF: return "STATE_EOF";
case STATE_EX: return "STATE_EX";
default: return "";
}
}
public void writeTo(DataOutput out) throws Exception {
out.writeByte(type);
Util.writeStreamable(digest, out);
Util.writeStreamable(bind_addr, out);
}
public void readFrom(DataInput in) throws Exception {
type=in.readByte();
digest=(Digest)Util.readStreamable(Digest.class, in);
bind_addr=(IpAddress)Util.readStreamable(IpAddress.class, in);
}
public int size() {
int retval=Global.BYTE_SIZE; // type
retval+=Global.BYTE_SIZE; // presence byte for my_digest
if(digest != null)
retval+=digest.serializedSize(true);
retval+=Util.size(bind_addr);
return retval;
}
}
/**
* Thread which invokes {@link MessageListener#getState(java.io.OutputStream)} in the application
*/
protected class StateGetter implements Runnable {
protected final Address requester;
protected final OutputStream output;
public StateGetter(Address requester, OutputStream output) {
this.requester=requester;
this.output=output;
}
public void run() {
try {
log.debug("%s: getting the state from the application", local_addr);
up_prot.up(new Event(Event.STATE_TRANSFER_OUTPUTSTREAM, output));
output.flush();
sendEof(requester); // send an EOF to the remote consumer
}
catch(Throwable e) {
sendException(requester, e); // send the exception to the remote consumer
}
finally {
if(isDigestNeeded()) {
resumeStable();
// openBarrier(); // was done after getting the digest
closeHoleFor(requester);
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy