Implementing Automatic Reconnection for Netty Client

One of the first requirement of Netty ISO8588 client connector is 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:

java
 1    public class ReconnectOnCloseListener implements ChannelFutureListener {
 2
 3        private final Logger logger = getLogger(ReconnectOnCloseListener.class);
 4
 5        private final Iso8583Client client;
 6        private final int reconnectInterval;
 7        private final AtomicBoolean disconnectRequested = new AtomicBoolean(false);
 8        private final ScheduledExecutorService executorService;
 9
10        public ReconnectOnCloseListener(Iso8583Client client, int reconnectInterval, ScheduledExecutorService executorService) {
11            this.client = client;
12            this.reconnectInterval = reconnectInterval;
13            this.executorService = executorService;
14        }
15
16        public void requestReconnect() {
17            disconnectRequested.set(false);
18        }
19
20        public void requestDisconnect() {
21            disconnectRequested.set(true);
22        }
23
24        @Override
25        public void operationComplete(ChannelFuture future) throws Exception {
26            final Channel channel = future.channel();
27            logger.debug("Client connection was closed to {}", channel.remoteAddress());
28            channel.disconnect();
29            scheduleReconnect();
30        }
31
32        public void scheduleReconnect() {
33            if (!disconnectRequested.get()) {
34                logger.trace("Failed to connect. Will try again in {} millis", reconnectInterval);
35                executorService.schedule(
36                        client::connectAsync,
37                        reconnectInterval, TimeUnit.MILLISECONDS);
38            }
39        }
40    }

To establish the connection I use the following code:

java
 1    reconnectOnCloseListener.requestReconnect();
 2    final ChannelFuture connectFuture = bootstrap.connect();
 3    connectFuture.addListener(connFuture -> {
 4        if (!connectFuture.isSuccess()) {
 5            reconnectOnCloseListener.scheduleReconnect();
 6            return;
 7        }
 8        Channel channel = connectFuture.channel();
 9        logger.info("Client is connected to {}", channel.remoteAddress());
10        setChannel(channel);
11        channel.closeFuture().addListener(reconnectOnCloseListener);
12    });
13    connectFuture.sync();// if you need to connect synchronously

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

java
1    reconnectOnCloseListener.requestDisconnect();
2    channel.close();

The solution works fine so far (integration test).

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

Konstantin Pavlov

Konstantin Pavlov

Software Engineer working with Java, Kotlin, Swift, and AI. Focusing on software architecture and building AI-infused apps. Passionate about testing and Open-Source projects.