Improved macOS support: java.nio.file Watch Service#41
Conversation
…at fully implements the `java.nio.file.WatchService` interface
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## improved-macos-support-main #41 +/- ##
================================================================
+ Coverage 65.3% 80.6% +15.2%
- Complexity 136 163 +27
================================================================
Files 20 23 +3
Lines 705 822 +117
Branches 80 95 +15
================================================================
+ Hits 461 663 +202
+ Misses 205 97 -108
- Partials 39 62 +23 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
…/nio-file-watch-service
…t/nio-file-watch-service
src/main/java/engineering/swat/watch/impl/mac/MacBlockingWatchService.java
Outdated
Show resolved
Hide resolved
src/main/java/engineering/swat/watch/impl/mac/MacBlockingWatchService.java
Outdated
Show resolved
Hide resolved
src/main/java/engineering/swat/watch/impl/mac/MacBlockingWatchService.java
Outdated
Show resolved
Hide resolved
src/main/java/engineering/swat/watch/impl/mac/MacBlockingWatchService.java
Outdated
Show resolved
Hide resolved
| public MacWatchKey(MacWatchable watchable, MacWatchService service) throws IOException { | ||
| this.watchable = watchable; | ||
| this.service = service; | ||
| this.pendingEvents = new LinkedBlockingQueue<>(); |
There was a problem hiding this comment.
Maybe we should batch add a whole range of events? Such that the queue contains lists of events? To avoid filling up the queue a lot and then cleaning it out again?
There was a problem hiding this comment.
For the sake of finishing this feature in a "good enough" state asap, for now, I'll add this as a possible performance optimization to the list of tasks in the feature PR (#39). Let's decide later if we want/need to do this as part of that PR, or move it to a separate issue.
| private final NativeEventStream stream; | ||
|
|
||
| private volatile Configuration config = new Configuration(); | ||
| private volatile boolean signalled = false; // `!signalled` means "ready" |
There was a problem hiding this comment.
Why do we need the signal variable? Is it not just : whenever the queue is not empty?
As I see all kinds of subtle races between clearing it and setting it in this class.
There was a problem hiding this comment.
According to the WatchKey spec, a watch key remains signalled until it's reset, even if the queue becomes (temporarily) nonempty, so a separate field is needed to do this bookkeeping.
The interaction between signalled and pendingEvents is quite tricky, indeed. To make it easier to understand what happens, I've isolated those fields and all code that accesses them in a separate helper inner class (including comments to argue that no harmful races arise).
| } | ||
|
|
||
| public void unregister(MacWatchService watcher) { | ||
| registrations.remove(watcher); |
There was a problem hiding this comment.
Shouldn't this also cause some kind of call to the wachter?
There was a problem hiding this comment.
This method is used only as part of WatchKey.cancel(), which is called by the poller when the watcher for the path of that watch key is closed. I.e., the watcher triggers the unregistration, so it already knows about it. I'll make it package-private, though, because it's just an internal bookkeeping method that shouldn't be used directly by third parties.
src/main/java/engineering/swat/watch/impl/mac/MacWatchable.java
Outdated
Show resolved
Hide resolved
…watch service is closed
…and improve comments
sungshik
left a comment
There was a problem hiding this comment.
Thanks! Addressed the comments and added a few clarifying responses.
|
|
||
| @Override | ||
| public List<WatchEvent<?>> pollEvents() { | ||
| var list = new ArrayList<WatchEvent<?>>(pendingEvents.size()); |
There was a problem hiding this comment.
Nope. (Well, to be fair, yes, computing the length of a linked list is indeed O(n) 😎, but LinkedBlockingQueue has an extra field to track it.)
| } | ||
|
|
||
| public void unregister(MacWatchService watcher) { | ||
| registrations.remove(watcher); |
There was a problem hiding this comment.
This method is used only as part of WatchKey.cancel(), which is called by the poller when the watcher for the path of that watch key is closed. I.e., the watcher triggers the unregistration, so it already knows about it. I'll make it package-private, though, because it's just an internal bookkeeping method that shouldn't be used directly by third parties.
| public MacWatchKey(MacWatchable watchable, MacWatchService service) throws IOException { | ||
| this.watchable = watchable; | ||
| this.service = service; | ||
| this.pendingEvents = new LinkedBlockingQueue<>(); |
There was a problem hiding this comment.
For the sake of finishing this feature in a "good enough" state asap, for now, I'll add this as a possible performance optimization to the list of tasks in the feature PR (#39). Let's decide later if we want/need to do this as part of that PR, or move it to a separate issue.
| private final NativeEventStream stream; | ||
|
|
||
| private volatile Configuration config = new Configuration(); | ||
| private volatile boolean signalled = false; // `!signalled` means "ready" |
There was a problem hiding this comment.
According to the WatchKey spec, a watch key remains signalled until it's reset, even if the queue becomes (temporarily) nonempty, so a separate field is needed to do this bookkeeping.
The interaction between signalled and pendingEvents is quite tricky, indeed. To make it easier to understand what happens, I've isolated those fields and all code that accesses them in a separate helper inner class (including comments to argue that no harmful races arise).
This PR adds an implementation of
java.nio.file's Watch Service API that usesNativeEventStreams (added in #40) to access the native APIs on macOS. It's an alternative to JDK'sFileSystems.getDefault().newWatchService().(The additional comments below contain copy/pastes of the JDK docs for convenience.)