0

I have an ExecutorService that is running a WatchService to monitor when files are changed in a directory and then save a copy of that file. I start the ExecutorService when Tomcat starts using the ServletContextListener. This code works great for several days but then eventually the code quits working with no errors in Tomcat log. Does anyone know why this might be stopping or even how I could monitor when it does stop and restart it without having to restart Tomcat? Below is the code I am using.

import java.io.File;
import java.io.IOException;
import java.nio.file.CopyOption;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class BackupServlet implements ServletContextListener {
    private static String uiFolderPath = "C:\\.......\\custom\\";
    private final static CopyOption[] options = new CopyOption[]{
        StandardCopyOption.REPLACE_EXISTING,
        StandardCopyOption.COPY_ATTRIBUTES
    };

    private ExecutorService executor;
    private WatchService watcher;
    private FileSystem fs = FileSystems.getDefault();

    public void contextInitialized(ServletContextEvent contextEvent) {
        try {
            String folderPath = uiFolderPath + "backups\\updates"; 
            createFolder(folderPath);
            watcher = fs.newWatchService();
            Path path = Paths.get(uiFolderPath);
            WatchKey key = path.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY);
            executor = Executors.newSingleThreadExecutor();
            startWatching();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void contextDestroyed(ServletContextEvent contextEvent) {
        executor.shutdownNow();
    }   

    private void startWatching() {
        executor.submit(new Runnable() {
            @Override
            public  void run() {
                try {
                    WatchKey key;                

                    while ((key = watcher.take()) != null) {
                        Thread.sleep(100);                  
                        List<WatchEvent<?>> events = key.pollEvents();

                        for (WatchEvent<?> event : events) {
                            String fileName = event.context().toString();

                            if (fileName.endsWith(".json")) {
                                //save backup version of file modified
                            }
                        }

                        key.reset();
                    }
                } catch (Exception ex) {
                    Utilities.writeLog("Error backing up files", ex.toString());
                }
            }
        });
    }

    private File createFolder(String folderPath) {
        File folder = new File(folderPath);

        if (!folder.exists())
            folder.mkdir();    

        return folder;
    }
}
second
  • 4,069
  • 2
  • 9
  • 24
Chris
  • 379
  • 1
  • 6
  • 19
  • Your `Runnable` appears to have a terminating condition: if there are no changes in watch service, it will exit the `run()` method. I'd either put the whole thing into the `while(true)` loop (with caveat that it needs to check whether the server is stopping in each loop), or make it a `ScheduledExecutorService` instead, with some not so big interval between runs. – M. Prokhorov Aug 01 '19 at 16:38
  • 1
    For starters you should probably add some more logging to the listenerer and your `Runnable`. `contextDestroyed` might be invoked by some other events (e.g. the war is updated). You could probably use the Tomcat Manager to redeploy the webapp. – second Aug 01 '19 at 16:39
  • @second, what do you think about my comment? I expect simply running out of watch service events to process (as would happen if user activity is low) is more likely reason than whole app being destroyed on accident. – M. Prokhorov Aug 01 '19 at 16:42
  • 1
    @M.Prokhorov: In doubt we dont know, hence more logging. I haven't used `watchService` myself, but from the API I understand it should be blocking (so there should not be a possibility of running out of events). – second Aug 01 '19 at 16:44
  • @second, you are actually right about that: `take()` waits for events if none are present. I made a mistake in confusing it with `poll()`. Then your suggestion is more sound. – M. Prokhorov Aug 01 '19 at 16:48
  • @Chris, also, what does your `Utilities.writeLog` do? You say there's nothing in log, but are you sure this `Utilities` output actually writes to log, and doesn't try to print into some logger log level with ignored by it? – M. Prokhorov Aug 01 '19 at 16:51
  • Judging from some of the related questions using `executor.shutdownNow()` in the `contextDestroyed` method is a problem of its own, as this doesn't guarantee the shutdown of the thread. You're better of using daemon threads (https://stackoverflow.com/questions/13883293/turning-an-executorservice-to-daemon-in-java/13883412#13883412). Based on that we could assume its more likely that your `Runnable` throws an exception or an error and terminates that way, like `@M.Prokhorov` suggested. – second Aug 02 '19 at 07:35

0 Answers0