org.jboss.remoting3.remote.RemoteConnectionChannel Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote Jakarta Enterprise Beans and Jakarta Messaging, including
all dependencies. It is intended for use by those not using maven, maven users should just import the Jakarta Enterprise Beans and
Jakarta Messaging 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).
The newest version!
/*
* 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 static org.jboss.remoting3._private.Messages.log;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.function.ToIntFunction;
import org.jboss.remoting3.Attachments;
import org.jboss.remoting3.Channel;
import org.jboss.remoting3.ChannelBusyException;
import org.jboss.remoting3.Connection;
import org.jboss.remoting3.MessageCancelledException;
import org.jboss.remoting3.MessageOutputStream;
import org.jboss.remoting3.NotOpenException;
import org.jboss.remoting3.RemotingOptions;
import org.jboss.remoting3._private.Equaller;
import org.jboss.remoting3._private.IntIndexHashMap;
import org.jboss.remoting3._private.IntIndexMap;
import org.jboss.remoting3.spi.AbstractHandleableCloseable;
import org.jboss.remoting3.spi.ConnectionHandlerContext;
import org.xnio.Bits;
import org.xnio.Option;
import org.xnio.Pooled;
/**
* @author David M. Lloyd
*/
final class RemoteConnectionChannel extends AbstractHandleableCloseable implements Channel {
static final ToIntFunction INDEXER = RemoteConnectionChannel::getChannelId;
private final RemoteConnectionHandler connectionHandler;
private final ConnectionHandlerContext connectionHandlerContext;
private final RemoteConnection connection;
private final int channelId;
private final IntIndexMap outboundMessages = new IntIndexHashMap(OutboundMessage.INDEXER, Equaller.IDENTITY, 512, 0.5f);
private final IntIndexMap inboundMessages = new IntIndexHashMap(InboundMessage.INDEXER, Equaller.IDENTITY, 512, 0.5f);
private final int outboundWindow;
private final int inboundWindow;
private final Attachments attachments = new Attachments();
private final Queue inboundMessageQueue = new ArrayDeque();
private final int maxOutboundMessages;
private final int maxInboundMessages;
private final long maxOutboundMessageSize;
private final long maxInboundMessageSize;
private final long messageAckTimeout;
private volatile int channelState = 0;
private static final AtomicIntegerFieldUpdater channelStateUpdater = AtomicIntegerFieldUpdater.newUpdater(RemoteConnectionChannel.class, "channelState");
private Receiver nextReceiver;
private static final int WRITE_CLOSED = (1 << 31);
private static final int READ_CLOSED = (1 << 30);
private static final int OUTBOUND_MESSAGES_MASK = (1 << 15) - 1;
private static final int ONE_OUTBOUND_MESSAGE = 1;
private static final int INBOUND_MESSAGES_MASK = ((1 << 30) - 1) & ~OUTBOUND_MESSAGES_MASK;
private static final int ONE_INBOUND_MESSAGE = (1 << 15);
RemoteConnectionChannel(final RemoteConnectionHandler connectionHandler, final RemoteConnection connection, final int channelId, final int outboundWindow, final int inboundWindow, final int maxOutboundMessages, final int maxInboundMessages, final long maxOutboundMessageSize, final long maxInboundMessageSize, final long messageAckTimeout) {
super(connectionHandler.getConnectionContext().getConnectionProviderContext().getExecutor(), true);
this.maxOutboundMessageSize = maxOutboundMessageSize;
this.maxInboundMessageSize = maxInboundMessageSize;
connectionHandlerContext = connectionHandler.getConnectionContext();
this.connectionHandler = connectionHandler;
this.connection = connection;
this.channelId = channelId;
this.outboundWindow = outboundWindow;
this.inboundWindow = inboundWindow;
this.maxOutboundMessages = maxOutboundMessages;
this.maxInboundMessages = maxInboundMessages;
this.messageAckTimeout = messageAckTimeout;
}
void openOutboundMessage() throws IOException {
int oldState, newState;
do {
oldState = channelState;
if ((oldState & WRITE_CLOSED) != 0) {
throw new NotOpenException("Writes closed");
}
final int outboundCount = oldState & OUTBOUND_MESSAGES_MASK;
if (outboundCount == maxOutboundMessages) {
throw new ChannelBusyException("Too many open outbound writes");
}
newState = oldState + ONE_OUTBOUND_MESSAGE;
} while (!casState(oldState, newState));
log.tracef("Opened outbound message on %s", this);
}
private int incrementState(final int count) {
final int oldState = channelStateUpdater.getAndAdd(this, count);
if (log.isTraceEnabled()) {
final int newState = oldState + count;
log.tracef("CAS %s\n\told: RS=%s WS=%s IM=%d OM=%d\n\tnew: RS=%s WS=%s IM=%d OM=%d", this,
Boolean.valueOf((oldState & READ_CLOSED) != 0),
Boolean.valueOf((oldState & WRITE_CLOSED) != 0),
Integer.valueOf((oldState & INBOUND_MESSAGES_MASK) >> Integer.numberOfTrailingZeros(ONE_INBOUND_MESSAGE)),
Integer.valueOf((oldState & OUTBOUND_MESSAGES_MASK) >> Integer.numberOfTrailingZeros(ONE_OUTBOUND_MESSAGE)),
Boolean.valueOf((newState & READ_CLOSED) != 0),
Boolean.valueOf((newState & WRITE_CLOSED) != 0),
Integer.valueOf((newState & INBOUND_MESSAGES_MASK) >> Integer.numberOfTrailingZeros(ONE_INBOUND_MESSAGE)),
Integer.valueOf((newState & OUTBOUND_MESSAGES_MASK) >> Integer.numberOfTrailingZeros(ONE_OUTBOUND_MESSAGE))
);
}
return oldState;
}
private boolean casState(final int oldState, final int newState) {
final boolean result = channelStateUpdater.compareAndSet(this, oldState, newState);
if (result && log.isTraceEnabled()) {
log.tracef("CAS %s\n\told: RS=%s WS=%s IM=%d OM=%d\n\tnew: RS=%s WS=%s IM=%d OM=%d", this,
Boolean.valueOf((oldState & READ_CLOSED) != 0),
Boolean.valueOf((oldState & WRITE_CLOSED) != 0),
Integer.valueOf((oldState & INBOUND_MESSAGES_MASK) >> Integer.numberOfTrailingZeros(ONE_INBOUND_MESSAGE)),
Integer.valueOf((oldState & OUTBOUND_MESSAGES_MASK) >> Integer.numberOfTrailingZeros(ONE_OUTBOUND_MESSAGE)),
Boolean.valueOf((newState & READ_CLOSED) != 0),
Boolean.valueOf((newState & WRITE_CLOSED) != 0),
Integer.valueOf((newState & INBOUND_MESSAGES_MASK) >> Integer.numberOfTrailingZeros(ONE_INBOUND_MESSAGE)),
Integer.valueOf((newState & OUTBOUND_MESSAGES_MASK) >> Integer.numberOfTrailingZeros(ONE_OUTBOUND_MESSAGE))
);
}
return result;
}
void closeOutboundMessage() {
int oldState = incrementState(- ONE_OUTBOUND_MESSAGE);
if (oldState == (WRITE_CLOSED | READ_CLOSED)) {
// no messages left and read & write closed
log.tracef("Closed outbound message on %s (unregistering)", this);
unregister();
} else {
log.tracef("Closed outbound message on %s", this);
}
}
boolean openInboundMessage() {
int oldState, newState;
do {
oldState = channelState;
if ((oldState & READ_CLOSED) != 0) {
log.tracef("Refusing inbound message on %s (reads closed)", this);
return false;
}
final int inboundCount = oldState & INBOUND_MESSAGES_MASK;
if (inboundCount == maxInboundMessages) {
log.tracef("Refusing inbound message on %s (too many concurrent reads)", this);
return false;
}
newState = oldState + ONE_INBOUND_MESSAGE;
} while (!casState(oldState, newState));
log.tracef("Opened inbound message on %s", this);
return true;
}
void closeInboundMessage() {
int oldState = incrementState(-ONE_INBOUND_MESSAGE);
if (oldState == (WRITE_CLOSED | READ_CLOSED)) {
// no messages left and read & write closed
log.tracef("Closed inbound message on %s (unregistering)", this);
unregister();
} else {
log.tracef("Closed inbound message on %s", this);
}
}
void closeReads() {
int oldState, newState;
do {
oldState = channelState;
if ((oldState & READ_CLOSED) != 0) {
return;
}
newState = oldState | READ_CLOSED;
} while (!casState(oldState, newState));
if (oldState == WRITE_CLOSED) {
// no channels
log.tracef("Closed channel reads on %s (unregistering)", this);
unregister();
} else {
log.tracef("Closed channel reads on %s", this);
}
notifyEnd();
}
boolean closeWrites() {
int oldState, newState;
do {
oldState = channelState;
if ((oldState & WRITE_CLOSED) != 0) {
return false;
}
newState = oldState | WRITE_CLOSED;
} while (!casState(oldState, newState));
if (oldState == READ_CLOSED) {
// no channels and read was closed
log.tracef("Closed channel writes on %s (unregistering)", this);
unregister();
} else {
log.tracef("Closed channel writes on %s", this);
}
return true;
}
boolean closeReadsAndWrites() {
int oldState, newState;
do {
oldState = channelState;
if ((oldState & (READ_CLOSED | WRITE_CLOSED)) == (READ_CLOSED | WRITE_CLOSED)) {
return false;
}
newState = oldState | READ_CLOSED | WRITE_CLOSED;
} while (!casState(oldState, newState));
if ((oldState & WRITE_CLOSED) == 0) {
// we're sending the write close request asynchronously
Pooled pooled = connection.allocate();
boolean ok = false;
try {
ByteBuffer byteBuffer = pooled.getResource();
byteBuffer.put(Protocol.CHANNEL_SHUTDOWN_WRITE);
byteBuffer.putInt(channelId);
byteBuffer.flip();
ok = true;
connection.send(pooled);
} finally {
if (! ok) pooled.free();
}
log.tracef("Closed channel reads on %s", this);
}
if ((oldState & (INBOUND_MESSAGES_MASK | OUTBOUND_MESSAGES_MASK)) == 0) {
// there were no channels open
log.tracef("Closed channel reads and writes on %s (unregistering)", this);
unregister();
} else {
log.tracef("Closed channel reads and writes on %s", this);
}
notifyEnd();
return true;
}
private void notifyEnd() {
synchronized (connection.getLock()) {
if (nextReceiver != null) {
final Receiver receiver = nextReceiver;
nextReceiver = null;
try {
getExecutor().execute(() -> receiver.handleEnd(RemoteConnectionChannel.this));
} catch (Throwable t) {
connection.handleException(new IOException("Fatal connection error", t));
return;
}
}
}
}
private void unregister() {
log.tracef("Unregistering %s", this);
closeAsync();
connectionHandler.handleChannelClosed(this);
}
public MessageOutputStream writeMessage() throws IOException {
int tries = 50;
IntIndexMap outboundMessages = this.outboundMessages;
openOutboundMessage();
boolean ok = false;
try {
final Random random = ThreadLocalRandom.current();
while (tries > 0) {
final int id = random.nextInt() & 0xfffe;
if (! outboundMessages.containsKey(id)) {
OutboundMessage message = new OutboundMessage((short) id, this, outboundWindow, maxOutboundMessageSize, messageAckTimeout);
OutboundMessage existing = outboundMessages.putIfAbsent(message);
if (existing == null) {
ok = true;
return message;
}
}
tries --;
}
throw log.channelBusy();
} finally {
if (! ok) {
closeOutboundMessage();
}
}
}
void free(OutboundMessage outboundMessage) {
if (outboundMessages.remove(outboundMessage)) {
log.tracef("Removed %s", outboundMessage);
} else {
log.tracef("Got redundant free for %s", outboundMessage);
}
}
public void writeShutdown() throws IOException {
if (closeWrites()) {
Pooled pooled = connection.allocate();
boolean ok = false;
try {
ByteBuffer byteBuffer = pooled.getResource();
byteBuffer.put(Protocol.CHANNEL_SHUTDOWN_WRITE);
byteBuffer.putInt(channelId);
byteBuffer.flip();
connection.send(pooled);
ok = true;
} finally {
if (! ok) pooled.free();
}
}
}
void handleRemoteClose() {
closeReadsAndWrites();
}
void handleIncomingWriteShutdown() {
closeReads();
}
public void receiveMessage(final Receiver handler) {
synchronized (connection.getLock()) {
if (inboundMessageQueue.isEmpty()) {
if ((channelState & READ_CLOSED) != 0) {
getExecutor().execute(() -> handler.handleEnd(RemoteConnectionChannel.this));
} else if (nextReceiver != null) {
throw new IllegalStateException("Message handler already queued");
} else {
nextReceiver = handler;
}
} else {
final InboundMessage message = inboundMessageQueue.remove();
try {
getExecutor().execute(() -> handler.handleMessage(RemoteConnectionChannel.this, message.messageInputStream));
} catch (Throwable t) {
connection.handleException(new IOException("Fatal connection error", t));
return;
}
}
connection.getLock().notify();
}
}
private static Set
© 2015 - 2025 Weber Informatics LLC | Privacy Policy