2
votes

I'm trying to pass an object from one handler to another using the ChannelContext.attr() method:

private class TCPInitializer extends ChannelInitializer<SocketChannel>
{
    @Override
    public void initChannel(final SocketChannel ch) throws Exception
    {
        ChannelPipeline pipeline = ch.pipeline();

        pipeline.addLast(new ReadTimeoutHandler(mIdleTimeout));
        pipeline.addLast(new HostMappingHandler<SyslogHandler>(mRegisteredClients,
                                                               mChannels));
        pipeline.addLast(new DelimiterBasedFrameDecoder(MAX_FRAME_SIZE,
                         Delimiters.lineDelimiter()));
        pipeline.addLast(new Dispatcher());
    }
}

The HostMappingHandler is a template class that maps hosts to data:

public class HostMappingHandler<T> extends ChannelStateHandlerAdapter
{

    public static final AttributeKey<HostMappedObject> HOST_MAPPING =
        new AttributeKey<HostMappedObject>("HostMappingHandler.attr");


    private final ChannelGroup mChannels;
    private final Map<String, T> mMap;


    public HostMappingHandler(Map<String, T> registrations,
                              ChannelGroup channelGroup)
    {
        mChannels = channelGroup;
        mMap = registrations;
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception
    {
        SocketAddress addr = ctx.channel().remoteAddress();
        T mappedObj = null;

        if (addr instanceof InetSocketAddress)
        {
            String host = ((InetSocketAddress) addr).getHostName().toLowerCase();
            mappedObj = mMap.get(host);
        }

        if (mappedObj != null)
        {
            // Add the channel to the list so it can be easily removed if unregistered
            mChannels.add(ctx.channel());

            // Attach the host-mapped object
            ctx.attr(HOST_MAPPING).set(new HostMappedObject<T>(mappedObj));
        }
        else
        {
            log.debug("Bad host [" + addr + "]; aborting connection request");
            ctx.channel().close();
        }
        super.channelRegistered(ctx);
    }

    @Override
    public void inboundBufferUpdated(ChannelHandlerContext ctx) throws Exception
    {
        // Find the parser for this host. If the host is no longer registered,
        // disconnect the client.
        if (ctx.channel().remoteAddress() instanceof InetSocketAddress)
        {
            InetSocketAddress addr = (InetSocketAddress) ctx.channel().remoteAddress();
            T handler = mMap.get(addr.getHostName().toLowerCase());

            if (handler == null)
            {
                log.debug("Host no longer registered");
                ctx.channel().close();
            }
            else
            {
                log.debug("Sanity Check: " + ctx.attr(HostMappingHandler.HOST_MAPPING).get());
            }
        }
        super.inboundBufferUpdated(ctx);
    }

    // ==================================================

    public static class HostMappedObject<C>
    {
        private final C mObj;

        public HostMappedObject(final C in)
        {
            mObj = in;
        }

        public C get()
        {
            return mObj;
        }
    }
}

And the dispatcher is currently very simple:

private static class Dispatcher extends ChannelInboundMessageHandlerAdapter<Object>
{
    @Override
    public void messageReceived(final ChannelHandlerContext ctx,
                                final Object msg)
            throws Exception
    {
        HostMappingHandler.HostMappedObject wrapper =
                ctx.attr(HostMappingHandler.HOST_MAPPING).get();

        log.debug("Received: " + wrapper);
    }
}

Incidentally the initializer and dispatcher are private classes of a "server" object. However when I run a unit test that registers the localhost and attempts to connect to it, it fails and my debug output shows:

HostMappingHandler.inboundBufferUpdated: Sanity Check: test.comms.netty.HostMappingHandler$HostMappedObject@eb017e
Dispatcher.messageReceived: Received: null

So the mapping is definitely preserved between channel registration and receiving an inbound message in the HostMapper class, and investigating a little further in the debugger shows that the attribute map for the context in Dispatcher does have an entry - the problem is that the value is NULL and not the object.

Am I missing something obvious, or just an alpha bug?

Edit:

I've worked around the problem for now by attaching the data to the Dispatcher's context, by having the HostMappingHandler also take a class to attach to. The bug appears to be that setting an attribute in a handler's ChannelHandlerContext causes the attribute to appear in another handler with the correct key but NULL value. Without knowing the desired workflow, the bug is that either the key shouldn't appear at all, or the value should not be null.

public HostMappingHandler(final Map<String, T> registrations,
                          final ChannelGroup channelGroup,
                          final Class<? extends ChannelHandler> channelHandler)
{
    mChannels = channelGroup;
    mMap = registrations;
    mHandler = channelHandler;
}

//...

@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception
{
    //...
    // Attach the host-mapped object
     ctx.pipeline().context(mHandler).attr(HOST_MAPPING).set(new HostMappedObject<T>(mappedObj));
    //...
}
1

1 Answers

10
votes

AttributeMap in ChannelHandlerContext is bound to the context, and that's why the attribute you set in one context is not visible in other context.

Please note that Channel also implements AttributeMap. You can just use the attribute map bound to a channel:

ctx.channel().attr(...) ...;