Before showing-off the code, here is a bit of an explanation.
I have a Spring component that spawns a thread for watching a directory for new files.
In this context, I have a method that runs in a Java Thread
and performs operations that might be interrupted (especially if the container class is being close()
d). The Spring component is designed to be Autocloseable
and dispose the thread when the component self is being disposed.
SonarCloud warns me that I catch the InterruptedException
without re-throwing it, but I am just logging the event and return
ing.
public class MyBean implements MyInterface, InitializingBean, AutoCloseable {
/**
* Watcher thread
* Listens for new files available in the input directory and notifies listeners synchronously
*/
private Thread watcherThread;
/**
* File system watch key
*/
private WatchKey fileSystemWatchKey;
private WatchService watchService;
@Override
public synchronized void afterPropertiesSet() throws Exception {
log.info("Watching directory {} for new files to acquire", inputPath);
this.watchService = inputPath.getFileSystem().newWatchService();
this.fileSystemWatchKey = inputPath.register(watchService, StandardWatchEventKinds.ENTRY_CREATE);
watcherThread = new Thread(this::watchDirectory, beanName + "-watcher");
watcherThread.setPriority(Thread.MIN_PRIORITY);
watcherThread.setDaemon(true);
watcherThread.setUncaughtExceptionHandler((thread, exception) -> log.error("Exception in watcher thread {}: {}", thread.getName(), exception.getMessage(), exception));
watcherThread.start();
}
@Override
public void close() {
//Stop watcher thread
if (watcherThread != null)
try {
log.debug("Stopping thread {}", watcherThread.getName());
watcherThread.interrupt();
watcherThread.join(60000);
log.debug("Stopped thread {}", watcherThread.getName());
} catch (InterruptedException ex) {
} catch (Exception ex) {
log.error("Error disposing watcher thread {}", watcherThread.getName(), ex);
}
watcherThread = null;
}
/**
* Starts a watch loop over the file system
*/
@SuppressWarnings("unchecked")
protected void watchDirectory() {
if (watchService == null)
throw new IllegalStateException("Watch service not initialized");
while (true) {
try {
WatchKey watchKey = watchService.take(); //This throws InterruptedException
if (watchKey == null) {
log.debug("No new file found in input directory");
Thread.sleep(60000);
continue;
}
for (WatchEvent<?> pollEvent : watchKey.pollEvents()) {
WatchEvent.Kind<?> kind = pollEvent.kind();
if (!Path.class.isAssignableFrom(kind.type())) {
log.trace("Unsupported event kind {} type {}", kind.name(), kind.type());
continue;
}
WatchEvent<Path> pathWatchEvent = (WatchEvent<Path>) pollEvent;
File newFile = this.inputPath.resolve(pathWatchEvent.context()).toFile().getAbsoluteFile();
if (newFile.isFile())
try {
doStuff();
} finally {
doFinalize();
}
}
} catch (ClosedWatchServiceException closedWatchServiceException) {
log.debug("Closed watch service. Ending loop");
log.trace(closedWatchServiceException.getMessage(), closedWatchServiceException);
return;
} catch (InterruptedException ex) {
log.debug("Watcher thread interrupted: ", getBeanName());
log.trace(ex.getMessage(), ex);
return;
} catch (IOException ex) {
log.error("I/O exception: {}", ex.getMessage(), ex);
return;
}
}
}
}
Notes:
- The bean is designed to be combination of
InitializingBean
/AutoCloseable
:afterPropertiesSet
starts the thread,close
stops it - Method
WatchServie.take
throws InterruptedException return
ing from thread method ends the thread. No other code to execute
Question: is it a false positive?
Offending fragment
} catch (InterruptedException ex) {
log.debug("Watcher thread interrupted: ", getBeanName());
log.trace(ex.getMessage(), ex);
return;
}
Expected fragment
} catch (InterruptedException ex) {
log.debug("Watcher thread interrupted: ", getBeanName());
log.trace(ex.getMessage(), ex);
Thread.currentThread().interrupt();
return;
}
SonarCloud suggests me to add Thread.currentThread.interrupt()
, but I don't find it meaningful in this context. Probably Sonar uses default rules that do not necessarily take all the context into consideration (this is why I suppressed a load more remarks in other contexts)