com.gemstone.gemfire.internal.tcp.MsgDestreamer 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.internal.tcp;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import com.gemstone.gemfire.CancelCriterion;
import com.gemstone.gemfire.InternalGemFireError;
import com.gemstone.gemfire.SystemFailure;
import com.gemstone.gemfire.distributed.DistributedSystemDisconnectedException;
import com.gemstone.gemfire.distributed.internal.DMStats;
import com.gemstone.gemfire.distributed.internal.DistributionMessage;
import com.gemstone.gemfire.distributed.internal.InternalDistributedSystem;
import com.gemstone.gemfire.distributed.internal.ReplyProcessor21;
import com.gemstone.gemfire.i18n.LogWriterI18n;
import com.gemstone.gemfire.internal.InternalDataSerializer;
import com.gemstone.gemfire.internal.VersionedDataInputStream;
import com.gemstone.gemfire.internal.i18n.LocalizedStrings;
import com.gemstone.gemfire.internal.shared.Version;
/** MsgDestreamer supports destreaming a streamed message from a tcp Connection
* that arrives in chunks.
* This allows us to receive a message without needing to
* read it completely into a buffer before we can start deserializing it.
@author Darrel
@since 5.0.2
*/
public class MsgDestreamer {
/**
* If an exception occurs during deserialization of the message it will be
* recorded here.
*/
private Throwable failure;
/**
* Used to store the deserialized message on success.
*/
private DistributionMessage result;
/**
* The current failed messages reply processor id if it has one
*/
private int RPid;
/**
* The thread that will be doing the deserialization of the message.
*/
private final DestreamerThread t;
private int size;
final CancelCriterion stopper;
final Version version;
public MsgDestreamer(DMStats stats, CancelCriterion stopper, Version v) {
this.stopper = stopper;
this.t = new DestreamerThread(stats, stopper); // @todo darrel: add group support
this.version = v;
init();
}
private void init() {
this.t.start();
}
public void close() {
reset();
this.t.close();
}
public void reset() {
synchronized (this) {
this.failure = null;
this.result = null;
}
this.size = 0;
this.t.setName("IDLE p2pDestreamer");
}
public void setName(String name) {
this.t.setName("p2pDestreamer for " + name);
}
private void waitUntilDone() throws InterruptedException {
if (this.t.isClosed() || Thread.interrupted()) throw new InterruptedException();
synchronized (this) {
while (this.failure == null && this.result == null) {
if (this.t.isClosed() || Thread.interrupted()) throw new InterruptedException();
this.wait(); // spurious wakeup ok
}
}
}
// private final String me = "MsgDestreamer<" + System.identityHashCode(this) + ">";
// public String toString() {
// return this.me;
// }
// private void logit(String s) {
// LogWriterI18n l = getLogger();
// if (l != null) {
// l.fine(this + ": " + s);
// }
// }
// private LogWriterI18n getLogger() {
// LogWriterI18n result = null;
// DistributedSystem ds = InternalDistributedSystem.getAnyInstance();
// if (ds != null) {
// result = ds.getLogWriter();
// }
// return result;
// }
/**
* Adds a chunk for this guy to deserialize
* @param bb contains the bytes of the chunk
* @param length the number of bytes in bb that are this chunk
*/
public void addChunk(ByteBuffer bb, int length) throws IOException {
// if this destreamer has failed or this chunk is empty just return
if (this.failure == null && length > 0) {
// logit("addChunk bb length=" + length);
this.t.addChunk(bb, length);
this.size += length;
}
}
/**
* Adds a chunk for this guy to deserialize
* @param b a byte array contains the bytes of the chunk
*/
public void addChunk(byte[] b) throws IOException {
// if this destreamer has failed or this chunk is empty just return
if (this.failure == null && b != null && b.length > 0) {
// logit("addChunk length=" + b.length);
ByteBuffer bb = ByteBuffer.wrap(b);
this.t.addChunk(bb, b.length);
this.size += b.length;
}
}
/**
* Returns the number of bytes added to this destreamer.
*/
public int size() {
return this.size;
}
/**
* Waits for the deserialization to complete and returns the deserialized message.
* @throws IOException
* A problem occured while deserializing the message.
* @throws ClassNotFoundException
* The class of an object read from in
could
* not be found
*/
public DistributionMessage getMessage()
throws InterruptedException, IOException, ClassNotFoundException
{
// if (Thread.interrupted()) throw new InterruptedException(); not necessary done in waitUntilDone
//this.t.join();
waitUntilDone();
if (this.failure != null) {
// logit("failed with" + this.failure);
if (this.failure instanceof ClassNotFoundException) {
throw (ClassNotFoundException)this.failure;
} else if (this.failure instanceof IOException) {
throw (IOException)this.failure;
} else {
IOException io = new IOException(LocalizedStrings.MsgDestreamer_FAILURE_DURING_MESSAGE_DESERIALIZATION.toLocalizedString());
io.initCause(this.failure);
throw io;
}
} else {
// logit("result =" + this.result);
return this.result;
}
}
/**
* Returns the reply processor id for the current failed message.
* Returns 0 if it does not have one.
* Note this method should only be called after getMessage has thrown an exception.
*/
public int getRPid() {
return this.RPid;
}
protected void setFailure(Throwable ex, int RPid) {
synchronized (this) {
this.failure = ex;
this.RPid = RPid;
this.notify();
}
}
protected void setResult(DistributionMessage msg) {
synchronized (this) {
this.result = msg;
this.RPid = 0;
this.notify();
}
}
/**
* Thread used to deserialize chunks into a message.
*/
private class DestreamerThread extends Thread {
private volatile boolean closed = false;
final DestreamerIS is;
final DMStats stats;
public DestreamerThread(DMStats stats, CancelCriterion stopper) {
setDaemon(true);
super.setName("IDLE p2pDestreamer");
this.is = new DestreamerIS(this, stopper);
this.stats = stats;
}
// private final String me = "DestreamerThread<" + System.identityHashCode(this) + ">";
// public String toString() {
// return this.me;
// }
public void addChunk(ByteBuffer chunk, int bbLength) throws IOException {
ByteBuffer bb = chunk.slice();
bb.limit(bbLength);
this.is.addChunk(bb);
}
@Override
public void run() {
for (;;) {
if (isClosed()) {
return;
}
try {
ReplyProcessor21.initMessageRPId();
final Version v = version;
DataInputStream dis = v == null ? new DataInputStream(this.is)
: new VersionedDataInputStream(this.is, v);
long startSer = this.stats.startMsgDeserialization();
setResult((DistributionMessage)InternalDataSerializer.readDSFID(dis));
this.stats.endMsgDeserialization(startSer);
}
catch (Throwable ex) {
Error err;
if (ex instanceof Error && SystemFailure.isJVMFailureError(
err = (Error)ex)) {
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();
setFailure(ex, ReplyProcessor21.getMessageRPId());
} finally {
this.is.close();
ReplyProcessor21.clearMessageRPId();
}
}
}
public void close() {
this.closed = true;
// TODO nothing in the run method DIRECTLY throws an interrupt,
// though it's possible underlying methods might catch and suitable
// exit early...
interrupt();
}
public boolean isClosed() {
return this.closed;
}
}
/**
* This input stream waits for data to be available.
* Once it is provided, by a call to addChunk, it will stream the
* data in from that chunk, signal that is has completed, and then wait for
* another chunk.
*/
private static class DestreamerIS extends InputStream {
final Object dataMon = new Object();
final Object doneMon = new Object();
ByteBuffer data;
final DestreamerThread owner;
final CancelCriterion stopper;
private class Stopper extends CancelCriterion {
private final CancelCriterion stopper;
Stopper(CancelCriterion stopper) {
this.stopper = stopper;
}
/* (non-Javadoc)
* @see com.gemstone.gemfire.CancelCriterion#cancelInProgress()
*/
@Override
public String cancelInProgress() {
String reason = stopper.cancelInProgress();
if (reason != null) {
return reason;
}
if (owner.isClosed()) {
return "owner is closed";
}
return null;
}
/* (non-Javadoc)
* @see com.gemstone.gemfire.CancelCriterion#generateCancelledException(java.lang.Throwable)
*/
@Override
public RuntimeException generateCancelledException(Throwable e) {
String reason = cancelInProgress();
if (reason == null) {
return null;
}
RuntimeException result = stopper.generateCancelledException(e);
if (result != null) {
return result;
}
return new DistributedSystemDisconnectedException("owner is closed");
}
}
public DestreamerIS(DestreamerThread t, CancelCriterion stopper) {
this.owner = t;
this.data = null;
this.stopper = new Stopper(stopper);
}
// public String toString() {
// return this.owner.me;
// }
// private void logit(String s) {
// LogWriterI18n l = getLogger();
// if (l != null) {
// l.fine(this + ": " + s);
// }
// }
// private LogWriterI18n getLogger() {
// LogWriterI18n result = null;
// DistributedSystem ds = InternalDistributedSystem.getAnyInstance();
// if (ds != null) {
// result = ds.getLogWriter();
// }
// return result;
// }
private boolean isClosed() {
return this.owner.isClosed();
}
private ByteBuffer waitForData() throws InterruptedException {
if (isClosed() || Thread.interrupted()) throw new InterruptedException();
synchronized (this.dataMon) {
ByteBuffer result = this.data;
while (result == null) {
if (isClosed() || Thread.interrupted()) throw new InterruptedException();
//logit("about to dataMon wait");
this.dataMon.wait(); // spurious wakeup ok
//logit("after dataMon wait");
if (isClosed() || Thread.interrupted()) throw new InterruptedException();
result = this.data;
}
return result;
}
}
private void provideData(ByteBuffer bb) {
synchronized (this.dataMon) {
// if (bb != null) {
// logit("MDIS: providing bb with " +
// bb.remaining() + " bytes");
// }
this.data = bb;
//logit("dataMon notify bb=" + bb);
this.dataMon.notify();
}
}
private void waitUntilDone() throws InterruptedException {
if (isClosed() || Thread.interrupted()) throw new InterruptedException();
synchronized (this.doneMon) {
while (this.data != null) {
if (isClosed() || Thread.interrupted()) throw new InterruptedException();
//logit("about to doneMon wait");
this.doneMon.wait(); // spurious wakeup ok
//logit("after doneMon wait");
if (isClosed() || Thread.interrupted()) throw new InterruptedException();
}
}
}
private void signalDone() {
synchronized (this.doneMon) {
this.data = null;
//logit("doneMon notify");
this.doneMon.notify();
}
}
public void addChunk(ByteBuffer bb) throws IOException {
provideData(bb);
for (;;) {
stopper.checkCancelInProgress(null);
boolean interrupted = Thread.interrupted();
try {
waitUntilDone();
break;
}
catch (InterruptedException e) {
interrupted = true;
}
finally {
if (interrupted) {
Thread.currentThread().interrupt();
}
}
}
}
private ByteBuffer waitForAvailableData() throws IOException {
boolean available = false;
ByteBuffer myData;
do {
// only the thread that sets data to null ever does this check
// so I believe it is ok to do this check outside of sync.
// @todo darrel: mitch questions this logic
myData = this.data;
if (myData == null) {
for (;;) {
if (isClosed()) {
throw new IOException("owner closed"); // TODO
}
stopper.checkCancelInProgress(null);
boolean interrupted = Thread.interrupted();
try {
myData = waitForData();
break;
}
catch (InterruptedException e) {
interrupted = true;
}
finally {
if (interrupted) {
Thread.currentThread().interrupt();
}
}
}
if (myData == null) {
// someone must have called close so tell our caller
// that we were interrupted. This fixes bug 37230.
stopper.checkCancelInProgress(null);
throw new InternalGemFireError("bug 37230, please report to support");
}
// logit("received new bb with " +
// myData.remaining() + " bytes");
}
int remaining = myData.remaining();
if (remaining <= 0) {
signalDone();
} else {
available = true;
}
} while (!available);
return myData;
}
@Override
public final void close() {
signalDone();
}
/**
* See the InputStream read method for javadocs.
* Note that if an attempt
* to read past the end of the wrapped ByteBuffer is done this method
* throws BufferUnderflowException
*/
@Override
public final int read() throws IOException {
ByteBuffer bb = waitForAvailableData();
// logit("read result=" + result);
return (bb.get() & 0xff);
}
/* this method is not thread safe
* See the InputStream read method for javadocs.
* Note that if an attempt
* to read past the end of the wrapped ByteBuffer is done this method
* throws BufferUnderflowException
*/
@Override
public final int read(byte b[], int off, int len) throws IOException {
ByteBuffer bb = waitForAvailableData();
int remaining = bb.remaining();
int bytesToRead = len;
if (remaining < len) {
bytesToRead = remaining;
}
bb.get(b, off, bytesToRead);
// logit("read[] read=" + bytesToRead);
return bytesToRead;
}
@Override
public int available() throws IOException {
ByteBuffer bb = this.data;
if (bb == null) {
return 0;
} else {
return bb.remaining();
}
}
}
private static LogWriterI18n getLogger() {
LogWriterI18n result = null;
InternalDistributedSystem ids = InternalDistributedSystem.unsafeGetConnectedInstance();
if (ids != null) {
result = ids.getLogWriter().convertToLogWriterI18n();
}
return result;
}
}