-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Separate derivation building from the scheduler #12663
base: master
Are you sure you want to change the base?
Separate derivation building from the scheduler #12663
Conversation
ad62a08
to
1e01ec5
Compare
CC @rickynils and @elaforge, since you were both interested in this sort of modularity work. |
1e01ec5
to
2497082
Compare
virtual void killSandbox(bool getStats) = 0; | ||
}; | ||
|
||
std::unique_ptr<DerivationBuilder> makeDerivationBuilder( |
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.
Because DerivationBuilder
only has one implementation (I assume),
you should perhaps consider making this not a virtual class, but just exposing the symbols.
You could do it as such:
namespace derivation_builder {
struct t;
std::unique_ptr<t> make();
bool prepareBuild(t &);
// etc.
}
There is a minor concrete performance benefit I think, in that from my experience
clang
and gcc
will not inline virtual calls even if the callee is statically known,
but it could inline these with LTO I think, but we don't use that anyway.
Maybe not worth it.
* rather than incoming call edges that either should be removed, or | ||
* become (higher order) function parameters. | ||
*/ | ||
struct DerivationBuilder : RestrictionContext |
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.
Why not make this a class
, if it morally is one?
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 a fan of having RestrictionContext
as a superclass. Why not just add it as a field member?
|
||
const BuildMode & buildMode; | ||
|
||
DerivationBuilderParams( |
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.
Couldn't this be a simple struct without a constructor?
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 if it has references, unfortunately :(
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.
You could just use pointers honestly
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.
We have nonnull or something right?
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 know of a non-null, I've generally been using references to get non-null.
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 think I hallucinated it or had a commit where I added it. Might be worth adding, maybe not. References are in general IMO not well designed though, especially since you can't tell that a function is taking something by reference at the caller site without looking at the type.
You could have
template <typename T>
class ptr {
T& t;
explicit ptr(T& t) : t(t) {}
[..]
}
Then we could use this everywhere instead of references I think and it would Just Work.
In function calls you would do f(ptr(x))
, which would make it clear what's happening too, unless I'm wrong and this doesn't type check.
/** | ||
* Callbacks that `DerivationBuilder` needs. | ||
*/ | ||
struct DerivationBuilderCallbacks |
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.
Couldn't you merge params and callbacks?
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 maybe you shouldn't so params can be a simple struct
*/ | ||
Pid pid; | ||
|
||
DerivationBuilder() = default; |
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 happens again if you omit 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.
I am pretty sure then Impl
cannot construct this abstract class. But yes it is funny to need that.
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 feel like you're supposed to mark this as virtual or protected or both.
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.
There are no virtual constructors, but yes it should be protected
.
* become (higher order) function parameters. | ||
*/ | ||
class DerivationBuilderImpl : public DerivationBuilder, DerivationBuilderParams | ||
{ |
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.
There are a lot of fields remaining, many of which aren't used much of the time I imagine.
Couldn't we try to fix this properly by turning it into a sum type of all the possible states?
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.
Might be better for a future PR though.
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.
Yeah in this one I wanted to mainly figure out what things are used elsewhere, so we can then clean up the internals more easily.
@@ -868,11 +868,7 @@ void runPostBuildHook( | |||
} | |||
|
|||
|
|||
void appendLogTailErrorMsg( |
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.
Is this really a good change?
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.
See the commit message --- the other parameters I think don't belong in the interface but rather in the "closure environment" of the implementation.
Really longer term, this UI-focused thing is a layer violation and should not be in here at all.
🎉 All dependencies have been resolved ! |
The other parameters it took were somewhat implementation-specific.
Now, most of it is in two new functions: `LocalDerivationGoal::{,un}repareBuild`. This might seems like a step backwards from coroutines --- now we have more functions, and are stuck with class vars --- but I don't think it needs to be. There's a few options here: - (Re)introduce coroutines for the isolated building logic. We could use the same coroutines types, or simpler ones specialized to this use-case. The `tryLocalBuild` caller can still use `Goal::Co`, and just will manually "pump" this inner coroutine. - Return closures from each step. This is sort of like coroutines by hand, but it still allows us to stop writing down the local variables in each type. Being able to fully-use RAII again would be very nice! - Keep top-level first-order functions like now, but make more functional. Instead of having one state object (`DerivationBuilder`) for all steps (setup, run, teardown), we can have separate structs for the live variables at each point we consume and return. This at least avoids "are these variables active at this time?" questions, but doesn't give us the full benefit of RAII as we must manually ensure FIFO create/destroy orders still. One thing to note is that by keeping the `outputLock` unlocking in `tryLocalBuild`, we are arguably uncovering a rebuild scheduling vs building distinction, as the output locks are pretty squarely a scheduling concern. It's nice that the builder doesn't need to know about them at all.
We have a new `DerivationBuilder` struct, and `DerivationBuilderParams` `DerivationBuilderCallbacks` supporting it. `LocalDerivationGoal` doesn't subclass any of these, so we are ready to now move them out to a new file!
The building logic is now free of the scheduling logic! (The interface between them is just what is in the new header. This makes it much easier to audit, and shrink over time.)
2497082
to
30b2ae3
Compare
Motivation
Part 1 of #12628. The logic is finally separate!
The interface between
LocalDerivationGoal
and theDerivationBuilder
can almost certainly be improved, but such qualitative changes are best kept separate from this sort of mechanical refactoring.(We probably can just get rid of
LocalDerivationGoal
too, with just aDerivationGoal
andDerivationGoalImpl
with the latter handling both cases.Context
It's probably good to look through each commit one by one.
We might want to also rename
local-derivation-goal.{cc,hh}
, that way the git diffs stay with the larger file (derivation-builder.{cc,hh}
). Or, we can do the copy-then-filter that we did for the original split so long ago.Depends on #12658
Add 👍 to pull requests you find important.
The Nix maintainer team uses a GitHub project board to schedule and track reviews.