Skip to content

Draft: Implement watching file paths via oslib.watch #5068

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

Draft
wants to merge 24 commits into
base: main
Choose a base branch
from

Conversation

arturaz
Copy link
Contributor

@arturaz arturaz commented May 5, 2025

Currently watching files uses polling which is inefficient with large codebases. On my laptop ./mill --watch uses 20% CPU just to watch the files for changes.

This uses oslib's watch module to watch for the file changes instead of polling them.

We had to update oslib (com-lihaoyi/os-lib#386) to:

  • add a capability of filtering what folders it watches. This is needed because watching large generated folders like out or .bloop emits Java FS watcher OVERFLOW events.
  • remove the random println's that oslib.watch had.

The current approach to watching is to watch the workspace root via FS watching. FS watching only works with folders and we have to watch root/build.mill, and thus, root/, so everything else falls under it.

Mac OS watcher performs watching recursively natively, but on linux oslib adds recursive watches itself, so we use oslib.watch filter to prevent watching anything that is not an ancestor of watched root. For example, if we have source at root/module-a/src/, we'll watch root/, root/module-a/ and root/.

Finally, events are filtered so that unrelated changes (like creating root/random-file.txt) does not trigger reevaluation.

The fs watching is enabled by default and can be disabled via --watch-via-fs-notify=false.

0.12.x version: #5073

@lefou
Copy link
Member

lefou commented May 6, 2025

What are the CPU numbers on your laptop with this change applied?

@arturaz
Copy link
Contributor Author

arturaz commented May 6, 2025

What are the CPU numbers on your laptop with this change applied?

I haven't tested it yet, as my repo uses mill 0.12.10. With #5073 I'll be able to test it and report it later.

@arturaz
Copy link
Contributor Author

arturaz commented May 7, 2025

What are the CPU numbers on your laptop with this change applied?

I tested the 0.12.x branch, the CPU now hover's around 0.1%-2.5%, not enough to trigger the CPU fan, whilst previously CPU fan was constantly on.

Screencast_20250507_200300.webm

@arturaz arturaz force-pushed the improvement/fs-watching branch from 95c753d to 3e7c22c Compare May 8, 2025 09:56
@lihaoyi lihaoyi marked this pull request as draft May 13, 2025 05:26
@arturaz arturaz force-pushed the improvement/fs-watching branch from a2d3e4b to 73b6b6f Compare May 13, 2025 06:28
@arturaz arturaz force-pushed the improvement/fs-watching branch from 3ef16e8 to 1b797e9 Compare May 13, 2025 06:45
@@ -97,6 +97,11 @@ case class MillCliConfig(
doc = """Watch and re-run the given tasks when when their inputs change."""
)
watch: Flag = Flag(),
@arg(
name = "watch-via-fs-notify",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's shorten this to --notify-watch here and in the other PR, the current name is a bit of a mouthful

Comment on lines +13 to +16
private[mill] sealed trait Pollable extends Watchable

/** A [[Watchable]] that is being watched via a notification system (like inotify). */
private[mill] sealed trait Notifiable extends Watchable
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we actually need these two additional marker traits? It seems to me that working directly with Watchable.Path and Watchable.Value will be enough. There aren't any compatibility/extensibility concerns since all this stuff is private[mill] anyway

*
* Normally you should use [[validate]] so that types would guide your implementation.
*/
def validateAnyWatchable(w: Watchable): Boolean = poll(w) == signature(w)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This name is pretty weird; it wasn't great before but if we're going to change it we should use a better one. Maybe def checkForUpdate or something?

Comment on lines +155 to +161
// If I have 'root/a/b/c'
//
// Then I want to watch:
// root/a/b/c
// root/a/b
// root/a
// root
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is a bit confusing. We're only setting one os.watch.watch on the root, and this makes it sound like we're setting multiple. What we're actually doing is choosing the paths we need to watch recursively in Linux since inotify is non-recursive by default, since changes in any enclosing folder could result in the watched file or folder disappearing (e.g. if the enclosing folder was renamed) and we want to pick up such changes. Not sure how to phrase it in the comment but maybe you can come up with something

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants