1

I am working on 2 applications (web and standalone apps). I have built functionality into the web app for users to be able to restart certain modules of the standalone app from the web page. The way i accomplish this is using a ServerSocket object that listens on a port that is configured as a parameter in the database. This is a shortened version of the server side that listens for the incoming requests :

try
    {
        int port = Integer.parseInt(globalParamService.findByName("serviceInterconnectPort").getValue());
        ServerSocket serverSocket = new ServerSocket(port);
        logEntryService.logInfo(LogEntry.CONNECTIVITY, "Successfully started web client connector on port " + port);
        while(running)
        {
            socket = serverSocket.accept();
            logEntryService.logInfo(LogEntry.CONNECTIVITY, "Incoming request from web client");
            InputStream is = socket.getInputStream();
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);
            String message = br.readLine();
            if (message.contains("Restart Web Client Connector"))
            {
                if (!main.isWebClientConnectorRestarting())
                {
                    main.restartWebClientConnector();
                    String returnMessage = "Done\n";
                    OutputStream os = socket.getOutputStream();
                    OutputStreamWriter osw = new OutputStreamWriter(os);
                    BufferedWriter bw = new BufferedWriter(osw);
                    bw.write(returnMessage);
                    bw.flush();

                    os.close();
                    osw.close();
                    bw.close();
                }
                else
                {
                    String returnMessage = "Request cancelled\n";
                    OutputStream os = socket.getOutputStream();
                    OutputStreamWriter osw = new OutputStreamWriter(os);
                    BufferedWriter bw = new BufferedWriter(osw);
                    bw.write(returnMessage);
                    bw.flush();

                    os.close();
                    osw.close();
                    bw.close();
                    logEntryService.logWarning(LogEntry.CONNECTIVITY, "Web client connector restart request cancelled, restart already in progress");
                }
            }

            is.close();
            isr.close();
            br.close();
            socket.close();
        }
    }
    catch (Exception ex)
    {
        logEntryService.logError(LogEntry.CONNECTIVITY, "Error processing restart request from web client : " + ex.getMessage());
    }

Upon deployment of my 2 apps, it is possible that the users would need to change the port this listener is running on. When they do change it from the web app, i extract the unchanged port before updating it in the DB and send it to the below method :

public void restartWebClientConnector(int oldPort)
{
    Thread t = new Thread(() -> 
    {
        try
        {
            logEntryService.logInfo(LogEntry.CONNECTIVITY, "Connecting to port " + oldPort + " to restart web client connector");
            InetAddress address = InetAddress.getByName("localhost");
            socket = new Socket(address, oldPort);
            logEntryService.logDebug(LogEntry.CONNECTIVITY, "Successfully connected to port " + oldPort);
            OutputStream os = socket.getOutputStream();
            OutputStreamWriter osw = new OutputStreamWriter(os);
            BufferedWriter bw = new BufferedWriter(osw);
            bw.write("Restart Web Client Connector\n");
            bw.flush();
            InputStream is = socket.getInputStream();
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);
            String message = br.readLine();
            if (message.compareTo("Done") == 0)
            {
                logEntryService.logInfo(LogEntry.CONNECTIVITY, "Web client connector restart request acknowledged");
            }
            else
            {
                logEntryService.logWarning(LogEntry.CONNECTIVITY, "Web client connector restart request cancelled, restart already in progress");
            }

            os.close();
            osw.close();
            bw.close();
            is.close();
            isr.close();
            br.close();
            socket.close();
        }
        catch (IOException | NumberFormatException ex)
        {
            logEntryService.logError(LogEntry.CONNECTIVITY, "Error sending web client connector restart command : " + ex.getMessage());
        }
    });
    t.start();
}

This method then calls the following code which terminates my listener thread and re initializes it on the new updated port number :

public void restartWebClientConnector()
{
    if (!webClientConnectorRestarting)
    {
        webClientConnectorRestarting = true;
        webClientConnector.setRunning(false);
        webClientConnectorThread.interrupt();
        initWebClientConnector();
        logEntryService.logInfo(LogEntry.CONNECTIVITY, "Successfully restarted web client connector");
        webClientConnectorRestarting = false;
    }
}

private void initWebClientConnector()
{
    logEntryService.logInfo(LogEntry.CORE, "Initializing web connector"); 

    try
    {
        webClientConnector = new WebClientConnector(this, globalParamService, logEntryService);
        webClientConnectorThread = new Thread(threads, webClientConnector);
        webClientConnectorThread.setName("Web Client Connector Thread");
        webClientConnectorThread.start();
    }
    catch (Exception ex)
    {
        logEntryService.logError(LogEntry.CORE, "Error initializing messaging process : " + ex.getMessage()); 
    }
}

Everything works great except one thing : the old port number is NOT freed up despite explicitly closing everything i can think of. When doing netstat -a after changing the port from the web client, the old port is still listed in LISTENING status. I can change it several times in a row and it works every time but the ports are not freed up. I have spent quite some time researching this and from what i've read it appears i am doing everything correctly (evidently not!).

Whatever input you guys may have would be helpful.

Cheers!

Martin
  • 1,977
  • 5
  • 30
  • 67
  • i read my code carefully again and i figured that interrupting the thread might be causing it to not actually run the socket.close() method. I removed that line to let the thread terminate naturally by just setting the running variable to false but even that doesn't free up the port. – Martin Jan 22 '18 at 13:13

2 Answers2

0

I would recommend you to use resource try blocks, or at least try-finally blocks to ensure the resources are closed:

    int port = Integer.parseInt(globalParamService.findByName("serviceInterconnectPort").getValue());
    try (ServerSocket serverSocket = new ServerSocket(port)) {
        logEntryService.logInfo(LogEntry.CONNECTIVITY, "Successfully started web client connector on port " + port);
        while (running) {
            try (Socket socket = serverSocket.accept();
                    InputStream is = socket.getInputStream();
                    InputStreamReader isr = new InputStreamReader(is);
                    BufferedReader br = new BufferedReader(isr)) {
                String message = br.readLine();
                if (message.contains("Restart Web Client Connector")) {
                    if (!main.isWebClientConnectorRestarting()) {
                        main.restartWebClientConnector();
                        String returnMessage = "Done\n";
                        try (OutputStream os = socket.getOutputStream();
                                OutputStreamWriter osw = new OutputStreamWriter(os);
                                BufferedWriter bw = new BufferedWriter(osw)) {
                            bw.write(returnMessage);
                            bw.flush();
                        }
                    } else {
                        String returnMessage = "Request cancelled\n";
                        try (OutputStream os = socket.getOutputStream();
                                OutputStreamWriter osw = new OutputStreamWriter(os);
                                BufferedWriter bw = new BufferedWriter(osw)) {
                            bw.write(returnMessage);
                            bw.flush();
                        }
                        logEntryService.logWarning(LogEntry.CONNECTIVITY, "Web client connector restart request cancelled, restart already in progress");
                    }
                }
            }
        }
    } catch (IOException ex) {
        logEntryService.logError(LogEntry.CONNECTIVITY, "Error processing restart request from web client : " + ex.getMessage());
    }
Maurice Perry
  • 9,261
  • 2
  • 12
  • 24
  • Thank you for the suggestion! One thing i just thought of though is that when you call accept() on a server socket it will just halt at that line until it receives a connection right? If that is the case, setting running to false actually doesn't do anything for this code since it's stuck on this line waiting for a client connection. Am i understanding this correctly? – Martin Jan 22 '18 at 13:38
  • Thanks Maurice, that is more than likely my issue then. I will need to figure out a way to allow the server socket to listen for an incoming request, reply with the acknowledgement message while simultaneously and only then PROPERLY terminate itself. As i understand it though, in this case even a finally block would not execute the socket.close method since the execution remains jammed at the socket.accept() line after the acknowledgement message has been sent back to the web app. Looks like i will need to use some sort of Thread interrupt logic to close the socket when i stop it. – Martin Jan 22 '18 at 13:47
0

I changed my WebClientConnector class' run method (it implements Runnable) to the below code :

@Override
public void run() 
{
    try
    {
        initWebClientListener();
    }
    finally
    {
        try 
        {
            serverSocket.close();
        } 
        catch (IOException ex1) 
        {}
    }
}

And changed my restart method to call interrupt on it as well as setting the running variable to false. The ServerSocket now closes properly and the port is freed.

Martin
  • 1,977
  • 5
  • 30
  • 67
  • Also, you'll notice i was calling close on the Socket object as opposed to the ServerSocket object, that certainly didn't help! – Martin Jan 22 '18 at 14:22