7

I have a piece of Java program that essentially does the following:

public static void main(String[] args)
{
  while(true)
  {
  // does stuff ...
  }
}

The infinite loop is there by design - when left alone the program will loop infinitely. For the most part it works fine. However, sometimes I want to take the program down for maintenance, and when I take it down I want to make sure that it runs through all the code in the loop to the end then exit.

I am wondering what is the best solution for this. One idea I have in mind is to do something like this:

public static void main(String[] args)
{
    File f = new File("C:\exit.txt");
    while(!f.exists())
    {
        // does stuff ...
    }
}

which basically allows me to gracefully get out of the loop by creating a file called "exit.txt". This is probably OK for my purposes, but I would like to know if there are better, alternative methods.

illu
  • 79
  • 2
  • Well for simple things you can use a boolean like `downForMaintaince` and make it `'true` or `false` as of your need. then `while(!downForMaintance) { //infinite loop }` – Anirban Nag 'tintinmj' Jan 01 '15 at 21:55
  • How do I manipulate this downForMaintaince variable from the outside? – illu Jan 01 '15 at 21:56
  • @AnirbanNag, He meant without inside "help" so outside programs could signal this process. Those answers could direct you http://stackoverflow.com/questions/3244755/how-to-share-object-between-java-applications – Orel Eraki Jan 01 '15 at 21:56
  • Is this program continually running in a terminal? If so, you could make the program wait until you type in `exit` or something to `System.in`. –  Jan 01 '15 at 21:58
  • It is running on a terminal, but it is running on the background with &. I am not 100% sure how to invoke a keyboard action when it is on the background. – illu Jan 01 '15 at 21:59
  • 1
    @JarrodRoberson I would not consider this as a duplicate. The key here is that this should be done "remotely", from outside of the program, and not based on some user input from *within* the program. – Marco13 Jan 01 '15 at 22:06
  • There's quite a few different approaches to this, but the fact is that sometimes the simplest solutions (such as the flag file that you propose) are the best. – Steve C Jan 01 '15 at 23:14
  • Semaphores may help you. .. – Sumeet Sharma Jan 02 '15 at 04:43

5 Answers5

0

You could make use of runtime shutdown hook. That way you won't need to use console input in order to stop the loop. If JVM is being closed normally then shutdown hook thread will run. This thread will wait for the end of current loop iteration. Keep in mind that there are some limitations when using hooks though: https://docs.oracle.com/javase/8/docs/api/java/lang/Runtime.html#addShutdownHook-java.lang.Thread-

import java.util.concurrent.CountDownLatch;

public class Test {

    private volatile static CountDownLatch lastIterationLatch = null;
    private static boolean stop = false;

    public static void main(String [] args) throws Exception {

        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
               lastIterationLatch = new CountDownLatch(1);
               try {
                   lastIterationLatch.await();
               } catch (Exception e) {
                   throw new RuntimeException(e);
               }
            }
        });

        while(!stop) {
           System.out.println("iteration start");
           Thread.sleep(200);
           System.out.println("processing...");
           Thread.sleep(200);
           System.out.println("processing...");
           Thread.sleep(200);
           System.out.println("processing...");
           Thread.sleep(200);
           System.out.println("iteration end");
           if(lastIterationLatch != null) {
               stop = true;
               lastIterationLatch.countDown();
           }
        }
    }
}
extraleon
  • 67
  • 5
  • Agreed you could do it this way. When I gave the console answer I was mainly pointing to a pattern, the console being just one possible signal. It also allows program suspension and resumption without exiting. – Dan Jan 02 '15 at 11:13
0

I think that the WatchService that was introduced in Java 7 may be of use here (if you prefer a file based approach that is). From the JavaDocs:

A watch service that watches registered objects for changes and events. For example a file manager may use a watch service to monitor a directory for changes so that it can update its display of the list of files when files are created or deleted.

Basically what this means is that you can set up a WatchService that can watch a folder for changes. When a change occurs you can choose what actions to take.

The following code uses the WatchService to monitor a specified folder for changes. When a change has happened it executes a Runnable that the caller has provided (the method runWhenItIsTimeToExit).

public class ExitChecker {
    private final Path dir;
    private final Executor executor;
    private final WatchService watcher;

    // Create the checker using the provided path but with some defaults for
    // executor and watch service
    public ExitChecker(final Path dir) throws IOException {
        this(dir, FileSystems.getDefault().newWatchService(), Executors.newFixedThreadPool(1));
    }

    // Create the checker using the provided path, watcher and executor
    public ExitChecker(final Path dir, final WatchService watcher, final Executor executor) {
        this.dir = dir;
        this.watcher = watcher;
        this.executor = executor;
    }

    // Wait for the folder to be modified, then invoke the provided runnable
    public void runWhenItIsTimeToExit(final Runnable action) throws IOException {
        // Listen on events in the provided folder
        dir.register(watcher,
                StandardWatchEventKinds.ENTRY_CREATE,
                StandardWatchEventKinds.ENTRY_DELETE,
                StandardWatchEventKinds.ENTRY_MODIFY);

        // Run it async, otherwise the caller thread will be blocked
        CompletableFuture.runAsync(() -> {
            try {
                watcher.take();
            } catch (InterruptedException e) {
                // Ok, we got interrupted
            }
        }, executor).thenRunAsync(action);
    }
}

So, how do we use the checker then? Well, the following code illustrates this:

public static void main(String... args) throws IOException, InterruptedException {
    // Setup dirs in the home folder
    final Path directory = Files.createDirectories(
            new File(System.getProperty("user.home") + "/.exittst").toPath());

    // In this case we use an AtomicBoolean to hold the "exit-status"
    AtomicBoolean shouldExit = new AtomicBoolean(false);

    // Start the exit checker, provide a Runnable that will be executed
    // when it is time to exit the program
    new ExitChecker(directory).runWhenItIsTimeToExit(() -> {
        // This is where your exit code will end up. In this case we
        // simply change the value of the AtomicBoolean
        shouldExit.set(true);
    });

    // Start processing
    while (!shouldExit.get()) {
        System.out.println("Do something in loop");
        Thread.sleep(1000);
    }

    System.out.println("Exiting");
}

Finally, how do you exit the program then? Well simply touch a file in the specified folder. Example:

cd ~/.exittst
touch exit-now.please

Resources:

Community
  • 1
  • 1
wassgren
  • 18,651
  • 6
  • 63
  • 77
0

One could employ some sophisticated techniques here. The file watchdog is one option. RMI could be another. But in fact, the mechanisms that are required here are quite simple, so I'd like to propose another (very simple) solution.

Note: This solution is just one option, showing that it is possible to do it that way. It is not a general recommendation, and whether it is "good" or not depends on the application case.

The solution is simply based on Sockets. The ServerSocket#accept method already encapsulates the functionality that you want:

Listens for a connection to be made to this socket and accepts it. The method blocks until a connection is made.

Based on this, it is trivial to create such a "remote control": The server just waits for a connection, and sets a flag when the connection is opened:

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.atomic.AtomicBoolean;

class RemoteExitServer
{
    private final AtomicBoolean flag = new AtomicBoolean();

    RemoteExitServer()
    {
        Thread t = new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                waitForConnection();
            }
        });
        t.setDaemon(true);
        t.start();
    }

    private void waitForConnection()
    {
        ServerSocket server = null;
        Socket socket = null;
        try
        {
            server = new ServerSocket(1234);
            socket = server.accept();
            flag.set(true);
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (server != null)
            {
                try
                {
                    server.close();
                }
                catch (IOException e)
                {
                    e.printStackTrace();
                }
            }
            if (socket != null)
            {
                try
                {
                    socket.close();
                }
                catch (IOException e)
                {
                    e.printStackTrace();
                }
            }
        }

    }

    boolean shouldExit()
    {
        return flag.get();
    }
}

The client does exactly that: It opens a connection, and nothing else

import java.io.IOException;
import java.net.Socket;

public class RemoteExitClient
{
    public static void main(String[] args)
    {
        Socket socket = null;
        try
        {
            socket = new Socket("localhost", 1234);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (socket != null)
            {
                try
                {
                    socket.close();
                }
                catch (IOException e)
                {
                    e.printStackTrace();
                }
            }
        }
    }
}

The application is then also very simple:

public class RemoteExitTest
{
    public static void main(String[] args)
    {
        RemoteExitServer e = new RemoteExitServer();

        while (!e.shouldExit())
        {
            System.out.println("Working...");
            try
            {
                Thread.sleep(1000);
            }
            catch (InterruptedException e1)
            {
                e1.printStackTrace();
            }
        }
        System.out.println("done");
    }
}

(The code could be made even more concise with try-with-resources, but this should not matter here)

Marco13
  • 53,703
  • 9
  • 80
  • 159
  • It seems like every answer has been downvoted at least once so far. I'd really like to see an answer that the downvoter(s) do(es) not consider as "not helpful". From a systematic point of view, there are at most three options of communicating with a running Java application: 1. via files, 2. via network, 3. maybe somehow with JNI magic. The latter seems unpractical. The file-based approach looks a bit clumsy for me. So what is left is the network-based approach (regardless of whether it's via RMI or an own socket). And that's as close to *"remote-controlling"* as it gets. – Marco13 Jan 03 '15 at 14:23
-1

For something quick/dirty, use Signals:

boolean done = false;

// ...

Signal.handle(new Signal("USR1"), new SignalHandler() {
    @Override
    public void handle(Signal signal) {
        // signal triggered ...
        done = true;
    }
});

// ...

while(!done) { ... }

Then, use kill -USR1 _pid_ to trigger the signal.

Danny Daglas
  • 1,501
  • 1
  • 9
  • 9
  • This is a silly question but what library do I need to do this? I tried compiling this code here (http://stackoverflow.com/a/1409195/4410951) and it complains that "Signal" cannot be resolved. – illu Jan 01 '15 at 22:09
  • sun.misc is the package. So, import sun.misc.*; – Danny Daglas Jan 01 '15 at 22:21
  • Check this link on [why not to use sun.misc](http://stackoverflow.com/questions/11379660/why-is-sun-misc-disencouraged-for-use). – wassgren Jan 02 '15 at 22:43
-1

You could use a AtomicBoolean as in the test program below. To suspend just type true into the console to resume type false. The program will never exit.

public class Test2 {
public static void main(String[] args) {
    final AtomicBoolean suspended = new AtomicBoolean(false);

    new Thread() {
        public void run() {
            while (true)
            {
                Scanner sc = new Scanner(System.in);
                boolean b = sc.nextBoolean();
                suspended.set(b);
            }
        }
    }.start();


    while(true){
        if(!suspended.get()){
            System.out.println("working");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        else{
           //System.exit(0) //if you want to exit rather than suspend uncomment.
        }
    }

}

}

Dan
  • 9,681
  • 14
  • 55
  • 70
  • How do I make the program stop using this setup? – illu Jan 01 '15 at 22:23
  • Do you actually want to do System.exit? If so add an else clause with System.exit(0). I've edited code to reflect the change you would need. – Dan Jan 01 '15 at 22:24