-
-
Notifications
You must be signed in to change notification settings - Fork 160
Experiment: Add @parcel/watcher
as a file-watcher
#1299
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
base: updated-latest-electron
Are you sure you want to change the base?
Experiment: Add @parcel/watcher
as a file-watcher
#1299
Conversation
Ok, that is kind of amazing. One thing that I want is to check if we could, somehow, use the same API to remove pathwatcher too in the future (considering this code already does some interesting stuff with tasks). I'll merge this branch in my own fork and try things out to inform if there's some change (I do have some weirdness with the "normal" file watcher of Pulsar, but I can't really reproduce easily what's wrong) |
I also experienced some problems with file-watcher in pulsar-next (but it's still much better than in pulsar stable). I'd be happy to test the new option. The first problem is that after switching to |
Do you mean |
There are lots of places in the codebase where there are subtle and annoying assumptions that, once I set up a watcher via The let file = new File(`/foo/bar/baz.txt`)
file.onDidChange(() => console.log('did change!')) …it sets up a watcher. But which of those methods could we make async? Probably not the So this would probably need to change to something like… let file = new File(`/foo/bar/baz.txt`)
await file.watch(() => console.log('did change!')) …and I suppose that's not that bad, once I type it out. Same with Of course, they can support multiple listeners, so we could instead do… let file = new File(`/foo/bar/baz.txt`)
file.onDidChange(() => console.log('did change!'))
await file.watch() …and the file watcher function just emits Only trouble is that this will probably break some code in subtle ways, since let file = new File(`/foo/bar/baz.txt`)
file.onDidChange(() => console.log('did change!')) // <-- still sets up the file watcher…
await file.watch() // <-- and this method merely waits until the file-watcher promise resolves That's probably good enough. So the solution is to change the expectations of everything that still uses |
@pulsar/watcher
as a file-watcher@parcel/watcher
as a file-watcher
OK, made a dumb typo. @asiloisad, the stylesheet issue should be fixed now. (Surprised it wasn't broken even more than it was!) @mauricioszabo, if you were testing on this branch, please grab latest and disregard any prior findings. |
* The `nsfw` watcher is now also running in a separate process. * Both watchers now inherit `core.ignoredNames` in an effort to limit the cost of recursive watchers on Linux. * Watchers update automatically when `core.ignoredNames` changes.
Oof! Not my favorite side effect. @asiloisad, if you get a chance, could you install Watchman and see if that changes this symptom? |
OK, this PR is updated to reflect further experimentation. As I ranted about in Discord and in #1306, both of these watcher implementations make it easy to exhaust a limited number of allowed The least intrusive way to fix this is to apply the user's It was hard to make this work in It's costly to turn globs into actual file paths! And, as you'd expect, it's more costly when the directory tree is deeper. My “typical” use case test was the
So I did what I've already done for For The watchers themselves should also update their lists of exclusions whenever I also tweaked the description in @mauricioszabo and @asiloisad, I'm also curious about how the presence of Watchman affects perceived performance and other side effects. In Linux's case, I wonder whether there's somehow a further reduction in In Windows's case, I wonder whether the symptom @asiloisad described above (brief appearance of a terminal window) is something inherent to |
The hypothetical visitor to this PR (someone who wasn't in Discord for the discussion) may wonder why I haven't pursued using a project's Here's why: Right now, there isn't a 1:1 correlation between usages of It's therefore currently impossible for us to use So the options here are:
|
…ar into pulsar-next-experimental-parcel-watcher
…when asked to watch a file rather than a directory.
…when switching between file-watcher implementations.
I found a bug in the spec code for Still, that doesn't explain all the test failures in CI. I can't reproduce the macOS failures locally. I might try to run the |
Actually, I just realized that |
Thinking more about our options for ignored names (as outlined in this comment), I'm gravitating toward a sixth option: Add a This would be useful for adding paths that should not necessarily be part of |
Oh, also: this mystery got solved! Since the project window was the user's entire home directory, this was always going to involve a lot of |
I just discovered another regression on this branch: when using the I'm nearly certain that this is because of the |
My hypothesis was correct. The As an aside: one quick-and-dirty option here would be to stop using That would be one way to fix this without changing anything about the The good news is that the basic functionality of detecting branch changes can be replicated with a simple non-recursive The bad news is that such a change would have to be made in the github package. Since the fork, we've largely handled that package with big rubber gloves and tongs, touching it only when its dependencies need to be updated. The code conventions are vastly different (it was written by Facebook people, I think?), it uses an old version of React, and it generally gives off a mildly distasteful smell. Rewriting that package is on my long-term to-do list, but for the shorter term I'll have to come up with a more targeted patch if the |
Is there a chance to merge this branch into pulsar-next branch with beta flag as example? I'm stick to this branch, as it's work much better. |
I might have to do it without the |
This is one of those PRs that may sit here for a while without much attention. But that’s OK, because the alternative is that it sits on my hard drive or in one of my Git stashes.
In theory, we have a pluggable file-watching architecture that handles some of our file-watching tasks. The original PR that added it to Atom explained that various packages were using various file-watching libraries and probably duplicating efforts, so it made sense for it to be part of Atom’s API offering.
nsfw
was picked to back the initial implementation, but Atom then forkednsfw
, and in parallel were working on their own@atom/watcher
library. (Not to be confused withnode-pathwatcher
, which dates back to the early Atom days and is more challenging to remove from our codebase than asbestos is from buildings.)We don’t want to maintain our own file-watcher library, but neither do we want to be beholden to someone else’s library. It’s good to have options. A while back I poked around to see how VS Code was handling file watching, and it turns out they’re using
@parcel/watcher
. It’s built by the Parcel folks and offers a similar feature set tonsfw
.I have weird ideas of “fun”
I gave myself an evening to integrate this into Pulsar as an alternative to
nsfw
. At first, this PR was designed to point to themaster
branch —@parcel/watcher
claims to support Node versions all the way back to v10 — but it failed during theelectron-rebuild
stage. That suggests to me that it installs fine on Node 16 (the version I run in mypulsar
folder) but not on Node 14 (the major version against which it builds when it doeselectron-rebuild
targetingv12.2.3
), hence itsengines
field may be inaccurate.That was the first hiccup.
I wrote a wrapper around
@parcel/watcher
that was nearly identical to the one aroundnsfw
. All worked great until I needed to reload the window; when I did, I got a renderer process crash on a consistent basis. Sentry’s stack trace points to the new library but gives me a pretty generic error:Pretty much all crashes in N-API are due to thread-safe functions, so that’s not much help! I wonder if it’s not context-aware in practice; N-API gives you the framework to be context-aware, but you still have to do the hard work of not accidentally sharing things between contexts that should not be shared.
I vaguely recalled that VS Code ran their file-watcher in a separate process, so I thought I’d give that a shot. It went great!
Task
: the underappreciated core classWe’ve got a class whose purpose is to simplify the job of spawning another process to run a worker. It’s called
Task
and, though there aren’t many usages within the Pulsar codebase, it was easier to figure out how to use it than to start from scratch. I recalled some of the design decisions we made while buildinglinter-eslint-node
and that made things go even faster.The short version:
NSFWNativeWatcher
, it’s not bad at all. It means one more process per window taking up space in your task manager, but that process uses hardly any CPU and is basically hibernating most of the time.nsfw
),@parcel/watcher
doesn’t have explicit support for renaming as a filesystem event. But since it delivers events in batches, it’s not hard to envision that we could detect renames by looking for two files in the same batch of events — one with typedeleted
and the other with typecreated
.Why did I do this?
There’s someone on Discord who often experiences a Pulsar-related process using 100% of CPU (on a single core) while they’ve got it open. Louder fans, lower battery life, et cetera. The mystery goes pretty deep and we’re not sure exactly where the cost is being paid, but at this point we’ve got it narrowed down to file-watching.
This user is on Linux and often has a project window open to a path called
/user/home
— which, regardless of the particular Linux distro, has the potential to be a busy directory with lots of stuff in it.On top of which: Linux uses
inotify
for filesystem watchers. Unlike the FSEvents API on macOS — which is basically a metadata firehose that can easily be filtered down to only the filesystem events you’re interested in —inotify
is not recursive and can monitor one directory at a time. So to approximate a recursive watcher,nsfw
crawls a directory tree and creates oneinotify
watcher for each directory.By itself that isn’t enough to explain how a process ends up using 100% of CPU, but it’s a hard data point to ignore. I asked same user to test what happens if they open a new Pulsar project in a new empty folder; the user says that they’re not able to reproduce the high CPU usage in this test.
Anyway, it felt like a good time to audition an alternative file-watching library, if only to keep our options open! In support situations like the one I just described, I don’t have much to offer for troubleshooting outside of “build Pulsar from scratch, generate a debug build of
nsfw
, and step through it in your favorite C/C++ debugger” — and that’s rough. It’d be great if I could say something like “try switching to a different filesystem watcher in your settings and see if that makes a difference.”There are other thoughts I eventually want to capture about recursive vs. non-recursive watchers, but it’s late and I can do that tomorrow.
What should I do with this PR?
Play around with it a bit! There’s not much urgency, since this is feeling like a post-PulsarNext task. My eventual goal is to introduce this as a new option while keeping the same default, much like we did with the SQL state store vis-a-vis the IndexedDB state store. Folks could switch to it to try it out, and if it doesn’t work great, the worst that happens to them is that they’d have to switch back.
That reminds me: the setting to change is under Core -> File System Watcher in the settings GUI. There’s currently only one option (“native operating system APIs”), and the name is misleading, but I envision three options: NSFW, @parcel/watcher, and “default,” where you just let us pick one. Amazingly, you can change this setting on the fly in the middle of a session — the
PathWatcher
class handles all the work of tearing down the old watchers and creating new ones!No tests or anything yet, so this is a draft to start out. I'll probably ignore it for a while now, but at least it's on public display.