netty5/transport/src/main/java/io/netty/channel/ChannelOutboundBuffer.java

257 lines
8.1 KiB
Java

/*
* Copyright 2013 The Netty Project
*
* The Netty Project licenses this file to you 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.
*/
/*
* Written by Josh Bloch of Google Inc. and released to the public domain,
* as explained at http://creativecommons.org/publicdomain/zero/1.0/.
*/
package io.netty.channel;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
final class ChannelOutboundBuffer {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(ChannelOutboundBuffer.class);
private static final int MIN_INITIAL_CAPACITY = 8;
MessageList currentMessages;
int currentMessageIndex;
private long currentMessageListSize;
private MessageList[] messages;
private long[] messageListSizes;
private int head;
private int tail;
private boolean inFail;
private final AbstractChannel channel;
private long pendingOutboundBytes;
private static final AtomicIntegerFieldUpdater<ChannelOutboundBuffer> WRITABLE_UPDATER =
AtomicIntegerFieldUpdater.newUpdater(ChannelOutboundBuffer.class, "writable");
@SuppressWarnings("unused")
private volatile int writable = 1;
ChannelOutboundBuffer(AbstractChannel channel) {
this(channel, MIN_INITIAL_CAPACITY << 1);
}
@SuppressWarnings("unchecked")
ChannelOutboundBuffer(AbstractChannel channel, int initialCapacity) {
if (initialCapacity < 0) {
throw new IllegalArgumentException("initialCapacity: " + initialCapacity + " (expected: >= 0)");
}
// Find the best power of two to hold elements.
// Tests "<=" because arrays aren't kept full.
if (initialCapacity >= MIN_INITIAL_CAPACITY) {
initialCapacity |= initialCapacity >>> 1;
initialCapacity |= initialCapacity >>> 2;
initialCapacity |= initialCapacity >>> 4;
initialCapacity |= initialCapacity >>> 8;
initialCapacity |= initialCapacity >>> 16;
initialCapacity ++;
if (initialCapacity < 0) { // Too many elements, must back off
initialCapacity >>>= 1; // Good luck allocating 2 ^ 30 elements
}
} else {
initialCapacity = MIN_INITIAL_CAPACITY;
}
messages = new MessageList[initialCapacity];
messageListSizes = new long[initialCapacity];
this.channel = channel;
}
void addMessage(Object msg, ChannelPromise promise) {
int tail = this.tail;
MessageList msgs = messages[tail];
if (msgs == null) {
messages[tail] = msgs = MessageList.newInstance();
}
msgs.add(msg, promise);
int size = channel.calculateMessageSize(msg);
messageListSizes[tail] += size;
incrementPendingOutboundBytes(size);
}
void addFlush() {
int tail = this.tail;
if ((this.tail = tail + 1 & messages.length - 1) == head) {
doubleCapacity();
}
}
private void incrementPendingOutboundBytes(int size) {
if (size == 0) {
return;
}
long newWriteBufferSize = pendingOutboundBytes += size;
int highWaterMark = channel.config().getWriteBufferHighWaterMark();
if (newWriteBufferSize > highWaterMark) {
if (WRITABLE_UPDATER.compareAndSet(this, 1, 0)) {
channel.pipeline().fireChannelWritabilityChanged();
}
}
}
private void decrementPendingOutboundBytes(long size) {
if (size == 0) {
return;
}
long newWriteBufferSize = pendingOutboundBytes -= size;
int lowWaterMark = channel.config().getWriteBufferLowWaterMark();
if (newWriteBufferSize == 0 || newWriteBufferSize < lowWaterMark) {
if (WRITABLE_UPDATER.compareAndSet(this, 0, 1)) {
channel.pipeline().fireChannelWritabilityChanged();
}
}
}
private void doubleCapacity() {
assert head == tail;
int p = head;
int n = messages.length;
int r = n - p; // number of elements to the right of p
int newCapacity = n << 1;
if (newCapacity < 0) {
throw new IllegalStateException("Sorry, deque too big");
}
@SuppressWarnings("unchecked")
MessageList[] a1 = new MessageList[newCapacity];
System.arraycopy(messages, p, a1, 0, r);
System.arraycopy(messages, 0, a1, r, p);
messages = a1;
long[] a2 = new long[newCapacity];
System.arraycopy(messageListSizes, p, a2, 0, r);
System.arraycopy(messageListSizes, 0, a2, r, p);
messageListSizes = a2;
head = 0;
tail = n;
}
boolean next() {
// FIXME: pendingOutboundBytes should be decreased when the messages are flushed.
decrementPendingOutboundBytes(currentMessageListSize);
int h = head;
MessageList e = messages[h]; // Element is null if deque empty
if (e == null) {
currentMessageListSize = 0;
currentMessages = null;
return false;
}
currentMessages = messages[h];
currentMessageIndex = 0;
currentMessageListSize = messageListSizes[h];
messages[h] = null;
messageListSizes[h] = 0;
head = h + 1 & messages.length - 1;
return true;
}
boolean getWritable() {
return WRITABLE_UPDATER.get(this) == 1;
}
int size() {
return tail - head & messages.length - 1;
}
boolean isEmpty() {
return head == tail;
}
void clear() {
int head = this.head;
int tail = this.tail;
if (head != tail) {
this.head = this.tail = 0;
final int mask = messages.length - 1;
int i = head;
do {
messages[i] = null;
messageListSizes[i] = 0;
i = i + 1 & mask;
} while (i != tail);
}
}
void fail(Throwable cause) {
// Make sure that this method does not reenter. A listener added to the current promise can be notified by the
// current thread in the tryFailure() call of the loop below, and the listener can trigger another fail() call
// indirectly (usually by closing the channel.)
//
// See https://github.com/netty/netty/issues/1501
if (inFail) {
return;
}
try {
inFail = true;
if (currentMessages == null) {
if (!next()) {
return;
}
}
do {
if (currentMessages != null) {
// Release all failed messages.
Object[] messages = currentMessages.messages();
ChannelPromise[] promises = currentMessages.promises();
final int size = currentMessages.size();
try {
for (int i = currentMessageIndex; i < size; i++) {
ReferenceCountUtil.release(messages[i]);
ChannelPromise p = promises[i];
if (!(p instanceof VoidChannelPromise) && !p.tryFailure(cause)) {
logger.warn("Promise done already: {} - new exception is:", p, cause);
}
}
} finally {
currentMessages.recycle();
}
}
} while(next());
} finally {
inFail = false;
}
}
}