end an infinite loop (accepting incoming connections) from one of the running threads

Short: In Java, how can I end an infinite loop (accepting incoming connections) from one of the running threads?

Long: I’m using this example as basis for my code.

I have an ExecutorService which manages a pool of threads and accepts incoming connections in the main loop. The stop condition of the main loop is whether the service has been shutdown or not via isShutdown().

My case: When one thread receives a “bye” sequence, then all the threads have to stop as gracefully as possible and the main infinite loop has to quit. However, so far I haven’t managed to stop the loop, which runs under the isShutdown() condition.

There are many Q&A about hot to kill threads with ExecutorService but I haven’t found any tackling the issue of an infinite loop with a condition. I’ve tried:

  • Interruptions: I can interrupt the generated threads but the infinite loop condition will still be running and enqueuing new threads.
  • Volatile variables: they are handy to coordinate running threads but I have the same issue as above.
  • Passing the ExecutorService as paramether to the thread: then it was just calling .shutdown(), .shutdownNow(), awaitTermination() from the thread receiving the “bye”, but because how Java passes objects as parameters, I was not changing the global state to break the main loop and it was still running.

I don’t know whether I’m sticking too much to the isShutdown() condition and if I should be using another approach to solve this problem.

This is the example slightly modified to check if the message is “bye”. I’m using telnet as client to test it.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ExecutorHttpd {
  ExecutorService executor = Executors.newFixedThreadPool(3);

  public void start(int port) throws IOException {
    final ServerSocket ss = new ServerSocket(port);
    while (!executor.isShutdown())
      executor.submit(new TinyHttpdConnection(ss.accept()));
    ss.close();
  }

  public void shutdown() throws InterruptedException {
    executor.shutdown();
    executor.awaitTermination(30, TimeUnit.SECONDS);
    executor.shutdownNow();
  }

  public static void main(String argv[]) throws Exception {
    if(argv.length != 1) {
      System.out.println("Wrong number of arguments");
      System.out.println("tUsage: ExecutorHttpd PORT_NUMBER");
      return;
    }
    int port = Integer.parseInt(argv[0]);
    new ExecutorHttpd().start(port);
  }
}

class TinyHttpdConnection implements Runnable {
  Socket client;

  TinyHttpdConnection(Socket client) throws SocketException {
    this.client = client;
  }

  public void run() {
    try {
      BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
      OutputStream out = client.getOutputStream();
      String request = in.readLine();
      System.out.println("Request: " + request);
      if(request.equals("bye")) {
          System.out.println("End all threads");
      }

      byte[] data = "hello".getBytes();
      out.write(data, 0, data.length);
      out.flush();
      client.close();
    } catch (IOException e) {
      System.out.println("I/O error " + e);
    }
  }
}

Answer

shutting down an executor doesn’t do anything to the currently running jobs, it just affects anything that is listening for this event, ‘passively’ queries for it, and it affects how that executor pool is handling its job systems (e.g. offering any new jobs to it won’t work, and any enqueued jobs that haven’t been started yet never will).

The crucial blocker here is serverSocket.accept().

You want to stop that one from running.

The javadoc is quite clear: Invoke close() on the serversocket and voila, the accept method will stop waiting (by throwing a SocketException, to be precise).

So, when you want to shut it all down, invoke .close() on the serversocket. Make sure to handle the socketexception that ensues which should involve checking what kind of SocketException you got. You want the one that is caused by explicitly close()ing the ServerSocket whilst actively waiting for a new socket via .accept() to be ignored (as you know where it is from, and it was intentional), but not any other SocketExceptions. Silently ignoring exceptions without knowing exactly why they were caused is a very bad idea.