Overhaul on NIO socket writes
* Write operation is made in the caller thread opportunistically * DirectBufferPool became thread safe due to this change * This modification still requires more performance analysis and testing
This commit is contained in:
parent
c59da2dfe6
commit
b74cd60815
@ -29,8 +29,16 @@ final class DirectBufferPool {
|
|||||||
|
|
||||||
private static final int POOL_SIZE = 4;
|
private static final int POOL_SIZE = 4;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
private final ThreadLocal<SoftReference<ByteBuffer>[]> pool =
|
||||||
private final SoftReference<ByteBuffer>[] pool = new SoftReference[POOL_SIZE];
|
new ThreadLocal<SoftReference<ByteBuffer>[]>() {
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected SoftReference<ByteBuffer>[] initialValue() {
|
||||||
|
return new SoftReference[POOL_SIZE];
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
DirectBufferPool() {
|
DirectBufferPool() {
|
||||||
super();
|
super();
|
||||||
@ -44,6 +52,7 @@ final class DirectBufferPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final ByteBuffer acquire(int size) {
|
final ByteBuffer acquire(int size) {
|
||||||
|
final SoftReference<ByteBuffer>[] pool = this.pool.get();
|
||||||
for (int i = 0; i < POOL_SIZE; i ++) {
|
for (int i = 0; i < POOL_SIZE; i ++) {
|
||||||
SoftReference<ByteBuffer> ref = pool[i];
|
SoftReference<ByteBuffer> ref = pool[i];
|
||||||
if (ref == null) {
|
if (ref == null) {
|
||||||
@ -73,6 +82,7 @@ final class DirectBufferPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final void release(ByteBuffer buffer) {
|
final void release(ByteBuffer buffer) {
|
||||||
|
final SoftReference<ByteBuffer>[] pool = this.pool.get();
|
||||||
for (int i = 0; i < POOL_SIZE; i ++) {
|
for (int i = 0; i < POOL_SIZE; i ++) {
|
||||||
SoftReference<ByteBuffer> ref = pool[i];
|
SoftReference<ByteBuffer> ref = pool[i];
|
||||||
if (ref == null || ref.get() == null) {
|
if (ref == null || ref.get() == null) {
|
||||||
|
@ -114,7 +114,7 @@ class NioClientSocketPipelineSink extends AbstractChannelSink {
|
|||||||
NioSocketChannel channel = (NioSocketChannel) event.getChannel();
|
NioSocketChannel channel = (NioSocketChannel) event.getChannel();
|
||||||
boolean offered = channel.writeBuffer.offer(event);
|
boolean offered = channel.writeBuffer.offer(event);
|
||||||
assert offered;
|
assert offered;
|
||||||
channel.worker.write(channel, true);
|
channel.worker.write(channel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ class NioServerSocketPipelineSink extends AbstractChannelSink {
|
|||||||
NioSocketChannel channel = (NioSocketChannel) event.getChannel();
|
NioSocketChannel channel = (NioSocketChannel) event.getChannel();
|
||||||
boolean offered = channel.writeBuffer.offer(event);
|
boolean offered = channel.writeBuffer.offer(event);
|
||||||
assert offered;
|
assert offered;
|
||||||
channel.worker.write(channel, true);
|
channel.worker.write(channel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.jboss.netty.channel.socket.nio;
|
package org.jboss.netty.channel.socket.nio;
|
||||||
|
|
||||||
import static org.jboss.netty.channel.Channels.*;
|
import static org.jboss.netty.channel.Channels.fireChannelInterestChanged;
|
||||||
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
@ -24,6 +24,8 @@ import java.nio.channels.SocketChannel;
|
|||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
import org.jboss.netty.buffer.ChannelBuffer;
|
import org.jboss.netty.buffer.ChannelBuffer;
|
||||||
import org.jboss.netty.channel.AbstractChannel;
|
import org.jboss.netty.channel.AbstractChannel;
|
||||||
@ -59,7 +61,7 @@ class NioSocketChannel extends AbstractChannel
|
|||||||
private volatile InetSocketAddress remoteAddress;
|
private volatile InetSocketAddress remoteAddress;
|
||||||
|
|
||||||
final Object interestOpsLock = new Object();
|
final Object interestOpsLock = new Object();
|
||||||
final Object writeLock = new Object();
|
final Lock writeLock = new ReentrantLock();
|
||||||
|
|
||||||
final Runnable writeTask = new WriteTask();
|
final Runnable writeTask = new WriteTask();
|
||||||
final AtomicBoolean writeTaskInTaskQueue = new AtomicBoolean();
|
final AtomicBoolean writeTaskInTaskQueue = new AtomicBoolean();
|
||||||
@ -67,7 +69,6 @@ class NioSocketChannel extends AbstractChannel
|
|||||||
final Queue<MessageEvent> writeBuffer = new WriteBuffer();
|
final Queue<MessageEvent> writeBuffer = new WriteBuffer();
|
||||||
final AtomicInteger writeBufferSize = new AtomicInteger();
|
final AtomicInteger writeBufferSize = new AtomicInteger();
|
||||||
final AtomicInteger highWaterMarkCounter = new AtomicInteger();
|
final AtomicInteger highWaterMarkCounter = new AtomicInteger();
|
||||||
volatile boolean inWriteNowLoop;
|
|
||||||
|
|
||||||
MessageEvent currentWriteEvent;
|
MessageEvent currentWriteEvent;
|
||||||
ByteBuffer currentWriteBuffer;
|
ByteBuffer currentWriteBuffer;
|
||||||
@ -257,7 +258,7 @@ class NioSocketChannel extends AbstractChannel
|
|||||||
|
|
||||||
public void run() {
|
public void run() {
|
||||||
writeTaskInTaskQueue.set(false);
|
writeTaskInTaskQueue.set(false);
|
||||||
worker.write(NioSocketChannel.this, false);
|
worker.write(NioSocketChannel.this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -366,7 +366,7 @@ class NioWorker implements Runnable {
|
|||||||
|
|
||||||
private void write(SelectionKey k) {
|
private void write(SelectionKey k) {
|
||||||
NioSocketChannel ch = (NioSocketChannel) k.attachment();
|
NioSocketChannel ch = (NioSocketChannel) k.attachment();
|
||||||
write(ch, false);
|
write(ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void close(SelectionKey k) {
|
private void close(SelectionKey k) {
|
||||||
@ -374,69 +374,26 @@ class NioWorker implements Runnable {
|
|||||||
close(ch, succeededFuture(ch));
|
close(ch, succeededFuture(ch));
|
||||||
}
|
}
|
||||||
|
|
||||||
void write(final NioSocketChannel channel, boolean mightNeedWakeup) {
|
void write(final NioSocketChannel channel) {
|
||||||
if (!channel.isConnected()) {
|
if (!channel.isConnected()) {
|
||||||
cleanUpWriteBuffer(channel);
|
cleanUpWriteBuffer(channel);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mightNeedWakeup && scheduleWriteIfNecessary(channel)) {
|
if (!channel.writeLock.tryLock()) {
|
||||||
|
rescheduleWrite(channel);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (channel.inWriteNowLoop) {
|
final Queue<MessageEvent> writeBuffer = channel.writeBuffer;
|
||||||
scheduleWriteIfNecessary(channel);
|
final int writeSpinCount = channel.getConfig().getWriteSpinCount();
|
||||||
} else {
|
|
||||||
writeNow(channel, channel.getConfig().getWriteSpinCount());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean scheduleWriteIfNecessary(final NioSocketChannel channel) {
|
|
||||||
final Thread currentThread = Thread.currentThread();
|
|
||||||
final Thread workerThread = thread;
|
|
||||||
if (currentThread != workerThread) {
|
|
||||||
if (channel.writeTaskInTaskQueue.compareAndSet(false, true)) {
|
|
||||||
boolean offered = writeTaskQueue.offer(channel.writeTask);
|
|
||||||
assert offered;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(channel instanceof NioAcceptedSocketChannel) ||
|
|
||||||
((NioAcceptedSocketChannel) channel).bossThread != currentThread) {
|
|
||||||
final Selector workerSelector = selector;
|
|
||||||
if (workerSelector != null) {
|
|
||||||
if (wakenUp.compareAndSet(false, true)) {
|
|
||||||
workerSelector.wakeup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// A write request can be made from an acceptor thread (boss)
|
|
||||||
// when a user attempted to write something in:
|
|
||||||
//
|
|
||||||
// * channelOpen()
|
|
||||||
// * channelBound()
|
|
||||||
// * channelConnected().
|
|
||||||
//
|
|
||||||
// In this case, there's no need to wake up the selector because
|
|
||||||
// the channel is not even registered yet at this moment.
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeNow(NioSocketChannel channel, int writeSpinCount) {
|
|
||||||
|
|
||||||
boolean open = true;
|
boolean open = true;
|
||||||
boolean addOpWrite = false;
|
boolean addOpWrite = false;
|
||||||
boolean removeOpWrite = false;
|
boolean removeOpWrite = false;
|
||||||
|
|
||||||
int writtenBytes = 0;
|
int writtenBytes = 0;
|
||||||
|
|
||||||
Queue<MessageEvent> writeBuffer = channel.writeBuffer;
|
try {
|
||||||
synchronized (channel.writeLock) {
|
|
||||||
channel.inWriteNowLoop = true;
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
MessageEvent evt = channel.currentWriteEvent;
|
MessageEvent evt = channel.currentWriteEvent;
|
||||||
ByteBuffer buf;
|
ByteBuffer buf;
|
||||||
@ -503,75 +460,60 @@ class NioWorker implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
channel.inWriteNowLoop = false;
|
} finally {
|
||||||
|
channel.writeLock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
fireWriteComplete(channel, writtenBytes);
|
fireWriteComplete(channel, writtenBytes);
|
||||||
|
|
||||||
if (open) {
|
if (open) {
|
||||||
|
// interestOps can change at any time and at any thread.
|
||||||
|
// Acquire a lock to avoid possible race condition.
|
||||||
if (addOpWrite) {
|
if (addOpWrite) {
|
||||||
setOpWrite(channel);
|
synchronized (channel.interestOpsLock) {
|
||||||
|
int interestOps = channel.getRawInterestOps();
|
||||||
|
if ((interestOps & SelectionKey.OP_WRITE) == 0) {
|
||||||
|
interestOps |= SelectionKey.OP_WRITE;
|
||||||
|
setInterestOps0(channel, interestOps);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (removeOpWrite) {
|
} else if (removeOpWrite) {
|
||||||
clearOpWrite(channel);
|
synchronized (channel.interestOpsLock) {
|
||||||
|
int interestOps = channel.getRawInterestOps();
|
||||||
|
if ((interestOps & SelectionKey.OP_WRITE) != 0) {
|
||||||
|
interestOps &= ~SelectionKey.OP_WRITE;
|
||||||
|
setInterestOps0(channel, interestOps);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setOpWrite(NioSocketChannel channel) {
|
private void rescheduleWrite(final NioSocketChannel channel) {
|
||||||
Selector selector = this.selector;
|
final Thread currentThread = Thread.currentThread();
|
||||||
SelectionKey key = channel.socket.keyFor(selector);
|
if (channel.writeTaskInTaskQueue.compareAndSet(false, true)) {
|
||||||
if (key == null) {
|
boolean offered = writeTaskQueue.offer(channel.writeTask);
|
||||||
return;
|
assert offered;
|
||||||
}
|
}
|
||||||
if (!key.isValid()) {
|
|
||||||
close(key);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int interestOps;
|
|
||||||
boolean changed = false;
|
|
||||||
|
|
||||||
// interestOps can change at any time and at any thread.
|
if (!(channel instanceof NioAcceptedSocketChannel) ||
|
||||||
// Acquire a lock to avoid possible race condition.
|
((NioAcceptedSocketChannel) channel).bossThread != currentThread) {
|
||||||
synchronized (channel.interestOpsLock) {
|
final Selector workerSelector = selector;
|
||||||
interestOps = channel.getRawInterestOps();
|
if (workerSelector != null) {
|
||||||
if ((interestOps & SelectionKey.OP_WRITE) == 0) {
|
if (wakenUp.compareAndSet(false, true)) {
|
||||||
interestOps |= SelectionKey.OP_WRITE;
|
workerSelector.wakeup();
|
||||||
key.interestOps(interestOps);
|
}
|
||||||
changed = true;
|
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
// A write request can be made from an acceptor thread (boss)
|
||||||
if (changed) {
|
// when a user attempted to write something in:
|
||||||
channel.setRawInterestOpsNow(interestOps);
|
//
|
||||||
}
|
// * channelOpen()
|
||||||
}
|
// * channelBound()
|
||||||
|
// * channelConnected().
|
||||||
private void clearOpWrite(NioSocketChannel channel) {
|
//
|
||||||
Selector selector = this.selector;
|
// In this case, there's no need to wake up the selector because
|
||||||
SelectionKey key = channel.socket.keyFor(selector);
|
// the channel is not even registered yet at this moment.
|
||||||
if (key == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!key.isValid()) {
|
|
||||||
close(key);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int interestOps;
|
|
||||||
boolean changed = false;
|
|
||||||
|
|
||||||
// interestOps can change at any time and at any thread.
|
|
||||||
// Acquire a lock to avoid possible race condition.
|
|
||||||
synchronized (channel.interestOpsLock) {
|
|
||||||
interestOps = channel.getRawInterestOps();
|
|
||||||
if ((interestOps & SelectionKey.OP_WRITE) != 0) {
|
|
||||||
interestOps &= ~SelectionKey.OP_WRITE;
|
|
||||||
key.interestOps(interestOps);
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changed) {
|
|
||||||
channel.setRawInterestOpsNow(interestOps);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -607,7 +549,8 @@ class NioWorker implements Runnable {
|
|||||||
boolean fireExceptionCaught = false;
|
boolean fireExceptionCaught = false;
|
||||||
|
|
||||||
// Clean up the stale messages in the write buffer.
|
// Clean up the stale messages in the write buffer.
|
||||||
synchronized (channel.writeLock) {
|
channel.writeLock.lock();
|
||||||
|
try {
|
||||||
MessageEvent evt = channel.currentWriteEvent;
|
MessageEvent evt = channel.currentWriteEvent;
|
||||||
ByteBuffer buf = channel.currentWriteBuffer;
|
ByteBuffer buf = channel.currentWriteBuffer;
|
||||||
if (evt != null) {
|
if (evt != null) {
|
||||||
@ -653,6 +596,8 @@ class NioWorker implements Runnable {
|
|||||||
fireExceptionCaught = true;
|
fireExceptionCaught = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
channel.writeLock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fireExceptionCaught) {
|
if (fireExceptionCaught) {
|
||||||
@ -662,64 +607,21 @@ class NioWorker implements Runnable {
|
|||||||
|
|
||||||
void setInterestOps(
|
void setInterestOps(
|
||||||
NioSocketChannel channel, ChannelFuture future, int interestOps) {
|
NioSocketChannel channel, ChannelFuture future, int interestOps) {
|
||||||
boolean changed = false;
|
|
||||||
|
// Override OP_WRITE flag - a user cannot change this flag.
|
||||||
|
interestOps &= ~Channel.OP_WRITE;
|
||||||
|
interestOps |= channel.getRawInterestOps() & Channel.OP_WRITE;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// interestOps can change at any time and at any thread.
|
// interestOps can change at any time and at any thread.
|
||||||
// Acquire a lock to avoid possible race condition.
|
// Acquire a lock to avoid possible race condition.
|
||||||
|
boolean changed;
|
||||||
synchronized (channel.interestOpsLock) {
|
synchronized (channel.interestOpsLock) {
|
||||||
Selector selector = this.selector;
|
changed = setInterestOps0(channel, interestOps);
|
||||||
SelectionKey key = channel.socket.keyFor(selector);
|
|
||||||
|
|
||||||
if (key == null || selector == null) {
|
|
||||||
// Not registered to the worker yet.
|
|
||||||
// Set the rawInterestOps immediately; RegisterTask will pick it up.
|
|
||||||
channel.setRawInterestOpsNow(interestOps);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Override OP_WRITE flag - a user cannot change this flag.
|
|
||||||
interestOps &= ~Channel.OP_WRITE;
|
|
||||||
interestOps |= channel.getRawInterestOps() & Channel.OP_WRITE;
|
|
||||||
|
|
||||||
switch (CONSTRAINT_LEVEL) {
|
|
||||||
case 0:
|
|
||||||
if (channel.getRawInterestOps() != interestOps) {
|
|
||||||
key.interestOps(interestOps);
|
|
||||||
if (Thread.currentThread() != thread &&
|
|
||||||
wakenUp.compareAndSet(false, true)) {
|
|
||||||
selector.wakeup();
|
|
||||||
}
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
case 2:
|
|
||||||
if (channel.getRawInterestOps() != interestOps) {
|
|
||||||
if (Thread.currentThread() == thread) {
|
|
||||||
key.interestOps(interestOps);
|
|
||||||
changed = true;
|
|
||||||
} else {
|
|
||||||
selectorGuard.readLock().lock();
|
|
||||||
try {
|
|
||||||
if (wakenUp.compareAndSet(false, true)) {
|
|
||||||
selector.wakeup();
|
|
||||||
}
|
|
||||||
key.interestOps(interestOps);
|
|
||||||
changed = true;
|
|
||||||
} finally {
|
|
||||||
selectorGuard.readLock().unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
future.setSuccess();
|
future.setSuccess();
|
||||||
if (changed) {
|
if (changed) {
|
||||||
channel.setRawInterestOpsNow(interestOps);
|
|
||||||
fireChannelInterestChanged(channel);
|
fireChannelInterestChanged(channel);
|
||||||
}
|
}
|
||||||
} catch (CancelledKeyException e) {
|
} catch (CancelledKeyException e) {
|
||||||
@ -733,6 +635,57 @@ class NioWorker implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean setInterestOps0(NioSocketChannel channel, int interestOps) {
|
||||||
|
Selector selector = this.selector;
|
||||||
|
SelectionKey key = channel.socket.keyFor(selector);
|
||||||
|
|
||||||
|
if (key == null || selector == null) {
|
||||||
|
// Not registered to the worker yet.
|
||||||
|
// Set the rawInterestOps immediately; RegisterTask will pick it up.
|
||||||
|
channel.setRawInterestOpsNow(interestOps);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (CONSTRAINT_LEVEL) {
|
||||||
|
case 0:
|
||||||
|
if (channel.getRawInterestOps() != interestOps) {
|
||||||
|
key.interestOps(interestOps);
|
||||||
|
if (Thread.currentThread() != thread &&
|
||||||
|
wakenUp.compareAndSet(false, true)) {
|
||||||
|
selector.wakeup();
|
||||||
|
}
|
||||||
|
channel.setRawInterestOpsNow(interestOps);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
if (channel.getRawInterestOps() != interestOps) {
|
||||||
|
if (Thread.currentThread() == thread) {
|
||||||
|
key.interestOps(interestOps);
|
||||||
|
channel.setRawInterestOpsNow(interestOps);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
selectorGuard.readLock().lock();
|
||||||
|
try {
|
||||||
|
if (wakenUp.compareAndSet(false, true)) {
|
||||||
|
selector.wakeup();
|
||||||
|
}
|
||||||
|
key.interestOps(interestOps);
|
||||||
|
channel.setRawInterestOpsNow(interestOps);
|
||||||
|
return true;
|
||||||
|
} finally {
|
||||||
|
selectorGuard.readLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private final class RegisterTask implements Runnable {
|
private final class RegisterTask implements Runnable {
|
||||||
private final NioSocketChannel channel;
|
private final NioSocketChannel channel;
|
||||||
private final ChannelFuture future;
|
private final ChannelFuture future;
|
||||||
|
Loading…
Reference in New Issue
Block a user