-
Notifications
You must be signed in to change notification settings - Fork 110
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
Support user-specified callbacks #443
Conversation
This feature is one half of what `Revise.entr` already does. The function `entr` allows the user to install callbacks for when a file or module changes, and also enters a blocking loop that catches any errors that might arise from the callbacks. This new `add_callback` function allows installing callbacks and keeping them around in the background. This commit also reimplements `entr` in terms of `add_callback` and `remove_callback`.
Just to see if I understand, the goal here is to allow more granular control: the ability to control behavior when errors are thrown, and to remove callbacks from the queue? If so, I'm all in favor. However, this failed tests on every platform but Linux. One possible fix is another |
Exactly. Happy that you're in favour, I'll look into the test failures then! |
Codecov Report
@@ Coverage Diff @@
## master #443 +/- ##
==========================================
- Coverage 81.88% 80.62% -1.26%
==========================================
Files 12 12
Lines 1551 1667 +116
==========================================
+ Hits 1270 1344 +74
- Misses 281 323 +42
Continue to review full report at Codecov.
|
I'm going to cycle CI on this and see how it does now that #450 is merged. There's a failure on 1.0 that I don't understand. |
Still some CI failures on non-linux platforms. It's obviously important to support those as well. Do you have any best practices that might help me tackle this? Myself, I'd be tempted to replace all reliance on |
The In the past it has been observed that magic dust occasionally helps: Line 904 in 836d261
Would someone with a mac or windows machine check out this branch and see if they can figure out the problem? I've been thinking @mlhetland should review this anyway. |
Hm. Just trying out the test more manually, my first observation is that the callback is not triggered unless the file is closed. However, if you (e.g.) replace I'm not sure if the callback is supposed to be triggered automatically; it's only triggered once I call |
As observed by @mlhetland, this does not seem to work on all platforms.
That's a great point. I pushed a commit that hopefully fixes it in the way you describe.
Yes, that's the idea of these callbacks. If someone wants them to be called "asap" then they don't need My personal use case is loading custom template code in HAML.jl, but one might also imagine using this as (untested): const COUPLING_CONSTANTS = Ref(read_json("coupling_constants.json"))
Revise.add_callback(["coupling_constants.json"]) do
COUPLING_CONSTANTS[] = read_json("coupling_constants.json")
end Then the user can be sure that the coupling constants don't change in mid-computation. |
For some reason Travis doesn't seem to be reporting back (not sure whether that's a Travis problem or a GitHub problem), but you can monitor it by navigating to https://travis-ci.org/github/timholy/Revise.jl, clicking on "More options," choosing "Requests," and then clicking on the build number for the most recent request. Looking very promising! I expect failure on non-Linux nightly due to JuliaLang/julia#35297, so that's nothing to worry about. |
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.
Now that we're passing tests, here's a more serious review. Perhaps @mlhetland will have other specific comments.
for file in srcfiles(pkgdata) | ||
absname = joinpath(basedir(pkgdata), file) | ||
push!(files, absname) | ||
track(mod, absname) |
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.
It only recently became safe to call track
more than once on the same file, so this seems OK. But, under what circumstances would these not already be tracked?
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.
I don't actually see any reason why they would already be tracked? What if someone does Revise.add_callback([], Base) do ....
? I'm probably missing something here.
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.
I guess I'm reacting to the fact that you're pulling pre-existing files out of the pkgdata
and adding them to files
. Tracking would be on if you'd said using MyPkg
and then called this later with add_callback(..., modules=(MyPkg,),...)
. Stated differently, if they're already in pkgdata
how are they not tracked?
Perhaps I don't understand the purpose, and a comment would be helpful.
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.
Ah, now I see what you mean! Yes, this is faulty code then -- I'm trying to guard for the fact that the module may not be under tracking at all yet. Under that logic, we should first call track(mod)
and only then read the data from pkgdata
, right?
If that's the case, I should also add a doctest that tests for this correct behaviour.
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.
Any time you've said using MyPkg
it should be tracked. Though perhaps the watch_package
callback (see how it gets registered in __init__
) has to fire first. Is that the issue?
I think I've make track
safe for re-entry (see #433), but I tend to be jittery (probably, needlessly) about things getting done twice and messing up the internal accounting. Maybe poke at this a bit first and try to define more clearly what circumstances require this?
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.
Any time you've said
using MyPkg
it should be tracked.
The two things that I have in mind are:
- packages loaded before
Revise
was loaded, e.g.
using MyPackage
using Revise
Revise.add_callback([], [MyPackage]) do ....
- packages in the system image (e.g.
Base
or anything the user added inPackageCompiler
).
The first one could potentially be tested by spawning another Julia process. The second one seems almost impossible to test. But in both cases, calling Revise.track(<module>)
before reading pkgdata
seems like it's the right thing to do -- what do you think?
I wasn't thinking about a race condition related to when watch_package
is being called; do you think we should?
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.
packages loaded before Revise was loaded
If package precompilation were required for all modules, I could support that, but the existence (even in principle) of non-precompiled packages makes robust support for such a feature impossible. The problematic use case is loading the package, making some changes to the source, and then tracking with Revise: in this case Revise has no way of knowing that what's in the files does not match the code in your running session. Conversely, if you load Revise first, it "defensively" parses the entire package when it's loaded and then it has a baseline to compare any file changes. This is not necessary for precompied packages because I added a source-cache to *.ji
files in JuliaLang/julia#23898, and did the same for Julia itself in JuliaLang/julia#24120. This means that you can always recover the state of the source at the time at which the *.ji
file was built. But there is no such fix available for non-precompiled packages.
So I plan to support this feature by saying "load Revise first, otherwise you're screwed" 😄. More seriously, I could contemplate supporting this specifically for precompiled packages, but we'd need to enforce it.
packages in the system image (e.g. Base or anything the user added in PackageCompiler).
That's a much more important use-case. Do such files record their source text? If not, we're screwed for now but should fix that ASAP.
But in both cases, calling Revise.track() before reading pkgdata seems like it's the right thing to do -- what do you think?
Yeah, track
populates the structure and then you can do stuff with it.
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.
@ffevotte this is the thing that needs resolving: in add_callback
we need to move the call to Revise.track(mod)
to the place before reading pkgdatas[id]
. We don't have to call Revise.track(mod, absname)
after that.
After that change, hopefully this is done.
Looks like tests are passing on all platforms now 😀 There's only an unrelated line-number issue on Julia nightly. Anything I can still improve? |
I haven't done a thorough review, but I've looked at the code, and it looks good to me! I also think this make the whole idea of callbacks a more natural part of Revise; I guess we might consider renaming |
Thanks for adding your comments @mlhetland ! I also understand your point about the naming: I was one of the users that didn't know about the entr project so I was a bit confused about it 😅 I have no real opinion on whether we should also rename or add an alias to |
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 seems basically ready to merge, maybe just a couple tweaks?
for file in srcfiles(pkgdata) | ||
absname = joinpath(basedir(pkgdata), file) | ||
push!(files, absname) | ||
track(mod, absname) |
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.
I guess I'm reacting to the fact that you're pulling pre-existing files out of the pkgdata
and adding them to files
. Tracking would be on if you'd said using MyPkg
and then called this later with add_callback(..., modules=(MyPkg,),...)
. Stated differently, if they're already in pkgdata
how are they not tracked?
Perhaps I don't understand the purpose, and a comment would be helpful.
And for the record, it looks like this PR also fixes #470 by not relying on |
Yes, I'd appreciate the help! Sorry for not finishing + pushing this sooner -- I just started at a new employer and need to jump through a few legal hoops before I can continue contributing to open source again. Shouldn't be a problem but it takes some time. So feel free to beat me to it and finish this one :) |
OK, I'll try to re-read this discussion, paying more attention to details and trying to determine what's missing for this to get merged. But if the next steps are clear in your mind and you can spend a few minutes listing them here, I'd greatly appreciate it! |
@ffevotte thank you! Just tagged you in the conversation that has an actionable. |
OK, I started working on this, but am not sure how to proceed to add commits to this PR. Should I make a PR from my working branch (https://github.com/ffevotte/Revise.jl/tree/tkluck/support-callbacks) to this one? In any case, here is what I have so far:
@timholy @tkluck I think it would be best if you could carefully review this: I fear I'm too new to the Revise codebase to be 100% sure of what I did; I might very well have made a few mistakes (especially regarding that last merge...) (Tests seem to pass on my system (Linux), but |
Thanks @ffevotte! Submitting a fresh PR that preserves authorship would be good. I suspect we're not going to want the full version history, so perhaps squashing all of @tkluck's into a single commit, and then squashing your own changes into a different commit right before we merge your PR makes the most sense. I like your changes. I think the file deletion is actually OK, with the new watching logic the only part of it that it has to cover (?) is whether it should keep watching the file/directory. But since there's no CI for BSD, |
Thanks for the review! Is something like #488 what you had in mind? |
This feature is one half of what
Revise.entr
already does. The functionentr
allows the user to install callbacks for when a file or module changes, and also enters a blocking loop that catches any errors that might arise from the callbacks.This new
add_callback
function allows installing callbacks and keeping them around in the background. This commit also reimplementsentr
in terms ofadd_callback
andremove_callback
.(Basically,
add_callback
now does what I thoughtentr
would do after seeing its signature.)I'm hoping to use this in
HAML.jl
for updating generated code when its source file changes. It may be similarly useful for hot code reloading in web servers, etc.