package org.jgroups.blocks;
import org.jgroups.*;
import org.jgroups.conf.ClassConfigurator;
import org.jgroups.logging.Log;
import org.jgroups.logging.LogFactory;
import org.jgroups.protocols.TP;
import org.jgroups.AnycastAddress;
import org.jgroups.protocols.relay.SiteMaster;
import org.jgroups.stack.DiagnosticsHandler;
import org.jgroups.stack.Protocol;
import org.jgroups.util.Buffer;
import org.jgroups.util.Util;
import java.io.DataInput;
import java.io.DataOutput;
import java.util.*;
import java.util.concurrent.ConcurrentMap;
/**
* Framework to send requests and receive matching responses (matching on
* request ID).
* Multiple requests can be sent at a time. Whenever a response is received,
* the correct <code>RspCollector</code> is looked up (key = id) and its
* method <code>receiveResponse()</code> invoked. A caller may use
* <code>done()</code> to signal that no more responses are expected, and that
* the corresponding entry may be removed.
* <p>
* <code>RequestCorrelator</code> can be installed at both client and server
* sides, it can also switch roles dynamically; i.e., send a request and at
* the same time process an incoming request (when local delivery is enabled,
* this is actually the default).
* <p>
*
* @author Bela Ban
*/
public class RequestCorrelator {
/** The protocol layer to use to pass up/down messages. Can be either a Protocol or a Transport */
protected Protocol transport=null;
/**
* The table of pending requests (keys=Long (request IDs), values=<tt>RequestEntry</tt>)
*/
protected final ConcurrentMap<Long, RspCollector> requests=Util.createConcurrentMap();
/** The handler for the incoming requests. It is called from inside the dispatcher thread */
protected RequestHandler request_handler=null;
/** Possibility for an external marshaller to marshal/unmarshal responses */
protected RpcDispatcher.Marshaller marshaller=null;
/** makes the instance unique (together with IDs) */
protected short id=ClassConfigurator.getProtocolId(this.getClass());
/** The address of this group member */
protected Address local_addr=null;
protected volatile View view;
protected boolean started=false;
private final MyProbeHandler probe_handler=new MyProbeHandler(requests);
protected static final Log log=LogFactory.getLog(RequestCorrelator.class);
/**
* Constructor. Uses transport to send messages. If <code>handler</code>
* is not null, all incoming requests will be dispatched to it (via
* <code>handle(Message)</code>).
*
* @param id Used to differentiate between different RequestCorrelators
* (e.g. in different protocol layers). Has to be unique if multiple
* request correlators are used.
*
* @param transport Used to send/pass up requests. Is a Protocol (up_prot.up()/down_prot.down() will be used)
*
* @param handler Request handler. Method <code>handle(Message)</code>
* will be called when a request is received.
*/
public RequestCorrelator(short id, Protocol transport, RequestHandler handler, Address local_addr) {
this.id = id;
this.transport = transport;
this.local_addr = local_addr;
request_handler = handler;
start();
}
public RequestCorrelator(Protocol transport, RequestHandler handler, Address local_addr) {
this.transport = transport;
this.local_addr = local_addr;
request_handler = handler;
start();
}
public void setRequestHandler(RequestHandler handler) {
request_handler=handler;
start();
}
public RpcDispatcher.Marshaller getMarshaller() {
return marshaller;
}
public void setMarshaller(RpcDispatcher.Marshaller marshaller) {
this.marshaller=marshaller;
}
public void sendRequest(long id, List<Address> dest_mbrs, Message msg, RspCollector coll) throws Exception {
sendRequest(id, dest_mbrs, msg, coll, new RequestOptions().setAnycasting(false));
}
/**
* Sends a request to a group. If no response collector is given, no responses are expected (making the call asynchronous)
*
* @param id The request ID. Must be unique for this JVM (e.g. current time in millisecs)
* @param dest_mbrs The list of members who should receive the call. Usually a group RPC
* is sent via multicast, but a receiver drops the request if its own address
* is not in this list. Will not be used if it is null.
* @param msg The request to be sent. The body of the message carries
* the request data
*
* @param coll A response collector (usually the object that invokes this method). Its methods
* <code>receiveResponse()</code> and <code>suspect()</code> will be invoked when a message has been received
* or a member is suspected, respectively.
*/
public void sendRequest(long id, Collection<Address> dest_mbrs, Message msg, RspCollector coll, RequestOptions options) throws Exception {
if(transport == null) {
if(log.isWarnEnabled()) log.warn("transport is not available !");
return;
}
// i. Create the request correlator header and add it to the msg
// ii. If a reply is expected (coll != null), add a coresponding entry in the pending requests table
// iii. If deadlock detection is enabled, set/update the call stack
// iv. Pass the msg down to the protocol layer below
Header hdr=options.hasExclusionList()?
new MultiDestinationHeader(Header.REQ, id, (coll != null), this.id, options.getExclusionList())
: new Header(Header.REQ, id, (coll != null), this.id);
msg.putHeader(this.id, hdr);
if(coll != null) {
addEntry(hdr.id, coll);
// make sure no view is received before we add ourself as a view handler (https://issues.jboss.org/browse/JGRP-1428)
coll.viewChange(view);
}
if(options.getAnycasting()) {
if(options.useAnycastAddresses()) {
Message copy=msg.copy(true);
AnycastAddress dest=new AnycastAddress(dest_mbrs);
copy.setDest(dest);
transport.down(new Event(Event.MSG, copy));
}
else {
for(Address mbr: dest_mbrs) {
Message copy=msg.copy(true);
copy.setDest(mbr);
transport.down(new Event(Event.MSG, copy));
}
}
}
else
transport.down(new Event(Event.MSG, msg));
}
/**
* Sends a request to a single destination
* @param id
* @param target
* @param msg
* @param coll
* @throws Exception
*/
public void sendUnicastRequest(long id, Address target, Message msg, RspCollector coll) throws Exception {
if(transport == null) {
if(log.isWarnEnabled()) log.warn("transport is not available !");
return;
}
// i. Create the request correlator header and add it to the msg
// ii. If a reply is expected (coll != null), add a coresponding entry in the pending requests table
// iii. If deadlock detection is enabled, set/update the call stack
// iv. Pass the msg down to the protocol layer below
Header hdr=new Header(Header.REQ, id, (coll != null), this.id);
msg.putHeader(this.id, hdr);
if(coll != null) {
addEntry(hdr.id, coll);
// make sure no view is received before we add ourself as a view handler (https://issues.jboss.org/browse/JGRP-1428)
coll.viewChange(view);
}
transport.down(new Event(Event.MSG, msg));
}
/**
* Used to signal that a certain request may be garbage collected as all responses have been received.
*/
public void done(long id) {
removeEntry(id);
}
/**
* <b>Callback</b>.
* <p>
* Called by the protocol below when a message has been received. The
* algorithm should test whether the message is destined for us and,
* if not, pass it up to the next layer. Otherwise, it should remove
* the header and check whether the message is a request or response.
* In the first case, the message will be delivered to the request
* handler registered (calling its <code>handle()</code> method), in the
* second case, the corresponding response collector is looked up and
* the message delivered.
* @param evt The event to be received
* @return Whether or not the event was consumed. If true, don't pass message up, else pass it up
*/
public boolean receive(Event evt) {
switch(evt.getType()) {
case Event.SUSPECT: // don't wait for responses from faulty members
receiveSuspect((Address)evt.getArg());
break;
case Event.VIEW_CHANGE: // adjust number of responses to wait for
receiveView((View)evt.getArg());
break;
case Event.SET_LOCAL_ADDRESS:
setLocalAddress((Address)evt.getArg());
break;
case Event.MSG:
if(receiveMessage((Message)evt.getArg()))
return true; // message was consumed, don't pass it up
break;
case Event.SITE_UNREACHABLE:
SiteMaster site_master=(SiteMaster)evt.getArg();
short site=site_master.getSite();
setSiteUnreachable(site);
return true;
}
return false;
}
public final void start() {
started=true;
}
public void stop() {
started=false;
for(RspCollector coll: requests.values())
coll.transportClosed();
requests.clear();
}
public void registerProbeHandler(TP transport) {
if(transport != null)
transport.registerProbeHandler(probe_handler);
}
public void unregisterProbeHandler(TP transport) {
if(transport != null)
transport.unregisterProbeHandler(probe_handler);
}
// .......................................................................
/**
* <tt>Event.SUSPECT</tt> event received from a layer below.
* <p>
* All response collectors currently registered will
* be notified that <code>mbr</code> may have crashed, so they won't
* wait for its response.
*/
public void receiveSuspect(Address mbr) {
if(mbr == null) return;
if(log.isDebugEnabled()) log.debug("suspect=" + mbr);
// copy so we don't run into bug #761804 - Bela June 27 2003
// copy=new ArrayList(requests.values()); // removed because ConcurrentReaderHashMap can tolerate concurrent mods (bela May 8 2006)
for(RspCollector coll: requests.values()) {
if(coll != null)
coll.suspect(mbr);
}
}
/** An entire site is down; mark all requests that point to that site as unreachable (used by RELAY2) */
public void setSiteUnreachable(short site) {
for(RspCollector coll: requests.values()) {
if(coll != null)
coll.siteUnreachable(site);
}
}
/**
* <tt>Event.VIEW_CHANGE</tt> event received from a layer below.
* <p>
* Mark all responses from members that are not in new_view as
* NOT_RECEIVED.
*
*/
public void receiveView(View new_view) {
// ArrayList copy;
// copy so we don't run into bug #761804 - Bela June 27 2003
// copy=new ArrayList(requests.values()); // removed because ConcurrentHashMap can tolerate concurrent mods (bela May 8 2006)
view=new_view; // move this before the iteration (JGRP-1428)
for(RspCollector coll: requests.values()) {
if(coll != null)
coll.viewChange(new_view);
}
}
/**
* Handles a message coming from a layer below
*
* @return true if the message was consumed, don't pass it further up, else false
*/
public boolean receiveMessage(Message msg) {
// i. If header is not an instance of request correlator header, ignore
//
// ii. Check whether the message was sent by a request correlator with
// the same name (there may be multiple request correlators in the same
// protocol stack...)
Header hdr=(Header)msg.getHeader(this.id);
if(hdr == null)
return false;
if(hdr.corrId != this.id) {
if(log.isTraceEnabled()) {
log.trace(new StringBuilder("id of request correlator header (").append(hdr.corrId).
append(") is different from ours (").append(this.id).append("). Msg not accepted, passed up"));
}
return false;
}
if(hdr instanceof MultiDestinationHeader) {
// If the header contains an exclusion list, and we are part of it, then we discard the
// request (was addressed to other members)
java.util.Collection exclusion_list=((MultiDestinationHeader)hdr).exclusion_list;
if(exclusion_list != null && local_addr != null && exclusion_list.contains(local_addr)) {
if(log.isTraceEnabled()) {
log.trace(new StringBuilder("discarded request from ").append(msg.getSrc()).
append(" as we are in the exclusion list (local_addr=").
append(local_addr).append(", hdr=").append(hdr).append(')'));
}
return true; // don't pass this message further up
}
}
// [Header.REQ]:
// i. If there is no request handler, discard
// ii. Check whether priority: if synchronous and call stack contains
// address that equals local address -> add priority request. Else
// add normal request.
//
// [Header.RSP]:
// Remove the msg request correlator header and notify the associated
// <tt>RspCollector</tt> that a reply has been received
switch(hdr.type) {
case Header.REQ:
if(request_handler == null) {
if(log.isWarnEnabled()) {
log.warn("there is no request handler installed to deliver request !");
}
return true;
}
handleRequest(msg, hdr);
break;
case Header.RSP:
case Header.EXC_RSP:
RspCollector coll=requests.get(hdr.id);
if(coll != null) {
boolean is_exception=hdr.type == Header.EXC_RSP;
Address sender=msg.getSrc();
Object retval;
byte[] buf=msg.getBuffer();
int offset=msg.getOffset(), length=msg.getLength();
try {
retval=marshaller != null? marshaller.objectFromBuffer(buf, offset, length) :
Util.objectFromByteBuffer(buf, offset, length);
}
catch(Exception e) {
log.error("failed unmarshalling buffer into return value", e);
retval=e;
is_exception=true;
}
coll.receiveResponse(retval, sender, is_exception);
}
break;
default:
msg.getHeader(this.id);
if(log.isErrorEnabled()) log.error("header's type is neither REQ nor RSP !");
break;
}
return true; // message was consumed
}
public Address getLocalAddress() {
return local_addr;
}
public void setLocalAddress(Address local_addr) {
this.local_addr=local_addr;
}
// .......................................................................
/**
* Add an association of:<br>
* ID -> <tt>RspCollector</tt>
*/
private void addEntry(long id, RspCollector coll) {
requests.putIfAbsent(id, coll);
}
/**
* Remove the request entry associated with the given ID
*
* @param id the id of the <tt>RequestEntry</tt> to remove
*/
private void removeEntry(long id) {
// changed by bela Feb 28 2003 (bug fix for 690606)
// changed back to use synchronization by bela June 27 2003 (bug fix for #761804),
// we can do this because we now copy for iteration (viewChange() and suspect())
requests.remove(id);
}
/**
* Handle a request msg for this correlator
*
* @param req the request msg
*/
protected void handleRequest(Message req, Header hdr) {
Object retval;
boolean threwException = false;
// i. Get the request correlator header from the msg and pass it to
// the registered handler
//
// ii. If a reply is expected, pack the return value from the request
// handler to a reply msg and send it back. The reply msg has the same
// ID as the request and the name of the sender request correlator
if(log.isTraceEnabled()) {
log.trace(new StringBuilder("calling (").append((request_handler != null? request_handler.getClass().getName() : "null")).
append(") with request ").append(hdr.id));
}
MessageRequest messageRequest = new MessageRequestImpl(req, hdr);
try {
retval=request_handler.handle(messageRequest);
} catch(Throwable t) {
// if(log.isErrorEnabled()) log.error("error invoking method", t);
retval=t;
threwException = true;
}
messageRequest.sendReply(retval, threwException);
}
protected void prepareResponse(Message rsp) {
;
}
// .......................................................................
private class MessageRequestImpl implements MessageRequest {
private final Message request;
private final Header header;
private MessageRequestImpl(Message request, Header header) {
this.request = request;
this.header = header;
}
@Override
public final Message getMessage() {
return request;
}
@Override
public final void sendReply(Object reply, boolean exceptionThrown) {
if(!header.rsp_expected || reply == RequestHandler.DO_NOT_REPLY) // asynchronous call, we don't need to send a response; terminate call here
return;
if(transport == null) {
if(log.isErrorEnabled()) {
log.error("failure sending response; no transport available");
}
return;
}
Object rsp_buf;
// changed (bela Feb 20 2004): catch exception and return exception
try { // retval could be an exception, or a real value
rsp_buf = marshaller != null ? marshaller.objectToBuffer(reply) : Util.objectToByteBuffer(reply);
}
catch(Throwable t) {
try { // this call should succeed (all exceptions are serializable)
rsp_buf=marshaller != null? marshaller.objectToBuffer(t) : Util.objectToByteBuffer(t);
exceptionThrown = true;
}
catch(Throwable tt) {
if(log.isErrorEnabled()) log.error("failed sending rsp: return value (" + reply + ") is not serializable");
return;
}
}
Message response = request.makeReply();
prepareResponse(response);
response.setFlag(Message.OOB);
response.setFlag(Message.DONT_BUNDLE);
if(request.isFlagSet(Message.NO_FC))
response.setFlag(Message.NO_FC);
if(request.isFlagSet(Message.NO_RELIABILITY))
response.setFlag(Message.NO_RELIABILITY);
if(request.isFlagSet(Message.NO_TOTAL_ORDER))
response.setFlag(Message.NO_TOTAL_ORDER);
if(rsp_buf instanceof Buffer)
response.setBuffer((Buffer)rsp_buf);
else if (rsp_buf instanceof byte[])
response.setBuffer((byte[])rsp_buf);
Header responseHeader = new Header(exceptionThrown ? Header.EXC_RSP : Header.RSP, header.id, false,
RequestCorrelator.this.id);
response.putHeader(RequestCorrelator.this.id, responseHeader);
if(log.isTraceEnabled())
log.trace(new StringBuilder("sending rsp for ").append(responseHeader.id).append(" to ")
.append(response.getDest()));
transport.down(new Event(Event.MSG, response));
}
}
/**
* The header for <tt>RequestCorrelator</tt> messages
*/
public static class Header extends org.jgroups.Header {
public static final byte REQ = 0;
public static final byte RSP = 1;
public static final byte EXC_RSP = 2; // exception
/** Type of header: request or reply */
public byte type;
/**
* The id of this request to distinguish among other requests from the same <tt>RequestCorrelator</tt> */
public long id;
/** msg is synchronous if true */
public boolean rsp_expected;
/** The unique ID of the associated <tt>RequestCorrelator</tt> */
public short corrId;
/**
* Used for externalization
*/
public Header() {}
/**
* @param type type of header (<tt>REQ</tt>/<tt>RSP</tt>)
* @param id id of this header relative to ids of other requests
* originating from the same correlator
* @param rsp_expected whether it's a sync or async request
* @param corr_id The ID of the <tt>RequestCorrelator</tt> from which
*/
public Header(byte type, long id, boolean rsp_expected, short corr_id) {
this.type = type;
this.id = id;
this.rsp_expected = rsp_expected;
this.corrId = corr_id;
}
public String toString() {
StringBuilder ret=new StringBuilder();
ret.append("id=" + corrId + ", type=");
switch(type) {
case REQ: ret.append("REQ");
break;
case RSP: ret.append("RSP");
break;
case EXC_RSP: ret.append("EXC_RSP");
break;
default: ret.append("<unknown>");
}
ret.append(", id=" + id);
ret.append(", rsp_expected=" + rsp_expected);
return ret.toString();
}
public void writeTo(DataOutput out) throws Exception {
out.writeByte(type);
Util.writeLong(id, out);
out.writeBoolean(rsp_expected);
out.writeShort(corrId);
}
public void readFrom(DataInput in) throws Exception {
type=in.readByte();
id=Util.readLong(in);
rsp_expected=in.readBoolean();
corrId=in.readShort();
}
public int size() {
return Global.BYTE_SIZE // type
+ Util.size(id) // id
+ Global.BYTE_SIZE // rsp_expected
+ Global.SHORT_SIZE; // corrId
}
}
public static final class MultiDestinationHeader extends Header {
/** Contains a list of members who should not receive the request (others will drop). Ignored if null */
public java.util.Collection<? extends Address> exclusion_list;
public MultiDestinationHeader() {
}
public MultiDestinationHeader(byte type, long id, boolean rsp_expected, short corr_id, Collection<Address> exclusion_list) {
super(type, id, rsp_expected, corr_id);
this.exclusion_list=exclusion_list;
}
public void writeTo(DataOutput out) throws Exception {
super.writeTo(out);
Util.writeAddresses(exclusion_list, out);
}
public void readFrom(DataInput in) throws Exception {
super.readFrom(in);
exclusion_list=Util.readAddresses(in, LinkedList.class);
}
public int size() {
return (int)(super.size() + Util.size(exclusion_list));
}
public String toString() {
String str=super.toString();
if(exclusion_list != null)
str=str+ ", exclusion_list=" + exclusion_list;
return str;
}
}
private static class MyProbeHandler implements DiagnosticsHandler.ProbeHandler {
private final ConcurrentMap<Long,RspCollector> requests;
private MyProbeHandler(ConcurrentMap<Long,RspCollector> requests) {
this.requests=requests;
}
public Map<String, String> handleProbe(String... keys) {
if(requests == null)
return null;
Map<String,String> retval=new HashMap<String,String>();
for(String key: keys) {
if(key.equals("requests")) {
StringBuilder sb=new StringBuilder();
for(Map.Entry<Long,RspCollector> entry: requests.entrySet()) {
sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
}
retval.put("requests", sb.toString());
break;
}
}
return retval;
}
public String[] supportedKeys() {
return new String[]{"requests"};
}
}
}