com.gemstone.gemfire.distributed.internal.DistributionMessage Maven / Gradle / Ivy
/*
* Copyright (c) 2010-2015 Pivotal Software, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you
* may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License. See accompanying
* LICENSE file.
*/
package com.gemstone.gemfire.distributed.internal;
import com.gemstone.gemfire.internal.cache.locks.LockingPolicy;
import com.gemstone.gemfire.internal.cache.EventID;
import com.gemstone.gemfire.internal.i18n.LocalizedStrings;
import java.io.*;
import java.util.*;
import com.gemstone.gemfire.internal.Assert;
import com.gemstone.gemfire.internal.DataSerializableFixedID;
import com.gemstone.gemfire.CancelException;
import com.gemstone.gemfire.InternalGemFireException;
import com.gemstone.gemfire.SystemFailure;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import com.gemstone.gemfire.distributed.internal.deadlock.MessageDependencyMonitor;
import com.gemstone.gemfire.distributed.internal.membership.*;
import com.gemstone.gemfire.internal.sequencelog.MessageLogger;
import com.gemstone.gemfire.internal.shared.Version;
import com.gemstone.gemfire.internal.tcp.Connection;
import com.gemstone.gemfire.internal.tcp.ConnectionTable;
import com.gemstone.gnu.trove.THashSet;
import com.gemstone.gemfire.internal.util.Breadcrumbs;
/**
* A DistributionMessage
carries some piece of
* information to a distribution manager.
*
* Messages that don't have strict ordering requirements should extend
* {@link com.gemstone.gemfire.distributed.internal.PooledDistributionMessage}.
* Messages that must be processed serially in the order they were received
* can extend
* {@link com.gemstone.gemfire.distributed.internal.SerialDistributionMessage}.
* To customize the sequentialness/thread requirements of a message, extend
* DistributionMessage and implement getExecutor().
*
* @author David Whitlock
*
*/
public abstract class DistributionMessage
implements DataSerializableFixedID, Cloneable {
/** Indicates that a distribution message should be sent to all
* other distribution managers. */
public static final InternalDistributedMember ALL_RECIPIENTS = null;
/** Static for zero recipients for the message */
protected static final InternalDistributedMember[] ZERO_RECIPIENTS =
new InternalDistributedMember[0];
// common bitmask flags for different messages, in particular
// PartitionMessages
// other message types can reuse flags other than common ones
// HAS_PROCESSOR_ID, HAS_TX_ID, ENABLE_TIMESTATS
/**
* Keep this compatible with the other GFE layer PROCESSOR_ID flags. In case
* DSFID is not found, code relies on this to determine processorId to send
* back the exception.
*/
protected static final short HAS_PROCESSOR_ID =
ReplyMessage.PROCESSOR_ID_FLAG;
/** Flag set when this message carries a non-default {@link LockingPolicy}. */
protected static final short HAS_LOCK_POLICY = (HAS_PROCESSOR_ID << 1);
/** Flag set when this message carries a transactional context. */
protected static final short HAS_TX_ID = (HAS_LOCK_POLICY << 1);
/** Flag set when this message is a possible duplicate. */
protected static final short POS_DUP = (HAS_TX_ID << 1);
/** Indicate time statistics capturing as part of this message processing */
protected static final short ENABLE_TIMESTATS = (POS_DUP << 1);
/** If message sender has set the processor type to be used explicitly. */
protected static final short HAS_PROCESSOR_TYPE = (ENABLE_TIMESTATS << 1);
///**
// * Flag set when this message carries a transactional member in context.
// * Note this is only used by old TX implementation while new distributed
// * TX implementation uses the bitmask for {@link #HAS_LOCK_POLICY}.
// */
//private static final short HAS_TX_MEMBERID = 0x2;
/** the unreserved flags start for child classes */
protected static final short UNRESERVED_FLAGS_START =
(HAS_PROCESSOR_TYPE << 1);
//////////////////// Instance Fields ////////////////////
/** The sender of this message */
protected transient InternalDistributedMember sender;
/** A set of recipients for this message, not serialized*/
private transient InternalDistributedMember[] recipients = null;
/** every AbstractOperationMessage tracking whether time statistics enabled or not. */
protected transient boolean timeStatsEnabled;
/** A timestamp, in nanos, associated with this message. Not serialized. */
protected transient long timeStamp;
/** The number of bytes used to read this message, for statistics only */
private transient int bytesRead = 0;
/** true if message should be multicast; ignores recipients */
private transient boolean multicast = false;
/**
* non-null if messageBeingReceived stats need decrementing when done with msg
*/
private transient Connection messageReceiver;
/**
* True if this message has incremented (or will increment) the
* messagesReceived count on the Connection set in {@link #messageReceiver},
* and false if the caller should do that. Normally the counter is incremented
* by the message itself if the message is not processed inline.
*/
private transient boolean handleMessageReceived;
/**
* This field will be set if we can send a direct ack for
* this message.
*/
protected transient ReplySender acker = null;
////////////////////// Constructors //////////////////////
protected DistributionMessage() {
this.timeStatsEnabled = DistributionStats.enableClockStats;
if(this.timeStatsEnabled) {
this.timeStamp = DistributionStats.getStatTimeNoCheck();
}
}
////////////////////// Static Helper Methods //////////////////////
/**
* Get the next bit mask position while checking that the value should not
* exceed maximum byte value.
*/
protected static int getNextByteMask(final int mask) {
return getNextBitMask(mask, (Byte.MAX_VALUE) + 1);
}
/**
* Get the next bit mask position while checking that the value should not
* exceed given maximum value.
*/
protected static final int getNextBitMask(int mask, final int maxValue) {
mask <<= 1;
if (mask > maxValue) {
Assert.fail("exhausted bit flags with all available bits: 0x"
+ Integer.toHexString(mask) + ", max: 0x"
+ Integer.toHexString(maxValue));
}
return mask;
}
public static final byte getNumBits(final int maxValue) {
byte numBits = 1;
while ((1 << numBits) <= maxValue) {
numBits++;
}
return numBits;
}
////////////////////// Instance Methods //////////////////////
public final void setHandleReceiverStats(Connection conn) {
this.messageReceiver = conn;
}
public final boolean handleMessageReceived() {
return this.handleMessageReceived;
}
public final void setReplySender(ReplySender acker) {
this.acker = acker;
}
public final ReplySender getReplySender(DM dm) {
if(acker != null) {
return acker;
} else {
return dm;
}
}
public final boolean isDirectAck() {
return acker != null;
}
/**
* If true then this message most be sent on an ordered channel.
* If false then it can be unordered.
* @since 5.5
*/
public boolean orderedDelivery(boolean threadOwnsResources) {
final int processorType = getProcessorType();
switch (processorType) {
case DistributionManager.SERIAL_EXECUTOR:
case DistributionManager.PARTITIONED_REGION_EXECUTOR:
return true;
// need to use ordered connections for StateFlush when
// constainsRegionContentChange is true
case DistributionManager.REGION_FUNCTION_EXECUTION_EXECUTOR:
// allow nested distributed functions to be executed from within the
// execution of a function; this is required particularly for GemFireXD
// TODO: this can later be adjusted to use a separate property
return containsRegionContentChange();
default:
return threadOwnsResources || containsRegionContentChange();
}
}
/**
* Sets the intended recipient of the message. If recipient is
* {@link #ALL_RECIPIENTS} then the message will be sent to all
* distribution managers.
*/
public void setRecipient(InternalDistributedMember recipient) {
if (this.recipients != null) {
throw new IllegalStateException(LocalizedStrings.DistributionMessage_RECIPIENTS_CAN_ONLY_BE_SET_ONCE.toLocalizedString());
}
this.recipients = new InternalDistributedMember[] {recipient};
}
/**
* Causes this message to be send using multicast if v is true.
* @since 5.0
*/
public void setMulticast(boolean v) {
this.multicast = v;
}
/**
* Return true if this message should be sent using multicast.
* @since 5.0
*/
public boolean getMulticast() {
return this.multicast;
}
/**
* Return true of this message should be sent through JGroups instead of the
* direct-channel. This is typically only done for messages that are
* broadcast to the full membership set.
*/
public boolean sendViaJGroups() {
return false;
}
/**
* Sets the intended recipient of the message. If recipient set contains
* {@link #ALL_RECIPIENTS} then the message will be sent to all
* distribution managers.
*/
public void setRecipients(Collection recipients) {
if (this.recipients != null) {
throw new IllegalStateException(LocalizedStrings.DistributionMessage_RECIPIENTS_CAN_ONLY_BE_SET_ONCE.toLocalizedString());
}
if (recipients.size() > 0) {
this.recipients = (InternalDistributedMember[])recipients
.toArray(new InternalDistributedMember[recipients.size()]);
}
else {
this.recipients = ZERO_RECIPIENTS;
}
}
public void resetRecipients() {
this.recipients = null;
this.multicast = false;
}
public Set getSuccessfulRecipients() {
// note we can't use getRecipients() for plannedRecipients because it will
// return ALL_RECIPIENTS if multicast
// [sumedh] Changed to use THashSet below. Below does not look to be
// returning successful recipients only. Method should be renamed to
// getPlannedRecipients()?
InternalDistributedMember[] plannedRecipients = this.recipients;
if (plannedRecipients.length > 0) {
THashSet successfulRecipients = new THashSet(plannedRecipients.length);
for (int index = 0; index < plannedRecipients.length; index++) {
successfulRecipients.add(plannedRecipients[index]);
}
return successfulRecipients;
}
else {
return Collections.emptySet();
}
}
/**
* Returns the intended recipient(s) of this message. If the message
* is intended to delivered to all distribution managers, then
* the array will contain ALL_RECIPIENTS.
* If the recipients have not been set null is returned.
*/
public InternalDistributedMember[] getRecipients() {
if (this.multicast) {
return new InternalDistributedMember[] {ALL_RECIPIENTS};
}else if (this.recipients != null) {
return this.recipients;
} else {
return new InternalDistributedMember[] {ALL_RECIPIENTS};
}
}
/**
* Returns true if message will be sent to everyone.
*/
public boolean forAll() {
return (this.recipients == null)
|| (this.multicast)
|| ((this.recipients.length > 0)
&& (this.recipients[0] == ALL_RECIPIENTS));
}
public String getRecipientsDescription() {
if (this.recipients == null) {
return "";
}
else if (this.multicast) {
return "";
} else if (this.recipients.length > 0 && this.recipients[0] == ALL_RECIPIENTS) {
return "";
} else {
StringBuilder sb = new StringBuilder(100);
sb.append("");
return sb.toString();
}
}
/**
* Returns the sender of this message. Note that this value is not
* set until this message is received by a distribution manager.
*/
public InternalDistributedMember getSender() {
return this.sender;
}
/**
* Sets the sender of this message. This method is only invoked
* when the message is received by a
* DistributionManager
.
*/
public void setSender(InternalDistributedMember _sender) {
this.sender = _sender;
}
/**
* Return the Executor in which to process this message.
*/
protected Executor getExecutor(DistributionManager dm) {
return dm.getExecutor(getProcessorType(), sender);
}
// private Executor getExecutor(DistributionManager dm, Class clazz) {
// return dm.getExecutor(getProcessorType());
// }
public abstract int getProcessorType();
public void setProcessorType(boolean isReaderThread) {
}
/**
* Processes this message. This method is invoked by the receiver
* of the message.
* @param dm the distribution manager that is processing the message.
*/
protected abstract void process(DistributionManager dm);
/**
* Scheduled action to take when on this message when we are ready
* to process it.
*/
protected final void scheduleAction(final DistributionManager dm) {
if (DistributionManager.VERBOSE || dm.logger.fineEnabled()) {
dm.getLoggerI18n().info(
LocalizedStrings.DEBUG,
"Processing {" + this + "}");
}
String reason = dm.getCancelCriterion().cancelInProgress();
if (reason != null) {
// throw new ShutdownException(reason);
if (DistributionManager.VERBOSE || dm.logger.fineEnabled()) {
dm.logger.fine("scheduleAction: cancel in progress (" + reason
+ "); skipping <" + this + ">");
}
return;
}
if(MessageLogger.isEnabled()) {
MessageLogger.logMessage(this, getSender(), dm.getDistributionManagerId());
}
MessageDependencyMonitor.processingMessage(this);
long time = 0;
if (this.timeStatsEnabled) {
time = DistributionStats.getStatTimeNoCheck();
dm.getStats().incMessageProcessingScheduleTime(time-getTimestamp());
}
setBreadcrumbsInReceiver();
try {
DistributionMessageObserver observer = DistributionMessageObserver.getInstance();
if(observer != null) {
observer.beforeProcessMessage(dm, this);
}
process(dm);
if(observer != null) {
observer.afterProcessMessage(dm, this);
}
}
catch (CancelException e) {
if (dm.logger.fineEnabled()) {
dm.logger.fine("Cancelled caught processing "
+ this + ": "
+ e.getMessage());
}
}
catch (Throwable t) {
Error err;
if (t instanceof Error && SystemFailure.isJVMFailureError(
err = (Error)t)) {
SystemFailure.initiateFailure(err);
// If this ever returns, rethrow the error. We're poisoned
// now, so don't let this thread continue.
throw err;
}
// Whenever you catch Error or Throwable, you must also
// check for fatal JVM error (see above). However, there is
// _still_ a possibility that you are dealing with a cascading
// error condition, so you also need to check to see if the JVM
// is still usable:
SystemFailure.checkFailure();
dm.logger.severe(LocalizedStrings.DistributionMessage_UNCAUGHT_EXCEPTION_PROCESSING__0, this, t);
}
finally {
if (this.messageReceiver != null) {
dm.getStats().decMessagesBeingReceived(this.bytesRead);
}
dm.getStats().incProcessedMessages(1L);
if (this.timeStatsEnabled) {
dm.getStats().incProcessedMessagesTime(time);
}
Breadcrumbs.clearBreadcrumb();
MessageDependencyMonitor.doneProcessing(this);
}
}
/**
* Schedule this message's process() method in a thread determined
* by getExecutor()
*/
public final void schedule(final DistributionManager dm) {
boolean inlineProcess = DistributionManager.INLINE_PROCESS
&& getProcessorType() == DistributionManager.SERIAL_EXECUTOR
&& !isPreciousThread();
inlineProcess |= this.getInlineProcess();
inlineProcess |= ConnectionTable.isDominoThread();
inlineProcess |= this.acker != null;
if (inlineProcess) {
dm.getStats().incNumSerialThreads(1);
try {
scheduleAction(dm);
} finally {
dm.getStats().incNumSerialThreads(-1);
}
} else { // not inline
final Connection receiver = this.messageReceiver;
this.handleMessageReceived = (receiver != null);
try {
// if (dm.logger.fineEnabled()) {
// dm.logger.fine("adding message to executor " + getProcessorType());
// }
getExecutor(dm).execute(new SizeableRunnable(this.getBytesRead()) {
public void run() {
try {
scheduleAction(dm);
} finally {
if (receiver != null && containsRegionContentChange()) {
receiver.incMessagesReceived();
}
}
}
@Override
public String toString() {
return "Processing {" + DistributionMessage.this.toString() + "}";
}
});
}
catch (RejectedExecutionException ex) {
if (receiver != null && containsRegionContentChange()) {
receiver.incMessagesReceived();
}
if (!dm.shutdownInProgress()) { // fix for bug 32395
dm.logger.warning(LocalizedStrings.DistributionMessage_0__SCHEDULE_REJECTED, this.toString(), ex);
}
}
catch (Throwable t) {
Error err;
if (t instanceof Error && SystemFailure.isJVMFailureError(
err = (Error)t)) {
SystemFailure.initiateFailure(err);
// If this ever returns, rethrow the error. We're poisoned
// now, so don't let this thread continue.
throw err;
}
// Whenever you catch Error or Throwable, you must also
// check for fatal JVM error (see above). However, there is
// _still_ a possibility that you are dealing with a cascading
// error condition, so you also need to check to see if the JVM
// is still usable:
SystemFailure.checkFailure();
dm.logger.severe(LocalizedStrings.DistributionMessage_UNCAUGHT_EXCEPTION_PROCESSING__0, this, t);
// I don't believe this ever happens (DJP May 2007)
throw new InternalGemFireException(LocalizedStrings.DistributionMessage_UNEXPECTED_ERROR_SCHEDULING_MESSAGE.toLocalizedString(), t);
}
} // not inline
}
/**
* returns true if the current thread should not be used for inline
* processing. i.e., it is a "precious" resource
*/
public static boolean isPreciousThread() {
String thrname = Thread.currentThread().getName();
return thrname.startsWith("UDP");
}
/** most messages should not force in-line processing */
public boolean getInlineProcess() {
return false;
}
/**
* sets the breadcrumbs for this message into the current thread's name
*/
public void setBreadcrumbsInReceiver() {
if (Breadcrumbs.ENABLED) {
String sender = null;
String procId = "";
long pid = getProcessorId();
if (pid != 0) {
procId = " processorId=" + pid;
}
if (Thread.currentThread().getName().startsWith("P2P Message Reader")) {
sender = procId;
}
else {
sender = "sender=" + getSender() + procId;
}
if (sender.length() > 0) {
Breadcrumbs.setReceiveSide(sender);
}
Object evID = getEventID();
if (evID != null) {
Breadcrumbs.setEventId(evID);
}
}
}
/**
* sets breadcrumbs in a thread that is sending a message to another member
*/
public void setBreadcrumbsInSender() {
if (Breadcrumbs.ENABLED) {
String procId = "";
long pid = getProcessorId();
if (pid != 0) {
procId = "processorId=" + pid;
}
if (this.recipients != null && this.recipients.length <= 10) { // set a limit on recipients
Breadcrumbs.setSendSide(procId
+ " recipients="+Arrays.toString(this.recipients));
}
else {
if (procId.length() > 0) {
Breadcrumbs.setSendSide(procId);
}
}
Object evID = getEventID();
if (evID != null) {
Breadcrumbs.setEventId(evID);
}
}
}
public EventID getEventID() {
return null;
}
/**
* This method resets the state of this message, usually releasing
* objects and resources it was using. It is invoked after the
* message has been sent. Note that classes that override this
* method should always invoke the inherited method
* (super.reset()
).
*/
public void reset() {
resetRecipients();
this.sender = null;
}
/**
* Writes the contents of this DistributionMessage
to
* the given output.
* Note that classes that
* override this method should always invoke the inherited method
* (super.toData()
).
*/
public void toData(DataOutput out) throws IOException {
// DataSerializer.writeObject(this.recipients, out); // no need to serialize; filled in later
//((IpAddress)this.sender).toData(out); // no need to serialize; filled in later
//out.writeLong(this.timeStamp);
}
/**
* Reads the contents of this DistributionMessage
from
* the given input.
* Note that classes that override this
* method should always invoke the inherited method
* (super.fromData()
).
*/
public void fromData(DataInput in)
throws IOException, ClassNotFoundException {
// this.recipients = (Set)DataSerializer.readObject(in); // no to deserialize; filled in later
// this.sender = DataSerializer.readIpAddress(in); // no to deserialize; filled in later
// this.timeStamp = (long)in.readLong();
}
/**
* Returns a timestamp, in nanos, associated with this message.
*/
public long getTimestamp() {
return timeStamp;
}
/**
* Sets the timestamp of this message to the current time (in nanos).
* @return the number of elapsed nanos since this message's last timestamp
*/
public long resetTimestamp() {
if (this.timeStatsEnabled) {
long now = DistributionStats.getStatTimeNoCheck();
long result = now - this.timeStamp;
this.timeStamp = now;
return result;
} else {
return 0;
}
}
public void setBytesRead(int bytesRead)
{
this.bytesRead = bytesRead;
}
public int getBytesRead()
{
return bytesRead;
}
/**
*
* @return null if message is not conflatable. Otherwise return
* a key that can be used to identify the entry to conflate.
* @since 4.2.2
*/
public ConflationKey getConflationKey() {
return null; // by default conflate nothing; override in subclasses
}
/**
* @return the ID of the reply processor for this message, or zero if none
* @since 5.7
*/
public int getProcessorId() {
return 0;
}
/**
* Severe alert processing enables suspect processing at the ack-wait-threshold
* and issuing of a severe alert at the end of the ack-severe-alert-threshold.
* Some messages should not support this type of processing
* (e.g., GII, or DLockRequests)
* @return whether severe-alert processing may be performed on behalf
* of this message
*/
public boolean isSevereAlertCompatible() {
return false;
}
/**
* Returns true if the message is for internal-use such as a meta-data region.
*
* @return true if the message is for internal-use such as a meta-data region
* @since 7.0
*/
public boolean isInternal() {
return false;
}
/**
* does this message carry state that will alter the content of
* one or more cache regions? This is used to track the
* flight of content changes through communication channels
*/
public boolean containsRegionContentChange() {
return false;
}
/** returns the class name w/o package information. useful in logging */
public String getShortClassName() {
final Class> c = getClass();
final String cname = c.getName();
return cname.substring(c.getPackage().getName().length() + 1);
}
@Override
public String toString() {
String cname = getClass().getName().substring(
getClass().getPackage().getName().length() + 1);
final StringBuilder sb = new StringBuilder(cname);
sb.append('@').append(Integer.toHexString(System.identityHashCode(this)));
sb.append(" processorId=").append(getProcessorId());
sb.append(" sender=").append(getSender());
return sb.toString();
}
public Version[] getSerializationVersions() {
return null;
}
}