org.jboss.remoting3.remote.InboundMessage Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including
all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and
JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
/*
* JBoss, Home of Professional Open Source.
* Copyright 2017 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* 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.
*/
package org.jboss.remoting3.remote;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.function.ToIntFunction;
import org.jboss.remoting3.MessageCancelledException;
import org.jboss.remoting3.MessageInputStream;
import org.xnio.Pooled;
import org.xnio.streams.BufferPipeInputStream;
import static org.jboss.remoting3._private.Messages.conn;
import static org.jboss.remoting3._private.Messages.log;
import static java.lang.Thread.holdsLock;
import static org.xnio.IoUtils.safeClose;
/**
* @author David M. Lloyd
*/
final class InboundMessage {
final short messageId;
final RemoteConnectionChannel channel;
int inboundWindow;
boolean streamClosed;
boolean closeSent;
boolean eofReceived;
boolean cancelled;
long remaining;
static final ToIntFunction INDEXER = InboundMessage::getActualId;
InboundMessage(final short messageId, final RemoteConnectionChannel channel, int inboundWindow, final long maxInboundMessageSize) {
this.messageId = messageId;
this.channel = channel;
this.inboundWindow = inboundWindow;
remaining = maxInboundMessageSize;
}
final BufferPipeInputStream inputStream = new BufferPipeInputStream(new BufferPipeInputStream.InputHandler() {
public void acknowledge(final Pooled acked) throws IOException {
doAcknowledge(acked);
}
public void close() throws IOException {
doClose();
}
});
private int getActualId() {
return messageId & 0xffff;
}
void terminate() {
synchronized (inputStream) {
safeClose(inputStream);
}
}
private void doClose() {
assert holdsLock(inputStream);
if (streamClosed) {
// idempotent
return;
}
streamClosed = true;
// on close, send close message
doSendCloseMessage();
// but keep the mapping around until we receive our EOF
// else just keep discarding data until the EOF comes in.
}
private void doSendCloseMessage() {
assert holdsLock(inputStream);
if (closeSent || ! channel.getConnectionHandler().isMessageClose()) {
// we don't send a MESSAGE_CLOSE because broken versions will simply stop sending packets, and we won't know when the message is really gone.
// the risk is that the remote side could have started a new message in the meantime, and our MESSAGE_CLOSE would kill the wrong message.
// so this behavior is better than the alternative.
return;
}
Pooled pooled = allocate(Protocol.MESSAGE_CLOSE);
boolean ok = false;
try {
ByteBuffer buffer = pooled.getResource();
buffer.flip();
channel.getRemoteConnection().send(pooled);
ok = true;
closeSent = true;
} finally {
if (! ok) pooled.free();
}
}
private void doAcknowledge(final Pooled acked) {
assert holdsLock(inputStream);
if (eofReceived) {
// no ack needed; also a best-effort to work around broken peers
return;
}
final boolean badMsgSize = channel.getConnectionHandler().isFaultyMessageSize();
int consumed = acked.getResource().position();
if (! badMsgSize) consumed -= 8; // position minus header length (not including framing size)
inboundWindow += consumed;
Pooled pooled = allocate(Protocol.MESSAGE_WINDOW_OPEN);
boolean ok = false;
try {
ByteBuffer buffer = pooled.getResource();
buffer.putInt(consumed); // Open window by buffer size
buffer.flip();
channel.getRemoteConnection().send(pooled);
ok = true;
} finally {
if (! ok) pooled.free();
}
}
final MessageInputStream messageInputStream = new MessageInputStream() {
public int read() throws IOException {
synchronized (inputStream) {
if (cancelled) {
throw new MessageCancelledException();
}
return inputStream.read();
}
}
public int read(final byte[] bytes, final int offs, final int length) throws IOException {
synchronized (inputStream) {
if (cancelled) {
throw new MessageCancelledException();
}
return inputStream.read(bytes, offs, length);
}
}
public long skip(final long l) throws IOException {
synchronized (inputStream) {
if (cancelled) {
throw new MessageCancelledException();
}
return inputStream.skip(l);
}
}
public int available() throws IOException {
synchronized (inputStream) {
if (cancelled) {
throw new MessageCancelledException();
}
return inputStream.available();
}
}
public void close() throws IOException {
synchronized (inputStream) {
if (! streamClosed) {
inputStream.close();
if (cancelled) {
throw new MessageCancelledException();
}
}
}
}
};
Pooled allocate(byte protoId) {
Pooled pooled = channel.allocate(protoId);
ByteBuffer buffer = pooled.getResource();
buffer.putShort(messageId);
return pooled;
}
void handleIncoming(Pooled pooledBuffer) {
boolean eof;
boolean free = true;
try {
synchronized (inputStream) {
ByteBuffer buffer = pooledBuffer.getResource();
final int bufRemaining = buffer.remaining();
if ((inboundWindow -= bufRemaining) < 0) {
channel.getRemoteConnection().handleException(new IOException("Input overrun"));
return;
}
if (log.isTraceEnabled()) {
log.tracef("Received message (chan %08x msg %04x) (%d-%d=%d remaining)", Integer.valueOf(channel.getChannelId()), Short.valueOf(messageId), Integer.valueOf(inboundWindow + bufRemaining), Integer.valueOf(bufRemaining), Integer.valueOf(inboundWindow));
}
buffer.position(buffer.position() - 1);
byte flags = buffer.get();
eof = (flags & Protocol.MSG_FLAG_EOF) != 0;
boolean cancelled = (flags & Protocol.MSG_FLAG_CANCELLED) != 0;
if (bufRemaining > remaining) {
cancelled = true;
doClose();
}
if (cancelled) {
this.cancelled = true;
// make sure it goes through
inputStream.pushException(new MessageCancelledException());
}
if (streamClosed) {
// ignore, but keep the bits flowing
if (! eof && ! closeSent) {
// we don't need to acknowledge if it's EOF or if we sent a close msg since no more data is coming anyway
buffer.position(buffer.limit()); // "consume" everything
doAcknowledge(pooledBuffer);
}
} else if (! cancelled) {
remaining -= bufRemaining;
free = false;
inputStream.push(pooledBuffer);
}
if (eof) {
eofReceived = true;
if (!streamClosed) {
inputStream.pushEof();
}
channel.freeInboundMessage(messageId);
// if the peer is old, they might reuse the ID now regardless of us; if new, we have to send the close message to acknowledge the remainder
doSendCloseMessage();
}
}
} finally {
if (free) pooledBuffer.free();
}
}
void handleDuplicate() {
// this method is called when the remote side forgot about us. Our mapping will have been already replaced.
// We must not send anything to the peer from here on because things may be in a broken state.
// Though this is a best-effort strategy as everything is screwed up in this case anyway.
conn.duplicateMessageId(messageId, channel.getRemoteConnection().getPeerAddress());
synchronized (inputStream) {
if (! streamClosed) {
eofReceived = true; // it wasn't really, but we should act like it was
closeSent = true; // we didn't really, but we should act like we did
cancelled = true; // just not the usual way...
inputStream.pushException(conn.duplicateMessageIdException());
}
}
}
void dumpState(final StringBuilder b) {
b.append(" ").append(String.format("Inbound message ID %04x, window %d\n", messageId & 0xFFFF, inboundWindow));
b.append(" ").append("* flags: ");
if (cancelled) b.append("cancelled ");
if (closeSent) b.append("close-sent ");
if (streamClosed) b.append("stream-closed ");
if (eofReceived) b.append("eof-received ");
b.append('\n');
}
}