-
Notifications
You must be signed in to change notification settings - Fork 26
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
Add revise argument to serve(...) #220
Conversation
I agree with many of your points, and the ability to switch between revised modes with a keyword argument is neat! I think perhaps naming
This is a bit unclear to me. In the
I don't see situations where one would load |
I considered :jit, but I think it's a bit unclear given the multiple things it can be used to evoke. :prerequest. Probably the best combinations for consistency are :jit/:lazy/:eager on the one hand or :prerequest/:background on the other. I'm leaning towards the second, i.e. changing :eager => :background but I can be outvoted e.g. together with @ndortega.
Nevertheless, the Yes they should be immutable. Security problems often happen when several shoulds become turned out they weren't -s i.e. assumptions get invalidated. Also this forces the user to pick a Revise mode, and so sidesteps further discussion about the default. Again I could be persuaded if @ndortega takes your side on this, but I'm fairly strongly convinced that this kind of implicit behaviour usually isn't worth it. In terms of inconvenience of writing a slightly longer entrypoint script, this doesn't have to be the last word: a generic entrypoint that does the correct thing could eventually be bundled with Oxygen; and a default-on behaviour could be added to some future "debug-mode" together with other features such as
This was the old behaviour and also what is being done now. :eager does a superset of what :prerequest does. You said earlier that:
By locking the Revise, we linearise them. This means that each Revise can see the result of the previous Revise. This ensures that Revise does not duplicate work by starting two Revises at the same time. This is analagous to the https://en.wikipedia.org/wiki/Thundering_herd_problem where multiple processes try and recompute a new version of an invalidated cached value simultaneously. I thought this was what you were referring to here? Or do you simply mean that Revise carries on Revising even when it is doing a stale update? Of course this is not ideal, but then again, in some cases Revise can reuse definitions the were unchanged since the last Revise meaning the subsequent Ts are not independent. I've been working with this eager mode and it seems to work well in practice, but it may depend on your exact file saving and code modification patterns. For that reason I think it's good that we have another Revise mode in case this one causes problems for some people. |
I have found
It seems like a bizarre thing to do, and it sounds highly unlikely to happen. It is often the case that HTTP servers are deployed with containers which also manage Julia installation and, by default, A few things can make implicit
I may be wrong, but I suspect Revise already has safeguards to ensure that one
This is what I have meant. Revision events, if Revise is not available, should be dropped when Revise is running, but there seems no harm in adding |
Done
I take your points, but I do still stand behind my argument. One specific counterpoint is that this would introduce environment variables as an alternative configuration method that's only supported for some arguments when currently everything is done through After Julia 1.12, it will become easier for Oxygen.jl to provide its own generic entrypoint. At this point command line based configuration method can be added. Let's pause this point until we get a third opinion?
I don't see any. There are locks to do with requires and adding new packages to the watch list. Please do go ahead and do your own analysis and/or bring this up in the Revise issue tracker. Locking the whole of each full Revise would may be a big change change so would probably take a while including lengthly debate and might be a big enough change to need a new major release. Let's not block on this so we can 🚢 |
Thanks for adding the implementation and some docs to go along with it! Feedback:
Questions:
Ideas:
|
I've tested this, and we can either have something like this:
In this case the route is not registered and so opening up We can fix this by putting it in
But then Revise doesn't see updates. Possibly because it has been executed in the context of the
I'm unclear on what that would look like both from the user's perspective and from an implementation perspective. Is it that you want everything to be in a single file? While not knowing the details, I will make a guess that generated modules like this wouldn't be able to benefit from precompilation. Then again, perhaps if @JanisErdmanis knows a lot about this then he could make a follow-up PR after this one is merged to add this in. In terms of removing boilerplate, one perspective is that by moving to the module format like this, eventually the entrypoint script can be removed in the simple case and replaced with a generic entrypoint which takes the target module as an argument. This puts us back to a single file. In summary, my current perspective is that |
The idea I had about evaluating into a module that Revise could track is covered with Regarding the issue, I am still curious whether it can cause a race condition and crash Julia when |
The usage of includet is recommended against in the Revise documentation. They recommend using a package + module. That way you get optimizations like precompilation. In addition this still requires 2 files. Now we have an entrypoint script, and the file with the routes targetted by However, in case this is something people are interested in supporting, they could test it out, add it to the documentation and detect this case and remove the warning for this case in a follow-up PR.
The normal usage of Revise e.g. in the interpreter is to be (automatically) called only in the interactive thread. The multithreaded behaviour of Revise is a bit beyond the scope of things here. I guess not many people understand its implementation well enough to answer exactly what cases are protected against. The locks, however, are not there to protect data structures against access from multiple threads, they're to serialise revisions in order prevent duplicated work. This is relevant also for single-threaded execution where multiple tasks can still interleave. This is important, since I have read enough of Revise's source code to determine that any locking is at a more fine grained level and does not lock the whole call to Revise and so multiple threads could start the first stage of Revise, deciding which parts of the code need to be revised at the same time. |
Okay I've asked here: https://discourse.julialang.org/t/is-it-safe-to-run-revise-from-multiple-threads/119743 |
Julian Samaroo from Slack #helpdesk agreed that there is probably an issue with Revise's internal data global state being manipulated. So I guess together with the thundering herd issue, there are two good reasons to keep the locks. |
Issue filed with Revise here: timholy/Revise.jl#845 |
I also looked into the discussion on the Slack. The lock on |
It looks like the next release of Revise will have internal locking of |
Excellent! We were drawn into details where we tried to fix the use of I hope we can merge this PR. Further improvements to address a major use case do not seem to conflict with what has already been done here. |
I'm a bit surprised to hear your say this. I was definitely thinking you had this in mind when bringing up concurrency issues. Anyway, we've made Revise add locks now, so I've just added a warning that it won't work with old Revise. It may be useful for speeding things up in development. Let's merge? |
My only complaint about the |
@JanisErdmanis No worries! It resulted in me uncovering a few edge cases in Revise that will hopefully help with its reliability long-term and maybe someone benefits from parallel Revise in the future. @ndortega I think the tests need to be re-run, but you need to be the one to do this. |
@ndortega Friendly bump |
@frankier |
I did consider adding some kind of test test, but I guess I wasn't sure how to add a meaningful test here without mocking Revise or starting a new Julia process within the tests, both of which are quite a pain/potentially fragile. The reason a new process would have to be started in testing to use real Revise is that the whole implementation is quite dependent on loading order of modules. For mocking in Julia there is either SimpleMock.jl which doesn't work anymore or Mocking.jl which requires mock targets to by marked with So it seems to me there's no nice option here, and that maybe leaving it untested for now was overall a lesser evil, but if you have some idea for a reasonably maintainable/non-fragile way then I could also help add the test. |
Fixes #122
@JanisErdmanis This implementation is guided quite a bit by your ideas:
There's some differences too:
@ndortega I haven't added Revise as a dependency, since I think if Oxygen imports Revise it is could be too late. As a side benefit, people can keep their production Oxygen project completely Revise-free, which as said seems like it could be a useful defense-in-depth precaution.