One of the first requirement of Netty ISO8588 client connector was the support for automatic reconnect.

One of the first receipts I came across was Thomas Termin’s one. He suggests adding a ChannelHandler which will schedule the calling of client’s connect() method once a Channel becomes inactive. Plus adding ChannelFutureListener which will re-create a bootstrap and re-connect if initial connection was failed.

Although this is a working solution, I had a feeling that something is not optimal. Namely, the new Bootstrap is being created on every connection attempt.

So, I created a FutureListener which should be registered once a Channel is closed.

Here is the ReconnectOnCloseListener code:

    public class ReconnectOnCloseListener implements ChannelFutureListener {

        private final Logger logger = getLogger(ReconnectOnCloseListener.class);

        private final Iso8583Client client;
        private final int reconnectInterval;
        private final AtomicBoolean disconnectRequested = new AtomicBoolean(false);
        private final ScheduledExecutorService executorService;

        public ReconnectOnCloseListener(Iso8583Client client, int reconnectInterval, ScheduledExecutorService executorService) {
            this.client = client;
            this.reconnectInterval = reconnectInterval;
            this.executorService = executorService;
        }

        public void requestReconnect() {
            disconnectRequested.set(false);
        }

        public void requestDisconnect() {
            disconnectRequested.set(true);
        }

        @Override
        public void operationComplete(ChannelFuture future) throws Exception {
            final Channel channel = future.channel();
            logger.debug("Client connection was closed to {}", channel.remoteAddress());
            channel.disconnect();
            scheduleReconnect();
        }

        public void scheduleReconnect() {
            if (!disconnectRequested.get()) {
                logger.trace("Failed to connect. Will try again in {} millis", reconnectInterval);
                executorService.schedule(
                        client::connectAsync,
                        reconnectInterval, TimeUnit.MILLISECONDS);
            }
        }
    }

To establish the connection I use the following code:

    reconnectOnCloseListener.requestReconnect();
    final ChannelFuture connectFuture = bootstrap.connect();
    connectFuture.addListener(connFuture -> {
        if (!connectFuture.isSuccess()) {
            reconnectOnCloseListener.scheduleReconnect();
            return;
        }
        Channel channel = connectFuture.channel();
        logger.info("Client is connected to {}", channel.remoteAddress());
        setChannel(channel);
        channel.closeFuture().addListener(reconnectOnCloseListener);
    });
    connectFuture.sync();// if you need to connect synchronously

When you want to disconnect, you’ll need to disable automatic reconnection first:

    reconnectOnCloseListener.requestDisconnect();
    channel.close();

The solution works fine so far (integration test).

Another option is to add a ChannelOutboundHandler which will handle disconnects.