Netty calls System.exit(n) to exit the application after a certain number of retries (2)

============================== System.exit() method ================= ==============

Prototype: System.exit(int status)

Its function is mainly to call Runtime.getRuntime().exit(status);

The function is to terminate the currently running Java virtual machine. This status indicates the exit status code, and non-zero indicates abnormal termination. (You can return a call return code to the caller of other processes to adopt different strategies according to the return code.)

Note: No matter what the status is, the program will exit. Compared with return, the difference is: return returns to the previous layer, while System.exit(status) returns to the top layer.

System. exit(0):

  • Exit normally, the program exits after normal execution, Java GC performs garbage collection, and exits directly.
  • In Swing development, it is generally used for the Swing form close button. (When rewriting the windowClosing method, call System.exit(0) to terminate the program. The dispose() method of the Window class just closes the window and does not allow the program to exit).

System. exit(1):

  • Is an abnormal exit, that is to say, the program exits regardless of whether it is executing or not.
  • If non-zero, this method will block indefinitely if the virtual machine has started a shutdown sequence after this method is called. If a shutdown hook is running, this method will block indefinitely. If off hooks run to completion, and finalizers are not called, start recycling completes if finalization-on-exit allows, and the virtual machine stops.
  • Generally, it will be used in the catch block (for example, when using Apache’s FTPClient class, it is recommended to use System.exit(1) in the source code to inform the connection failure), when the program will be called by the script, and the parent process calls abnormally, it needs to pass System.exit( 1) to inform that the operation failed. The default value returned by the program is 0. Even if an exception occurs, the default value is still 0. Therefore, in this case, it is necessary to manually specify the return value to be non-zero.

================================= Nettty Code ================= ===================

Netty client:

package org.jy.sso.websocket.stomp.push.netty.chat.system.chatcenter;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok. SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.time.DateFormatUtils;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chapter.handler.FirstClientSendMsgHandler;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chatcommond.ChatRetryCommand;
import org.jy.sso.websocket.stomp.push.netty.chat.system.constant.ChatClientConstant;

import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * IM chat system client
 * <p>
 * The tool mainly encapsulates the java.util.Random object and provides some methods for generating random numbers or random strings. The random tool class is RandomUtil, and the methods in it are all static methods.
 */
@Slf4j
public class ChatCenterClient {
    public static final ConcurrentHashMap<Byte,Bootstrap> bootStrapMap = new ConcurrentHashMap();
    @SneakyThrows
    public static void main(String[] args) {
        Bootstrap bootstrap = new Bootstrap();
        NioEventLoopGroup group = new NioEventLoopGroup();
        bootstrap. group(group)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<Channel>() {
                    @Override
                    protected void initChannel(Channel channel) {
                        // Add a logical processor on the client side, and write data to the server after the client successfully establishes a connection
                        channel.pipeline().addLast(new FirstClientSendMsgHandler());
                    }
                });
        connect(bootstrap, ChatClientConstant.CHAT_SERVER_HOST_IP, ChatClientConstant.CHAT_SERVER_PORT, ChatClientConstant.CHAT_MAX_RETRY_NUM);
        // The mapping relationship between the reconnection command and the instance started by the corresponding client
        bootStrapMap.put(ChatRetryCommand.CHAT_RETRY_CONNECTION, bootstrap);
    }

    /**
     * @param bootstrap client enabler
     * @param host host
     * @param port port
     * @param retryNum number of exponential backoff reconnections
     * @author yh19166
     * @deprecated connection and reconnection mechanism, realizing exponential backoff reconnection
     */
    private static void connect(Bootstrap bootstrap, String host, int port, int retryNum) {
        bootstrap.connect(host, port).addListener(future -> {
            // Use future.isSuccess() to determine whether the connection is successful
            if (future. isSuccess()) {
                System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + " -->> Connection succeeded....");
            } else if (retryNum == ChatClientConstant. CHAT_INI_RETRY_NUM) {
                System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + " -->> The number of reconnections has been exhausted, giving up the connection.... ");
                /**
                 * Prototype: System.exit(int status)
                 *
                 * Its function is mainly to call Runtime.getRuntime().exit(status);
                 *
                 * The function is to terminate the currently running Java virtual machine. This status indicates the exit status code, and non-zero indicates abnormal termination. (You can return a call return code to the caller of other processes to adopt different strategies according to the return code.)
                 *
                 * Note: No matter what the status is, the program will exit. Compared with return, the difference is: return returns to the previous layer, while System.exit(status) returns to the top layer.
                 *==================================================== ==================================================== ==================================================== ==
                 * System. exit(0):
                 *
                 * Normal exit, the program exits after normal execution, Java GC performs garbage collection, and exits directly.
                 * In Swing development, it is generally used for the Swing form close button. (When rewriting the windowClosing method, call System.exit(0) to terminate the program. The dispose() method of the Window class just closes the window and does not allow the program to exit).
                 * System. exit(1):
                 *
                 * Abnormal exit, that is to say, exit regardless of whether the program is executing or not.
                 * If non-zero, if this method is called, the virtual machine has started the shutdown sequence If the shutdown hook is running, this method will block indefinitely. If off hooks run to completion, and finalizers are not called, start recycling completes if finalization-on-exit allows, and the virtual machine stops.
                 * It is generally used in the catch block (for example, when using Apache's FTPClient class, it is recommended to use System.exit(1) in the source code to inform the connection failure), when the program will be called by the script and the parent process calls abnormally, it needs to pass System.exit (1) to inform that the operation failed. The default value returned by the program is 0. Even if an exception occurs, the default value is still 0. Therefore, in this case, it is necessary to manually specify the return value to be non-zero.
                 */
                System. exit(0);
            } else {
                // How many times to reconnect
                int order = (ChatClientConstant.CHAT_MAX_RETRY_NUM - retryNum) + ChatClientConstant.CHAT_STEP_RETRY_LENGTH;
                // The client considers the logic of reconnection, sub-gradient connection...
                System.out.println("No. " + order + " connection failed...");
                // Interval of this reconnection: Quickly calculate the reconnection time interval by shifting left
                int delay = ChatClientConstant. CHAT_STEP_RETRY_LENGTH << order;
                System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + " -->> connection failed, the first " + order + "restart connect....");
                // Implement timing task logic
                bootstrap.config().group().schedule(() -> connect(bootstrap, host, port, retryNum - ChatClientConstant.CHAT_STEP_RETRY_LENGTH), delay, TimeUnit.SECONDS);
            }
        });
    }

    /**
     * @param bootstrap client startup class
     * @param host host
     * @param port port
     * @param retryNum number of exponential backoff reconnects
     * @author yh19166
     * @deprecated connection and reconnection mechanism, realizing exponential backoff reconnection
     */
    @Deprecated
    public static void retryConnect(Bootstrap bootstrap, String host, int port, int retryNum) {
        bootstrap.connect(host, port).addListener(future -> {
            if (future. isSuccess()) {
                log.info("Connect to the server successfully!");
            } else if (retryNum == ChatClientConstant. CHAT_INI_RETRY_NUM) {
                log.error("The number of reconnections has been exhausted, give up the connection!");
                /**
                 * Prototype: System.exit(int status)
                 *
                 * Its function is mainly to call Runtime.getRuntime().exit(status);
                 *
                 * The function is to terminate the currently running Java virtual machine. This status indicates the exit status code, and non-zero indicates abnormal termination. (You can return a call return code to the caller of other processes to adopt different strategies according to the return code.)
                 *
                 * Note: No matter what the status is, the program will exit. Compared with return, the difference is: return returns to the previous layer, while System.exit(status) returns to the top layer.
                 *==================================================== ==================================================== ==================================================== ==
                 * System. exit(0):
                 *
                 * Normal exit, the program exits after normal execution, Java GC performs garbage collection, and exits directly.
                 * In Swing development, it is generally used for the Swing form close button. (When rewriting the windowClosing method, call System.exit(0) to terminate the program. The dispose() method of the Window class just closes the window and does not allow the program to exit).
                 * System. exit(1):
                 *
                 * Abnormal exit, that is to say, exit regardless of whether the program is executing or not.
                 * If non-zero, if this method is called, the virtual machine has started the shutdown sequence If the shutdown hook is running, this method will block indefinitely. If off hooks run to completion, and finalizers are not called, start recycling completes if finalization-on-exit allows, and the virtual machine stops.
                 * It is generally used in the catch block (for example, when using Apache's FTPClient class, it is recommended to use System.exit(1) in the source code to inform the connection failure), when the program will be called by the script and the parent process calls abnormally, it needs to pass System.exit (1) to inform that the operation failed. The default value returned by the program is 0. Even if an exception occurs, the default value is still 0. Therefore, in this case, it is necessary to manually specify the return value to be non-zero.
                 */
                System. exit(0);
            } else {
                // How many times to reconnect
                int order = (ChatClientConstant.CHAT_MAX_RETRY_NUM - retryNum) + ChatClientConstant.CHAT_STEP_RETRY_LENGTH;
                // The interval for this reconnection
                int delay = ChatClientConstant. CHAT_STEP_RETRY_LENGTH << order;
                log.error(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + ": Connection failed, reconnecting at " + order + ".... ");

                bootstrap.config().group().schedule(() -> connect(bootstrap, host, port, retryNum - ChatClientConstant.CHAT_STEP_RETRY_LENGTH), delay, TimeUnit.SECONDS);
            }
        });
    }
}

Constant classes used in the code:

package org.jy.sso.websocket.stomp.push.netty.chat.system.constant;

public class ChatClientConstant {

    // Host IP
    public static final String CHAT_SERVER_HOST_IP = "127.0.0.1";
    // port to connect to
    public static final int CHAT_SERVER_PORT = 8000;
    // maximum number of retries
    public static final int CHAT_MAX_RETRY_NUM = 4;
    // retry initial value
    public static final int CHAT_INI_RETRY_NUM = 0;
    // step size
    public static final int CHAT_STEP_RETRY_LENGTH = 1;
}

Client Handler:

package org.jy.sso.websocket.stomp.push.netty.chat.system.chapter.handler;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.time.DateFormatUtils;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chatcenter.ChatCenterClient;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chatcommond.ChatRetryCommand;
import org.jy.sso.websocket.stomp.push.netty.chat.system.constant.ChatClientConstant;

import java.nio.charset.StandardCharsets;
import java.util.Date;

/**
 * The client sends a message to the server. The first handler
 */
@Slf4j
public class FirstClientSendMsgHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        System.out.println("=================================================================================================================== ==================");
        System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + ": Client writes out data");
        // 1. Get data
        ByteBuf buffer = getByteBuf(ctx);
        // 2. Write data
        ctx.channel().writeAndFlush(buffer);
    }

    private ByteBuf getByteBuf(ChannelHandlerContext ctx) {
        // 1. Get the binary abstract ByteBuf
        ByteBuf buffer = ctx.alloc().buffer();
        // 2. Prepare the data to be passed to the server, and specify the character encoding as UTF-8
        byte[] bytes = "Hello, this is the data passed by the Netty client".getBytes(StandardCharsets.UTF_8);
        // 3. Fill data into ByteBuf
        buffer.writeBytes(bytes);
        return buffer;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        System.out.println("================================================================================================================== ===================");
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + " : the data passed by the client read by the server -> "
                 + byteBuf.toString(StandardCharsets.UTF_8)); // Remove this encoding and convert the result to:
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    /**
     * Program robustness considerations
     *
     * @param ctx channel processor context
     * @param cause The error message thrown
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // Exception caused by client not exiting normally: An existing connection was forcibly closed by the remote host
        // The server is closed suddenly, throwing this exception: The remote host forcibly closed an existing connection
        ctx.channel().flush();
        ctx. channel(). closeFuture();
        log.info("FirstClientSendMsgHandler.exceptionCaught : [{}]", cause.getMessage());
        Bootstrap Bootstrap = ChatCenterClient.bootStrapMap.get(ChatRetryCommand.CHAT_RETRY_CONNECTION);
        ChatCenterClient.retryConnect(Bootstrap, ChatClientConstant.CHAT_SERVER_HOST_IP, ChatClientConstant.CHAT_SERVER_PORT, ChatClientConstant.CHAT_MAX_RETRY_NUM);
    }
}

===================================== Netty server side ============== ==================

package org.jy.sso.websocket.stomp.push.netty.chat.system.chatcenter;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok. SneakyThrows;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chapter.handler.FirstServerReceiveDataHandler;

/**
 * IM server side
 */
public class ChatCenterServer {
    @SneakyThrows
    public static void main(String[] args) {
        // server-side startup class
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        // boss main thread (parent thread)
        NioEventLoopGroup bossLoopGroup = new NioEventLoopGroup();
        // worker thread (child thread)
        NioEventLoopGroup workerLoopGroup = new NioEventLoopGroup();
        serverBootstrap // Parent and child threads establish a group connection
                .group(bossLoopGroup, workerLoopGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) {
                        ch.pipeline().addLast(new FirstServerReceiveDataHandler());

                    }
                }).bind(8000).sync();
    }
}

The channel processor handler corresponding to the server-side Pipeline:

package org.jy.sso.websocket.stomp.push.netty.chat.system.chapter.handler;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateFormatUtils;

import java.nio.charset.StandardCharsets;
import java.util.Date;

/**
 * Processor for receiving client data on the server side:
 * The server side receives the client data
 * Respond to client requests and write data to the client
 */
@Slf4j
public class FirstServerReceiveDataHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object requestContent) {

        //Receive the data logic of the client
        System.out.println("============================== Receive client data =========== =====================");
        ByteBuf byteBuf = (ByteBuf) requestContent;
        System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + " : the data passed by the client read by the server -> "
                 + byteBuf.toString(StandardCharsets.UTF_8)); // Remove this encoding and convert the result to:
        System.out.println("==============================Write data to the client ========= =======================");
        ByteBuf response = getByteBuf(ctx);
        ctx.channel().writeAndFlush(response);


    }

    /**
     * Write response data to the client
     *
     * @param ctx channel processor context
     * @return {@link io.netty.buffer.ByteBuf}
     */
    private ByteBuf getByteBuf(ChannelHandlerContext ctx) {
        byte[] responseBytes = "Hello, welcome to pay attention to Brother Yang's WeChat official account, <<financial freedom cultivator>>!".getBytes(StandardCharsets.UTF_8);
        ByteBuf buffer = ctx.alloc().buffer();
        buffer.writeBytes(responseBytes);
        return buffer;
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // The client did not close normally, an exception was thrown: The host forcibly closed an existing connection
        ctx.channel().flush();
        ctx. channel(). closeFuture();
        log.info("exception: [{}]",cause.getMessage());
        // You can consider restarting the server, the difference between restarting|reconnecting
    }
}

The test results are getting worse:

======================== Run the client code first ============= Do not run the server code === ===================
20:04:33.435 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.tinyCacheSize: 512
20:04:33.435 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.smallCacheSize: 256
20:04:33.435 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.normalCacheSize: 64
20:04:33.435 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.maxCachedBufferCapacity: 32768
20:04:33.435 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.cacheTrimInterval: 8192
20:04:33.448 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled
20:04:33.448 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 65536
20:04:33.448 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384
1st connection failed...
2023-05-24 20:04:34 -->> Connection failed, reconnecting for the first time....
Connection 2 failed...
2023-05-24 20:04:37 -->> Connection failed, reconnecting for the second time....
3rd connection failed...
2023-05-24 20:04:42 -->> Connection failed, reconnected for the third time....
4th connection failed...
2023-05-24 20:04:51 -->> Connection failed, reconnecting for the 4th time....
2023-05-24 20:05:08 -->> The number of reconnections has been exhausted, giving up the connection....

Process finished with exit code 0
=============================== Client output log ================= =========================
====== Run the server-side code first, then run the client-side code, after the client connects to the server, then close the server-side ===============
20:27:31.730 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.normalCacheSize: 64
20:27:31.730 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.maxCachedBufferCapacity: 32768
20:27:31.730 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.cacheTrimInterval: 8192
20:27:31.748 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled
20:27:31.748 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 65536
20:27:31.748 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384
2023-05-24 20:27:31 -->> Connection succeeded....
============================ Write data to the server ====================== ========
2023-05-24 20:27:31: Client writes data
20:27:31.801 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxCapacityPerThread: 32768
20:27:31.801 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxSharedCapacityFactor: 2
20:27:31.801 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.linkCapacity: 16
20:27:31.809 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.ratio: 8
20:27:31.841 [nioEventLoopGroup-2-1] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.bytebuf.checkAccessible: true
20:27:31.841 [nioEventLoopGroup-2-1] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@5f1d79
====================================================================================================================== =========
2023-05-24 20:27:31 : The data read by the server and transmitted by the client -> Hello, welcome to pay attention to Brother Yang’s WeChat official account, <<Financial Freedom Cultivator>>!
20:27:37.561 [nioEventLoopGroup-2-1] INFO org.jy.sso.websocket.stomp.push.netty.chat.system.chapter.handler.FirstClientSendMsgHandler - FirstClientSendMsgHandler.exceptionCaught: [The remote host forcibly closed an existing Connection. ]
20:27:38.569 [nioEventLoopGroup-2-2] ERROR org.jy.sso.websocket.stomp.push.netty.chat.system.chatcenter.ChatCenterClient - 2023-05-24 20:27:38: Connection failed, page 1 reconnect....
Connection 2 failed...
2023-05-24 20:27:41 -->> Connection failed, reconnecting for the second time....
3rd connection failed...
2023-05-24 20:27:46 -->> Connection failed, reconnected for the third time....
4th connection failed...
2023-05-24 20:27:55 -->> Connection failed, reconnecting for the 4th time....
2023-05-24 20:28:12 -->> The number of reconnections has been exhausted, giving up the connection....

Process finished with exit code 0
================================== Client output log =============== ==================