Java 7 WatchService - Ignoring multiple occurrences of the same event

Sotirios Delimanolis picture Sotirios Delimanolis · May 27, 2013 · Viewed 26.8k times · Source

The javadoc for StandardWatchEventKinds.ENTRY_MODIFY says:

Directory entry modified. When a directory is registered for this event then the WatchKey is queued when it is observed that an entry in the directory has been modified. The event count for this event is 1 or greater.

When you edit the content of a file through an editor, it'll modify both date (or other metadata) and content. You therefore get two ENTRY_MODIFY events, but each will have a count of 1 (at least that's what I'm seeing).

I'm trying to monitor a configuration file (servers.cfg previously registered with the WatchService) that is manually updated (ie. through command line vi) with the following code:

while(true) {
    watchKey = watchService.take(); // blocks

    for (WatchEvent<?> event : watchKey.pollEvents()) {
        WatchEvent<Path> watchEvent = (WatchEvent<Path>) event;
        WatchEvent.Kind<Path> kind = watchEvent.kind();

        System.out.println(watchEvent.context() + ", count: "+ watchEvent.count() + ", event: "+ watchEvent.kind());
        // prints (loop on the while twice)
        // servers.cfg, count: 1, event: ENTRY_MODIFY
        // servers.cfg, count: 1, event: ENTRY_MODIFY

        switch(kind.name()) {
            case "ENTRY_MODIFY":
                handleModify(watchEvent.context()); // reload configuration class
                break;
            case "ENTRY_DELETE":
                handleDelete(watchEvent.context()); // do something else
                break;              
        }
    }   

    watchKey.reset();       
}

Since you get two ENTRY_MODIFY events, the above would reload the configuration twice when only once is needed. Is there any way to ignore all but one of these, assuming there could be more than one such event?

If the WatchService API has such a utility so much the better. (I kind of don't want to check times between each event. All the handler methods in my code are synchronous.

The same thing occurs if you create (copy/paste) a file from one directory to the watched directory. How can you combine both of those into one event?

Answer

user262129 picture user262129 · Aug 9, 2014

WatcherServices reports events twice because the underlying file is updated twice. Once for the content and once for the file modified time. These events happen within a short time span. To solve this, sleep between the poll() or take() calls and the key.pollEvents() call. For example:

@Override
@SuppressWarnings( "SleepWhileInLoop" )
public void run() {
  setListening( true );

  while( isListening() ) {
    try {
      final WatchKey key = getWatchService().take();
      final Path path = get( key );

      // Prevent receiving two separate ENTRY_MODIFY events: file modified
      // and timestamp updated. Instead, receive one ENTRY_MODIFY event
      // with two counts.
      Thread.sleep( 50 );

      for( final WatchEvent<?> event : key.pollEvents() ) {
        final Path changed = path.resolve( (Path)event.context() );

        if( event.kind() == ENTRY_MODIFY && isListening( changed ) ) {
          System.out.println( "Changed: " + changed );
        }
      }

      if( !key.reset() ) {
        ignore( path );
      }
    } catch( IOException | InterruptedException ex ) {
      // Stop eavesdropping.
      setListening( false );
    }
  }
}

Calling sleep() helps eliminate the double calls. The delay might have to be as high as three seconds.