1

I'm trying to create a watcher that looks for changes to a particular folder. I've created a watcher and put this within an Async method, but when I call it from a service the application pauses due to the while loop in the watcher method. It is like the method isn't being execute within a new thread.

Here is the class that contains the method I'm trying to execute;

    @Service
public class FileWatcher {

    @Async
    public Future<Object> watch(Path path, DataParser parser) throws IOException, InterruptedException {
        WatchService watchService = FileSystems.getDefault().newWatchService();

        path.register(
                watchService,
                StandardWatchEventKinds.ENTRY_CREATE,
                StandardWatchEventKinds.ENTRY_DELETE,
                StandardWatchEventKinds.ENTRY_MODIFY);

        WatchKey key;
        while ((key = watchService.take()) != null) {
            for (WatchEvent<?> event : key.pollEvents()) {
                File file = new File(path.toString() + "/" + event.context());

                if (event.kind() == StandardWatchEventKinds.ENTRY_MODIFY) {
                    parser.fileChanged(file);
                }

                if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
                    parser.fileCreated(file);
                }

                if (event.kind() == StandardWatchEventKinds.ENTRY_DELETE) {
                    parser.fileRemoved(file);
                }
            }
            key.reset();
        }

        return null;
    }

}

Then, I'm calling this within the constructor of a service.

@Service
public class SPIService {

    private final String DATAFOLDER = "/spi";

    private Path dataPath;

    public SPIService(@Value("${com.zf.trw.visualisation.data.path}") String dataPath) {
        this.dataPath = Paths.get(dataPath + DATAFOLDER);

        FileWatcher fileWatcher = new FileWatcher();
        try {
            fileWatcher.watch(this.dataPath, new SPIParser());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

Why isn't this working? Is it because I'm calling the method from the constructor of a service?

SheppardDigital
  • 3,165
  • 8
  • 44
  • 74
  • You may be having problem with exceptions not propagating to the main thread, check the documentation for EnableAsync and AsyncConfigurer – yorodm Aug 20 '18 at 13:43
  • I've tried putting the try-catch statement in the watch() method and it didn't make any difference – SheppardDigital Aug 20 '18 at 13:47
  • You better change the watch() method return type to a Future and return null at the end of the method. Then Future.get() will throw an exception if present – Yuriy Tsarkov Aug 20 '18 at 13:53
  • You can check [this thread](https://stackoverflow.com/questions/8735870/spring-async-uncaught-exception-handler) and [this article](https://dzone.com/articles/spring-async-and-exception) but basically if you're using @async with a void method you need to ensure that exceptions are properly handled, otherwise they will fail silently in the background – yorodm Aug 20 '18 at 13:54
  • Or as @YuriyTsarkov says, change your method to return a Future, in that way any exception will be propagated to your main thread – yorodm Aug 20 '18 at 13:55
  • I've updated my original question to reflect the suggestions above, still doesn't appear to be working. – SheppardDigital Aug 20 '18 at 14:08

1 Answers1

3

You're using new FileWatcher() which means the instance isn't a managed bean. This also means that @Async is ignored (which explains your application halting). You need to @Autowire FileWatcher instead.

Note also that your solution seems very suspicious to me, not only for having an infinite loop, but having one in an @Async method (this has some important consequences). I would at least use a single threaded threadpool for it.

Kayaman
  • 72,141
  • 5
  • 83
  • 121
  • Thanks that was it. Do you have a link to an example fo using a single threaded threadpool? – SheppardDigital Aug 20 '18 at 14:18
  • `Executors.newSingleThreadExecutor()` will give you one. If you have multiple paths to watch, I suggest you design your code so that the watch service (and the thread) is shared. – Kayaman Aug 20 '18 at 14:22
  • Thanks Kayaman. I've created a class that accepts multiple paths to monitor, and it uses a single thread and then using scheduleAtFixedRate() to check for changes at set intervals. – SheppardDigital Aug 20 '18 at 15:22
  • Well, since the `WatcherService` already tells you when something has happened, you don't need a scheduled executor. The thread will just block at `take()` until an event occurs (and it can't return `null`). – Kayaman Aug 20 '18 at 15:23
  • ah ok, so once an event occurs, it's just a case of calling take again to wait for the next event. Seems to work fine. – SheppardDigital Aug 20 '18 at 15:31