All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.cluster.com.NetworkReceiver Maven / Gradle / Ivy

Go to download

Library implementing Paxos and Heartbeat components required for High Availability Neo4j

The newest version!
/*
 * Copyright (c) 2018-2020 "Graph Foundation"
 * Graph Foundation, Inc. [https://graphfoundation.org]
 *
 * Copyright (c) 2002-2018 "Neo4j,"
 * Neo4j Sweden AB [http://neo4j.com]
 *
 * This file is part of ONgDB Enterprise Edition. The included source
 * code can be redistributed and/or modified under the terms of the
 * GNU AFFERO GENERAL PUBLIC LICENSE Version 3
 * (http://www.fsf.org/licensing/licenses/agpl-3.0.html) as found
 * in the associated LICENSE.txt file.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 */
package org.neo4j.cluster.com;

import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelException;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.group.DefaultChannelGroup;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.handler.codec.serialization.ObjectDecoder;
import org.jboss.netty.util.ThreadNameDeterminer;
import org.jboss.netty.util.ThreadRenamingRunnable;

import java.net.ConnectException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;

import org.neo4j.cluster.com.message.Message;
import org.neo4j.cluster.com.message.MessageProcessor;
import org.neo4j.cluster.com.message.MessageSource;
import org.neo4j.helpers.HostnamePort;
import org.neo4j.helpers.Listeners;
import org.neo4j.helpers.NamedThreadFactory;
import org.neo4j.kernel.lifecycle.Lifecycle;
import org.neo4j.logging.Log;
import org.neo4j.logging.LogProvider;

import static org.neo4j.helpers.NamedThreadFactory.daemon;

/**
 * TCP version of a Networked Instance. This handles receiving messages to be consumed by local state-machines and
 * sending outgoing messages
 */
public class NetworkReceiver
        implements MessageSource, Lifecycle
{
    public interface Monitor
        extends NamedThreadFactory.Monitor
    {
        void receivedMessage( Message message );

        void processedMessage( Message message );
    }

    public interface Configuration
    {
        HostnamePort clusterServer();

        int defaultPort();

        String name(); // Name of this cluster instance. Null in most cases, but tools may use e.g. "Backup"
    }

    public interface NetworkChannelsListener
    {
        void listeningAt( URI me );

        void channelOpened( URI to );

        void channelClosed( URI to );
    }

    public static final String CLUSTER_SCHEME = "cluster";
    public static final String INADDR_ANY = "0.0.0.0";

    private ChannelGroup channels;

    // Receiving
    private NioServerSocketChannelFactory nioChannelFactory;
    private ServerBootstrap serverBootstrap;
    private final Listeners processors = new Listeners<>();

    private final Monitor monitor;
    private final Configuration config;
    private final Log msgLog;

    private final Map connections = new ConcurrentHashMap<>();
    private final Listeners listeners = new Listeners<>();

    volatile boolean bindingDetected;

    private volatile boolean paused;
    private int port;

    public NetworkReceiver( Monitor monitor, Configuration config, LogProvider logProvider )
    {
        this.monitor = monitor;
        this.config = config;
        this.msgLog = logProvider.getLog( getClass() );
    }

    @Override
    public void init()
    {
        ThreadRenamingRunnable.setThreadNameDeterminer( ThreadNameDeterminer.CURRENT );
    }

    @Override
    public void start()
    {
        channels = new DefaultChannelGroup();

        // Listen for incoming connections
        nioChannelFactory = new NioServerSocketChannelFactory(
                Executors.newCachedThreadPool( daemon( "Cluster boss", monitor ) ),
                Executors.newFixedThreadPool( 2, daemon( "Cluster worker", monitor ) ), 2 );
        serverBootstrap = new ServerBootstrap( nioChannelFactory );
        serverBootstrap.setOption( "child.tcpNoDelay", Boolean.TRUE );
        serverBootstrap.setPipelineFactory( new NetworkNodePipelineFactory() );

        int[] ports = config.clusterServer().getPorts();

        int minPort = ports[0];
        int maxPort = ports.length == 2 ? ports[1] : minPort;

        // Try all ports in the given range
        port = listen( minPort, maxPort );

        msgLog.debug( "Started NetworkReceiver at " + config.clusterServer().getHost() + ":" + port );
    }

    @Override
    public void stop()
            throws Throwable
    {
        msgLog.debug( "Shutting down NetworkReceiver at " + config.clusterServer().getHost() + ":" + port );

        channels.close().awaitUninterruptibly();
        serverBootstrap.releaseExternalResources();
        msgLog.debug( "Shutting down NetworkReceiver complete" );
    }

    @Override
    public void shutdown()
    {
    }

    public void setPaused( boolean paused )
    {
        this.paused = paused;
    }

    private int listen( int minPort, int maxPort )
            throws ChannelException
    {
        ChannelException ex = null;
        for ( int checkPort = minPort; checkPort <= maxPort; checkPort++ )
        {
            try
            {
                String address = config.clusterServer().getHost();
                InetSocketAddress localAddress;
                if ( address == null || address.equals( INADDR_ANY ) )
                {
                    localAddress = new InetSocketAddress( checkPort );
                }
                else
                {
                    localAddress = new InetSocketAddress( address, checkPort );
                    bindingDetected = true;
                }

                Channel listenChannel = serverBootstrap.bind( localAddress );

                listeningAt( getURI( localAddress ) );

                channels.add( listenChannel );
                return checkPort;
            }
            catch ( ChannelException e )
            {
                ex = e;
            }
        }

        nioChannelFactory.releaseExternalResources();
        throw ex;
    }

    // MessageSource implementation
    @Override
    public void addMessageProcessor( MessageProcessor processor )
    {
        processors.add( processor );
    }

    public void receive( Message message )
    {
        if ( !paused )
        {
            for ( MessageProcessor processor : processors )
            {
                try
                {
                    if ( !processor.process( message ) )
                    {
                        break;
                    }
                }
                catch ( Exception e )
                {
                    // Ignore
                }
            }

            monitor.processedMessage( message );
        }
    }

    URI getURI( InetSocketAddress socketAddress )
    {
        String uri;

        InetAddress address = socketAddress.getAddress();

        if ( address instanceof Inet6Address )
        {
            uri = CLUSTER_SCHEME + "://" + wrapAddressForIPv6Uri( address.getHostAddress() ) + ":" + socketAddress.getPort();
        }
        else if ( address instanceof Inet4Address )
        {
            uri = CLUSTER_SCHEME + "://" + address.getHostAddress() + ":" + socketAddress.getPort();
        }
        else
        {
            throw new IllegalArgumentException( "Address type unknown" );
        }

        // Add name if given
        if ( config.name() != null )
        {
            uri += "/?name=" + config.name();
        }

        return URI.create( uri );
    }

    public void listeningAt( URI me )
    {
        listeners.notify( listener -> listener.listeningAt( me ) );
    }

    protected void openedChannel( URI uri, Channel ctxChannel )
    {
        connections.put( uri, ctxChannel );

        listeners.notify( listener -> listener.channelOpened( uri ) );
    }

    protected void closedChannel( URI uri )
    {
        Channel channel = connections.remove( uri );
        if ( channel != null )
        {
            channel.close();
        }

        listeners.notify( listener -> listener.channelClosed( uri ) );
    }

    public void addNetworkChannelsListener( NetworkChannelsListener listener )
    {
        listeners.add( listener );
    }

    private class NetworkNodePipelineFactory
            implements ChannelPipelineFactory
    {
        @Override
        public ChannelPipeline getPipeline()
        {
            ChannelPipeline pipeline = Channels.pipeline();
            pipeline.addLast( "frameDecoder",
                    new ObjectDecoder( 1024 * 1000,
                            NetworkNodePipelineFactory.this.getClass().getClassLoader() ) );
            pipeline.addLast( "serverHandler", new MessageReceiver() );
            return pipeline;
        }
    }

    class MessageReceiver
            extends SimpleChannelHandler
    {
        @Override
        public void channelOpen( ChannelHandlerContext ctx, ChannelStateEvent e )
        {
            Channel ctxChannel = ctx.getChannel();
            openedChannel( getURI( (InetSocketAddress) ctxChannel.getRemoteAddress() ), ctxChannel );
            channels.add( ctxChannel );
        }

        @Override
        public void messageReceived( ChannelHandlerContext ctx, MessageEvent event )
        {
            if ( !bindingDetected )
            {
                InetSocketAddress local = (InetSocketAddress) event.getChannel().getLocalAddress();
                bindingDetected = true;
                listeningAt( getURI( local ) );
            }

            final Message message = (Message) event.getMessage();

            // Fix HEADER_FROM header since sender cannot know it's correct IP/hostname
            InetSocketAddress remote = (InetSocketAddress) ctx.getChannel().getRemoteAddress();
            String remoteAddress = remote.getAddress().getHostAddress();
            URI fromHeader = URI.create( message.getHeader( Message.HEADER_FROM ) );
            if ( remote.getAddress() instanceof Inet6Address )
            {
                remoteAddress = wrapAddressForIPv6Uri( remoteAddress );
            }
            fromHeader = URI.create( fromHeader.getScheme() + "://" + remoteAddress + ":" + fromHeader.getPort() );
            message.setHeader( Message.HEADER_FROM, fromHeader.toASCIIString() );

            msgLog.debug( "Received:" + message );
            monitor.receivedMessage( message );
            receive( message );
        }

        @Override
        public void channelDisconnected( ChannelHandlerContext ctx, ChannelStateEvent e )
        {
            closedChannel( getURI( (InetSocketAddress) ctx.getChannel().getRemoteAddress() ) );
        }

        @Override
        public void channelClosed( ChannelHandlerContext ctx, ChannelStateEvent e )
        {
            closedChannel( getURI( (InetSocketAddress) ctx.getChannel().getRemoteAddress() ) );
            channels.remove( ctx.getChannel() );
        }

        @Override
        public void exceptionCaught( ChannelHandlerContext ctx, ExceptionEvent e )
        {
            if ( !(e.getCause() instanceof ConnectException) )
            {
                msgLog.error( "Receive exception:", e.getCause() );
            }
        }
    }

    private static String wrapAddressForIPv6Uri( String address )
    {
        return "[" + address + "]";
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy