-
Notifications
You must be signed in to change notification settings - Fork 0
Improved macOS support: java.nio.file
Watch Service
#41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comments
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some small comments
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this also cause some kind of call to the wachter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 usesNativeEventStream
s (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.)