Implementing Automatic Reconnection for Netty Client
Table of Contents
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.
Links #
- Sources: ReconnectListener, Client
- StackOverflow: answer one, answer two