-
Notifications
You must be signed in to change notification settings - Fork 176
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
Dynamically load middleware #438
Conversation
Current flame graph. No cider middleware there except the very light pprint and version. I am done here. Not optimizing the debugger in any specific way as it loads too many heavy things, and should be mostly dealt from emacs side anyway. |
Hmm, that was one unexpected change. I'll try to take a look at the code later today. I was under the impression dynamic middleware loading was problematic and I'm curious to see what approach did you take. |
88aeaf9
to
055d282
Compare
@bbatsov Any update here? Seems like a real quality-of-life improvement if it could be merged. |
It's a huge change and I haven't had time to carefully look into it. I don't make big changes lightly because there might be all sorts of unforeseen implications. It seems that no one else has looked into the PR as well, looking at the feedback it has gathered. |
Touché 😉 |
I agree with Bozhidar. |
So, I finally had the time to look more closely into this and here are my main concerns:
In general I really wonder if it's our place to jump over such hoops to make deferred middleware loading a reality. Seems to me something like this should be part of the core nREPL functionality. I'd love to hear from @cemerick on this. Anyways, let's see what @Malabarba thinks about those changes as well. They are certainly pretty useful, but come at the cost of more complicated codebase. For me the slow startup times of the REPL are not that big of an issue as you don't really restart it that often so I wonder how far should we be willing to go to defer the loading of the middleware. P.S. To be clear - I'm not opposed to merging this, but I think there's room for improvement at this point. |
[cider.nrepl.middleware.version] | ||
[cider.nrepl.print-method])) | ||
|
||
(def DELAYS (atom nil)) |
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.
That's one extremely obsure name, without a docstring.
|
||
(defmacro ^{:arglists '([name handler-fn descriptor] | ||
[name handler-fn handle descriptor])} | ||
def-wrapper |
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.
Probably def-middleware(-wrapper)
or def-nrepl-wrapper
would be a better name for this.
cider.nrepl.middleware.ns/wrap-ns | ||
cider.nrepl.middleware.spec/wrap-spec | ||
cider.nrepl.middleware.out/wrap-out | ||
:dev {:repl-options {:nrepl-middleware [cider.nrepl/wrap-apropos |
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 definitely don't like that all the middleware wrappers ended up being in some utility namespace.
(or (resolve sym) | ||
(throw (IllegalArgumentException. (format "Cannot resolve %s" sym))))) | ||
|
||
(defmacro run-delayed-handler |
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'm not sure run-delayed-handler
is the most appropriate name for 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.
This macro does a bunch of things at compile time, but the name is highly suggestive for what happens at run-time. Have a look at the usage. I find it ok, but wouldn't mind a better name though.
|
||
(defmacro run-delayed-handler | ||
"Make a delay of `fn-name` and place it in `DELAYS` atom at compile time. | ||
Require and invoke the delay at run-time with arguments h and msg." |
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.
What are h and msg? 😃
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 is an internal function. Definition of h and msg are in def-wrapper, if not it should be improved. Will smooth this out.
|
||
;;; Wrappers | ||
|
||
(def-wrapper wrap-debug cider.nrepl.middleware.debug/handle-debug |
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 like that all the middleware definitions are dumped in a file used for utility functions.
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.
Not sure I understand. That is a declaration of the middleware files. We can rename it, but i think nrepl.clj
is good enough. What utility functions? There are no other utilities there except def-wrapper
which is used for defining wrappers.
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.
My bad. I just saw what the file originally just defined the default handler.
{:doc "Provide instrumentation and debugging functionality." | ||
:expects #{"eval"} | ||
:requires #{#'pprint/wrap-pprint-fn #'session} | ||
:handles {"debug-input" |
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 wonder if those definitions should mark just the ops something handle and obtain the full metadata from the actual middleware descriptor which would be defined as usual. Overall I really hate that the middleware implementation and definition are split and as I result you have to jump to two places to figure out how something is supposed to behave.
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 wonder if those definitions should mark just the ops something handle and obtain the full metadata from the actual middleware descriptor which would be defined as usual.
This is just not possible. See my main answer. In order to get the metadata of something, you need to load the file where that "something" is declared.
Overall I really hate that the middleware implementation and definition are split and as I result you have to jump to two places to figure out how something is supposed to behave.
This is not as bad as you make it sound. Logic is in one place, exposed API declaration is in another place. It's actually a plus IMO.
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.
Well, those things are always subjective. 😃 Anyways, I can live with this for now. Unfortunately I don't really have time to work on this and I'm not the type of person who'd just complain something instead of do it better themselves.
@@ -41,7 +41,7 @@ | |||
and may be case senstive. Types returned correspond to Apropos types. | |||
Docstring search returns the full doc; symbol search returns an abbreviated | |||
version." | |||
[ns query search-ns docs? privates? case-sensitive? filter-regexps] | |||
[{:keys [ns query search-ns docs? privates? case-sensitive? filter-regexps]}] |
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 saw this change at few places and it seems somewhat unrelated, so it should probably be in a separate commit for clarity's sake.
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 remember exactly anymore, but I am pretty confident that this change is needed to make the handle-apropos
behave as all other handle-xyz
functions (i.e. take in two args - an nrepl handler and a map msg).
I've added a few remarks inline. Also - this PR should probably undo the delayed loading of deps done by some previous PRs. |
These are stylistic concerns and I think we should not put too much weight on them in order to decide on this. There are some notable benefits of the proposed organization as well:
No objection, but it should be part of the same global "middleware" declaration file (see below).
Not all at the moment but almost all. I think all of them should be actually declared for organizational reasons - either all or nothing IMO. Otherwise it would create extra layeir of complexity. Each time you would have to decide whther to auto-load or not. Future changes to middleware might introduce heavy changes. External users and developers of the middlware have to deal with different naming conventions of the middleware.
I consider this a pretty straightforward change, so I frankly don't see any major "hoops". I am actually pretty surprised that you see this as "complicated".
I am afraid that won't solve your stylistic concerns. Even if nrepl itself comes with some kind of solution you will still need to declare something in a separate file from the main logic. You cannot load part of the file and later load the rest. These are irreconcilable targets.
Adding the following is probably a good idea:
|
Just to define some terms as I'll use them (and as they've been used by a couple of others that've worked on things similar to this):
This PR looks to accomplish the former for a cider stack. To answer @bbatsov's question, dynamic loading will definitely be part of nREPL. Phil has worked on this more than I have and has a couple of concrete proposals, so the particular approach isn't set in stone, but the result would also satisfy the objectives of any deferred-loading change. (Since a dynamically-loaded middleware wouldn't be To be clear, I am not saying "don't merge this". If loading the code that supports cider's middleware stack is determined to be a serious UX problem for the community here, then godspeed. |
Unless you generate this during say build time of the artifact. :-) There's always more than one way, but this is definitely the simplest one - I'm not arguing with this. |
@cemerick Thanks for your input!
Looking forward to this! In the mean time I'll merge this as I believe the benefits outweigh the drawbacks. Let's see if this is going to cause any practical issues for our users. @benedekfazekas @expez I think you should switch to this same style for |
@vspinu Thanks for another great contribution! |
Ohh. I though there is some more work to be done here. Particularly:
Do you have an opinion on this? |
I like both ideas! I haven't actually pushed a new build out, so the end users won't see the impact of this commit for a while. |
what is the cljr version you are testing with? try with latest snapshot in case you are using an older version there was a PR merged not long ago with delayed loading of some nses... |
Yeah, much better with
|
no worries although I only merged that PR. so thanks @ryfow |
Is this a slow machine or a big project? On my i7 laptop it takes 4s with no project and 5-6 sec on cider-nrepl itself. How long does it take for the plain |
This is the actual (perceived) time. I don't use Leiningen much, but in Boot with or without the newest cider-nrepl the time is almost the same. |
That's super odd. What's the time in boot with and without |
@bbatsov Sorry, I must have confused you. The startup time with the latest cider-nrepl and without any cider-nrepl at all is (almost) the same. This is what is expected (and awesome)! |
Addresses #218 and clojure-emacs/cider#1717.
It now takes only 0.25s for me to load
cider.nrepl.clj
. So startup time is now compatible withlein repl
with an empty project. Some extra work could be done on emacs side to avoid loading extra things (like debugger) on startup.I will pile a couple more related commits here in the following days to further fix loading of the debugg.clj, error_handling.clj and maybe something else.