3

Let's say I have a third-party Java library called in a Task submitted to ExecutorService.

I trust the third-party library to not be malicious, but there is a rare chance that there are programming errors that can cause it to get stuck in an infinite loop, and if this is the case, I cannot fix it to address those rare occasions.

What is the best way to handle this so that the application doesn't get stuck as well? Is shutdownNow() good enough to handle this situation?

There's a related issue Stop an infinite loop in an ExecutorService task but this relies on the ability of the programmer to be cooperative and detect Thread.currentThread().isInterrupted() to stop processing, which I can't rely on.

(In my case it's Jython code; in an early version of Jython the interpreter apparently didn't check Thread.currentThread().isInterrupted(), not sure what it does now... but my question is general for any 3rd-party Java code.)

Community
  • 1
  • 1
Jason S
  • 184,598
  • 164
  • 608
  • 970
  • hmmm.. may be a duplicate of http://stackoverflow.com/questions/10853305/how-to-stop-long-duration-execution-task-e-g-infinite-loop-execution-inside but I'm not sure. – Jason S Feb 08 '17 at 16:15
  • "put in a loop checking state while interrupting" - I can't, if such a loop exists, it is in third-party code. – Jason S Feb 08 '17 at 16:32
  • 1
    @efekctive That makes little difference - if you wrap the method in a runnable: `run() { run3rdParty(); }` and the `run3rdParty` never returns, there isn't much you can do: if you insert an interruption check, it will not be executed. – assylias Feb 08 '17 at 17:00
  • Deleted my stuff because I misread the question. If you really need the 3rd party code I would spin another jvm and use ObjectStream or any other serialization logic. Then when the heartbeat stops, kill the process and restart or exit the runnable – bichito Feb 09 '17 at 12:22

2 Answers2

1

If the task has an infinite loop that does not check for the thread interrupted status and does not use methods that throw InterruptedExceptions, it won't be stopped by shutdownNow().

Simple example that doesn't allow you program to finish:

public static void main(String[] args) throws Exception {
  ExecutorService e = Executors.newFixedThreadPool(1);
  e.submit(() -> { while (true); });
  e.shutdownNow();
  System.out.println("Main is finished but the app keeps running");
}

One way would be to run the thread as a daemon:

public static void main(String[] args) throws Exception {
  ExecutorService e = Executors.newFixedThreadPool(1, r -> {
      Thread t = new Thread(r);
      t.setDaemon(true);
      return t;
    });
  e.submit(() -> { while (true); });
  e.shutdownNow();
  System.out.println("Main is finished and the app can exit");
}
assylias
  • 321,522
  • 82
  • 660
  • 783
  • hmm, this works if the JVM's life cycle is the same as the caller's life cycle, but if it's a plugin and the JVM lasts much longer then it will be a zombie daemon thread. – Jason S Feb 08 '17 at 21:10
  • @JasonS As long as your application is running, the task will keep running if you can't interrupt it. – assylias Feb 08 '17 at 22:15
  • grrrr. Surprised that it works this way in Java, given all the attention they pay to security privileges. – Jason S Feb 09 '17 at 15:21
  • 1
    @JasonS Yes, interruption is a collaborative process - if the thread you try to "stop" doesn't collaborate, you are out of luck... See also http://stackoverflow.com/a/11902443/829571 - alternatives include running that library code in a separate process but it will probably increase the complexity of your code if you need to retrieve results etc... – assylias Feb 09 '17 at 16:07
1

Following my correct reading of the question I put together this set of classes. Relatively simple: One Runnable that connects to a socket sending input and retrieving output from a secondary jvm that invokes the erratic library.

If after 3 tries no response has been received the secondary jvm is killed. But it could be relaunched. The secondary jvm has an exit hook to close down sockets.

class SafetyValve implements Runnable{
    PrintWriter out;
    BufferedReader in;
    Socket s = null;

    AtomicBoolean flag;

    SafetyValve(AtomicBoolean b){
        flag = b;
    }
    @Override
    public void run() {
        try {
            s = new Socket("localhost", 9000);
            out = new PrintWriter(s.getOutputStream(), true);
            in = new BufferedReader(new InputStreamReader(s.getInputStream()));

            while (!Thread.currentThread().isInterrupted()){
                flag.set(false);
                out.print(0);
                out.flush();
                System.out.print(in.read());
                flag.set(true);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        finally{
            try {
                s.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

}

Main/Controller class. It uses a Thread class for control

public class Switch {
    public static void main(String[] args) {
        try {
            AtomicBoolean flag = new AtomicBoolean(false);
            int counter = 0;
            ProcessBuilder pb = ...
            pb.directory(,,,);
            Process p = pb.start();
            SafetyValve sv = new SafetyValve(flag);
            Thread t = new Thread(sv);
            t.start();
            while(t.getState() != Thread.State.RUNNABLE){
                Thread.sleep(10);
            }
            while(true){
                if (flag.get() == false){
                    if (++counter == 3){
                        while(t.getState() != Thread.State.TERMINATED){
                            p.destroyForcibly();
                            t.interrupt();
                            Thread.sleep(10);
                        }
                        break;
                    }
                }
                else
                    counter = 0;
                Thread.sleep(100);
            }

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

The secondary jvm has an standard server socket implementation:

class UnYielding{
    int i = 0;

    int returnInt(){
        i++;
        if (i > 2)
            while(true);
        return i;
    }
}

class Hook extends Thread{

    RunWild rw;

    Hook(RunWild wr){
        rw = wr;
    }

    public void run() {
        try {
            System.out.println("exit...");
            System.out.flush();
            rw.socket.close();
            rw.server.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

public class RunWild {

    ServerSocket server;
    Socket socket;

    RunWild(){
        Runtime.getRuntime().addShutdownHook(new Hook(this));
    }

    public static void main(String[] args){
        UnYielding u;
        int i;
        PrintWriter out;
        BufferedReader in;
        RunWild rw = new RunWild();

        try {
            rw.server = new ServerSocket(9000);
            rw.socket = rw.server.accept();
            out = new PrintWriter(rw.socket.getOutputStream(), true);
            in = new BufferedReader(new InputStreamReader(rw.socket.getInputStream()));
            u = new UnYielding();
            while ((i = in.read()) != -1){
                out.print(u.returnInt());
                out.flush();
                Thread.sleep(10);
                System.out.print("waiting...");
                System.out.flush();
            }

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}

I have tested this against 1.8 on OS X it works as expected. If this unstable classes are needed this is one way of doing it

bichito
  • 1,406
  • 2
  • 19
  • 23