0

I tried finding a question that covered my problem, but all I could find were lots of similar questions, but no answer that solved my problem.

I am creating a java webapp for Tomcat, that, between other things, must also act as a TCP server that handles multiple incoming connections: to do so, I run the TCP server code in a separate thread, which in turn uses a ExecutorService to create threads for each connection. The problem basically is that, when I stop Tomcat, the server thread never gets stopped (even if no one has connected yet) and hangs Tomcat, until I close the related process in the Task Manager.

So, this is the starting point of the program:

private TCPServer tcpServer;
Thread serverThread;

@Override
public void init() throws ServletException {            
    DBConn = getDBConn();
    if (DBConn != null) {
        //initiates loggers, reads configurations from a file, etc

        //starts TCP server
        tcpServer = new TCPServer(50001, 200);    
        serverThread = new Thread(tcpServer);
        serverThread.start();    

        //will be doing other stuff
    } else {
        //handles DB connection failure
    }
}

This is the TCPServer class:

public class TCPServer implements Runnable {

   private final int listeningPort;
   PrintWriter out;
   BufferedReader in;
   ServerSocket serverSocket;
   private final ExecutorService pool;

   public TCPServer(int port, int poolSize) {
       listeningPort = port;
       pool = Executors.newFixedThreadPool(poolSize);
   }    

   @Override
   public void run() {
       LinkedBlockingQueue<Socket> queue = new LinkedBlockingQueue<>();
       try {
           serverSocket = new ServerSocket(listeningPort);

           while (!Thread.currentThread().isInterrupted()) {
               pool.execute(new ConnectionHandler(queue));
               queue.put(serverSocket.accept());               
           }
       } catch (IOException | InterruptedException ex) {
           Logger.getLogger(TCPServer.class.getName()).log(Level.SEVERE, null, ex);
       }
   }

   private class ConnectionHandler implements Runnable {
       private LinkedBlockingQueue<Socket> socketQueue;
       public ConnectionHandler(LinkedBlockingQueue<Socket> queue) {
           this.socketQueue = queue;
       }

       @Override
       public void run() {
           while (!Thread.interrupted() || !Thread.currentThread().isInterrupted()) {
               try (Socket clientSocket = socketQueue.take()) {
                   in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                   String line = in.readLine();
                   System.out.println("Incoming: " + line);

                   clientSocket.close();
               } catch (InterruptedException ex) {     
              Logger.getLogger(TCPServer.class.getName()).log(Level.SEVERE,    null, ex);
               } catch (IOException ex) {   
                Logger.getLogger(TCPServer.class.getName()).log(Level.SEVERE, null, ex);
               }  
           }
       }


   public void close(){
       Thread.currentThread().interrupt();
       try {
           serverSocket.close();
       } catch (IOException ex) {  
                Logger.getLogger(TCPServer.class.getName()).log(Level.SEVERE, null, ex);           }
       pool.shutdownNow();
       }
}

I added, in the main class (the same where the init() above is), this override, that runs when Tomcat gets closed:

@Override
public void contextDestroyed(ServletContextEvent sce) {
    serverThread.interrupt();
    tcpServer.close();
    ServletContextListener.super.contextDestroyed(sce); 
}

I made so many edits trying to implement solutions that I found around on the internet, that surely some of this code is probably redundant or useless. Can someone give me pointers on what I should do to correctly stop the TCPServer thread?

il_boga
  • 1,305
  • 1
  • 13
  • 21
  • 1
    It's not very clever to put a TCP server inside Tomcat, which already *is* a TCP server. Have you made sure you've shut down the executor pool? Also it's very annoying to try to debug code that's the result of "I tried pasting everything on the internet and nothing works and I don't know what I'm doing". – Kayaman Mar 21 '17 at 10:32
  • You are right, but I got pretty desperate this morning. Anyways, your comment kind of gave me strength, and I started almost from scratch and cleaned up the code...now it seems to be working. Do you think it's worth posting the working code? – il_boga Mar 21 '17 at 12:51
  • 1
    Sure, post it as an answer and you can then accept it and this can be considered a "closed" question. – Kayaman Mar 21 '17 at 12:58

1 Answers1

0

Thanks to this answer and Kayaman's comment, I revised the whole code and somehow got it working.

The init() override is the same, while the contextDestroyed override now is

@Override
public void contextDestroyed(ServletContextEvent sce) {        
    tcpServer.close();
    serverThread.interrupt();
    ServletContextListener.super.contextDestroyed(sce);
}

The TCPServer class now is:

public class TCPServer implements Runnable {
   private final int listeningPort;
   PrintWriter out;
   BufferedReader in;
   ServerSocket serverSocket;
   private final ExecutorService pool;

   public TCPServer(int port, int poolSize) {
       listeningPort = port;
       pool = Executors.newFixedThreadPool(poolSize);
   }    

   @Override
   public void run() {
       try {
           serverSocket = new ServerSocket(listeningPort);

           while (!Thread.currentThread().isInterrupted()) {    
               Socket clientSocket = serverSocket.accept();
               pool.submit(new ConnectionHandler(clientSocket)); 
           }

       } catch (IOException ex) {
           Logger.getLogger(TCPServer.class.getName()).log(Level.SEVERE, null, ex);
       } finally {
           pool.shutdown();
       }
   }

   private class ConnectionHandler implements Runnable {
        private Socket sock;

       public ConnectionHandler(Socket sock){
           this.sock = sock;
       }

       @Override
       public void run() {
           while (!Thread.currentThread().isInterrupted()) {
               try{
                   in = new BufferedReader(new InputStreamReader(sock.getInputStream()));
                   String line = in.readLine();
                   System.out.println("Incoming: " + line);

                   sock.close();

                  } catch (IOException ex) { 
                      Logger.getLogger(TCPServer.class.getName()).log(Level.SEVERE, null, ex);
                  }   
              }
          }
      }

   public void close(){        
       try {
           serverSocket.close();
       } catch (IOException ex) {
           Logger.getLogger(TCPServer.class.getName()).log(Level.SEVERE, null, ex);
       }
   }
}
Community
  • 1
  • 1
il_boga
  • 1,305
  • 1
  • 13
  • 21