I am using Netty embedded within Grails to process and display incoming SNMP messages. Since Netty 4 doesn't come with a built-in ChannelExecutionHandler
I had to make my own and would like some feedback on it.
package org.ciscotalk.common.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.socket.DatagramPacket;
import lombok.Getter;
import lombok.Setter;
import org.ciscotalk.common.netty.exec.ChannelDecoderEvent;
import org.ciscotalk.common.snmp.SnmpMessage;
import org.ciscotalk.snmp.SnmpService;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.concurrent.*;
/**
* @author Diljot
*/
@ChannelHandler.Sharable
public class ChannelExecutionHandler
extends CombinedChannelDuplexHandler<ChannelExecutionHandler.ChannelInboundExecutionHandler,
ChannelExecutionHandler.ChannelOutboundExecutionHandler>
{
@Autowired
@Getter @Setter
private SnmpService snmpService;
private final ExecutorService executor = Executors.newCachedThreadPool();
private final BlockingDeque<ByteBuf> incoming = new LinkedBlockingDeque<>();
//TODO private static final BlockingDeque<?> outgoing = new LinkedBlockingDeque<>();
private final BlockingDeque<Future<?>> futures = new LinkedBlockingDeque<>();
public ChannelExecutionHandler() {
init(new ChannelInboundExecutionHandler(), new ChannelOutboundExecutionHandler());
Executors.newSingleThreadExecutor().execute(producer);
Executors.newSingleThreadExecutor().execute(consumer);
}
public final Runnable producer = new Runnable() {
@Override
public void run() {
try {
ChannelDecoderEvent<?> ev = nextEvent(incoming.take());
if (ev != null) {
Future<?> f = executor.submit((Runnable) ev, ev);
futures.put(f);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
public final Runnable consumer = new Runnable() {
@Override
public void run() {
try {
Future<?> f = futures.take();
if (f != null) {
Object o = f.get();
snmpService.pushToQueue((SnmpMessage) o);
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
};
private <T extends SnmpMessage> ChannelDecoderEvent<T> nextEvent(ByteBuf buf) {
return new ChannelDecoderEvent<>(buf);
}
public final class ChannelInboundExecutionHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = ((DatagramPacket) msg).content();
buf.retain();
incoming.put(buf);
}
}
public final class ChannelOutboundExecutionHandler extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
ctx.write(msg, promise);
}
}
}
package org.ciscotalk.snmp
import grails.transaction.Transactional
import org.ciscotalk.common.snmp.SnmpMessage
@Transactional
class SnmpService<T extends SnmpMessage> {
Queue<T> incoming = new LinkedList<>()
def pushToQueue(T obj) {
incoming.add(obj)
}
}
package org.ciscotalk.common.snmp;
/**
* @author Diljot
*/
public interface SnmpMessage {
}
package org.ciscotalk.common.netty.exec;
import io.netty.buffer.ByteBuf;
import org.ciscotalk.common.snmp.SnmpMessage;
import java.util.concurrent.Callable;
/**
* @author Diljot
*/
public class ChannelDecoderEvent<T extends SnmpMessage> implements Callable<T> {
private final ByteBuf buf;
public ChannelDecoderEvent(ByteBuf buf) {
this.buf = buf;
}
@Override
public T call() throws Exception {
// handle decoding then return encoded SnmpMessage<T>
return null;
}
}
The intended flow is:
ChannelInboundExecutionHandler
receives incomingUDP packet
, stores it as aByteBuf
in theincoming queue
.- The
producer
thread picks up theByteBuf
and decodes/processes the packet by submitting aChannelDecoderEvent
- The future is stored in the
futures
queue - The
consumer
thread picks up theFuture<?>
and proceeds to invoke theget()
function. - Upon successful recovery it passes the processed
SnmpMessage<T>
to the GrailsSnmpService
which handles persisting the data and displaying it in the Grails webapp. (This hasn't been implemented yet but that's the plan)
I would like some C&C if this model works out, or if I should handle it differently.