/**
* Copyright 2007-2015, Kaazing Corporation. 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.
*/
package org.kaazing.k3po.driver.internal.netty.bootstrap.agrona;
import static java.lang.String.format;
import static java.lang.Thread.currentThread;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.jboss.netty.channel.Channels.fireChannelClosed;
import static org.jboss.netty.channel.Channels.fireChannelDisconnected;
import static org.jboss.netty.channel.Channels.fireChannelUnbound;
import static org.jboss.netty.channel.Channels.fireWriteComplete;
import static org.kaazing.k3po.driver.internal.netty.channel.Channels.fireFlushed;
import static org.kaazing.k3po.driver.internal.netty.channel.Channels.fireOutputShutdown;
import static org.agrona.BitUtil.SIZE_OF_INT;
import java.util.Deque;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CountDownLatch;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.ChannelException;
import org.jboss.netty.channel.ChannelFuture;
import org.kaazing.k3po.driver.internal.netty.channel.agrona.AgronaChannelAddress;
import org.kaazing.k3po.driver.internal.netty.channel.agrona.ChannelReader;
import org.kaazing.k3po.driver.internal.netty.channel.agrona.ChannelWriter;
import org.agrona.concurrent.BackoffIdleStrategy;
import org.agrona.concurrent.IdleStrategy;
import org.agrona.concurrent.UnsafeBuffer;
public final class AgronaWorker implements Runnable {
private static final long MAX_PARK_NS = MILLISECONDS.toNanos(100L);
private static final long MIN_PARK_NS = MILLISECONDS.toNanos(1L);
private static final int MAX_YIELDS = 30;
private static final int MAX_SPINS = 20;
private final Deque<Runnable> taskQueue;
private final Set<AgronaChannel> readableChannels;
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
private volatile boolean shutdown;
AgronaWorker() {
this.taskQueue = new ConcurrentLinkedDeque<>();
this.readableChannels = new ConcurrentSkipListSet<AgronaChannel>();
}
public void register(AgronaChannel channel) {
readableChannels.add(channel);
}
public void write(AgronaChannel channel, ChannelBuffer channelBuffer, ChannelFuture future) {
registerTask(new WriteTask(channel, channelBuffer, future));
}
public void flush(AgronaChannel channel, ChannelFuture future) {
registerTask(new FlushTask(channel, future));
}
public void shutdownOutput(AgronaChannel channel, ChannelFuture future) {
registerTask(new ShutdownOutputTask(channel, future));
}
public void close(AgronaChannel channel, ChannelFuture future) {
readableChannels.remove(channel);
registerTask(new CloseTask(channel, future));
}
@Override
public void run() {
final IdleStrategy idleStrategy = new BackoffIdleStrategy(MAX_SPINS, MAX_YIELDS, MIN_PARK_NS, MAX_PARK_NS);
while (!shutdown) {
int workCount = 0;
workCount += executeTasks();
workCount += readMessges();
idleStrategy.idle(workCount);
}
shutdownLatch.countDown();
}
public void shutdown() {
shutdown = true;
try {
shutdownLatch.await();
} catch (InterruptedException e) {
currentThread().interrupt();
}
}
private int executeTasks() {
int workCount = 0;
Runnable task;
while ((task = taskQueue.poll()) != null) {
task.run();
workCount++;
}
return workCount;
}
private int readMessges() {
int workCount = 0;
for (final AgronaChannel channel : readableChannels) {
AgronaChannelAddress remoteAddress = channel.getRemoteAddress();
ChannelReader reader = remoteAddress.getReader();
workCount += reader.read(channel.messageHandler);
}
return workCount;
}
private void registerTask(Runnable task) {
taskQueue.offer(task);
}
private static boolean flushWriteBufferIfNecessary(AgronaChannel channel) {
ChannelBuffer writeBuffer = channel.writeBuffer;
int readableBytes = writeBuffer.readableBytes();
if (readableBytes == 0) {
return false;
}
else if (readableBytes < SIZE_OF_INT) {
String message = format("Minimum %d bytes needed for message type id", SIZE_OF_INT);
throw new ChannelException(message);
}
else {
UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[readableBytes - SIZE_OF_INT]);
int msgTypeId = writeBuffer.getInt(0);
writeBuffer.getBytes(SIZE_OF_INT, srcBuffer.byteArray());
writeBuffer.writerIndex(0);
AgronaChannelAddress remoteAddress = channel.getRemoteAddress();
ChannelWriter writer = remoteAddress.getWriter();
writer.write(msgTypeId, srcBuffer, 0, srcBuffer.capacity());
return true;
}
}
private static final class WriteTask implements Runnable {
private final AgronaChannel channel;
private final ChannelBuffer buffer;
private final ChannelFuture future;
public WriteTask(
AgronaChannel channel,
ChannelBuffer buffer,
ChannelFuture future) {
this.channel = channel;
this.buffer = buffer;
this.future = future;
}
@Override
public void run() {
try {
ChannelBuffer writeBuffer = channel.writeBuffer;
int readableBytes = buffer.readableBytes();
writeBuffer.writeBytes(buffer);
future.setSuccess();
fireWriteComplete(channel, readableBytes);
}
catch (ChannelException ex) {
future.setFailure(ex);
}
}
}
private static final class FlushTask implements Runnable {
private final AgronaChannel channel;
private final ChannelFuture future;
public FlushTask(
AgronaChannel channel,
ChannelFuture future) {
this.channel = channel;
this.future = future;
}
@Override
public void run() {
try {
flushWriteBufferIfNecessary(channel);
future.setSuccess();
fireFlushed(channel);
}
catch (ChannelException ex) {
future.setFailure(ex);
}
}
}
private static final class ShutdownOutputTask implements Runnable {
private final AgronaChannel channel;
private final ChannelFuture future;
public ShutdownOutputTask(AgronaChannel channel, ChannelFuture future) {
this.channel = channel;
this.future = future;
}
@Override
public void run() {
try {
flushWriteBufferIfNecessary(channel);
future.setSuccess();
fireOutputShutdown(channel);
}
catch (ChannelException ex) {
future.setFailure(ex);
}
}
}
private static final class CloseTask implements Runnable {
private final AgronaChannel channel;
private final ChannelFuture future;
public CloseTask(
AgronaChannel channel,
ChannelFuture future) {
this.channel = channel;
this.future = future;
}
@Override
public void run() {
try {
flushWriteBufferIfNecessary(channel);
future.setSuccess();
if (channel.setClosed()) {
fireChannelDisconnected(channel);
fireChannelUnbound(channel);
fireChannelClosed(channel);
}
}
catch (ChannelException ex) {
future.setFailure(ex);
}
}
}
}