Skip to content
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

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

Ericson2314
Copy link
Member

@Ericson2314 Ericson2314 commented Mar 17, 2025

Motivation

Part 1 of #12628. The logic is finally separate!

The interface between LocalDerivationGoal and the DerivationBuilder 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 a DerivationGoal and DerivationGoalImpl 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.

@Ericson2314 Ericson2314 requested a review from edolstra as a code owner March 17, 2025 04:00
@github-actions github-actions bot added the store Issues and pull requests concerning the Nix store label Mar 17, 2025
@Ericson2314 Ericson2314 force-pushed the local-derivation-goal-encapsulation branch from ad62a08 to 1e01ec5 Compare March 17, 2025 04:05
@Ericson2314
Copy link
Member Author

CC @rickynils and @elaforge, since you were both interested in this sort of modularity work.

@Ericson2314 Ericson2314 force-pushed the local-derivation-goal-encapsulation branch from 1e01ec5 to 2497082 Compare March 17, 2025 15:10
virtual void killSandbox(bool getStats) = 0;
};

std::unique_ptr<DerivationBuilder> makeDerivationBuilder(
Copy link
Member

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
Copy link
Member

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?

Copy link
Member

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(
Copy link
Member

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?

Copy link
Member Author

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 :(

Copy link
Member

@L-as L-as Mar 18, 2025

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

Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

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
Copy link
Member

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?

Copy link
Member

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;
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

@L-as L-as Mar 18, 2025

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.

Copy link
Member Author

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
{
Copy link
Member

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?

Copy link
Member

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.

Copy link
Member Author

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(
Copy link
Member

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?

Copy link
Member Author

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.

Copy link

dpulls bot commented Mar 19, 2025

🎉 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!
This is done to prior to splitting, just like
05cc5a8 for
68f4c72.
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.)
@Ericson2314 Ericson2314 force-pushed the local-derivation-goal-encapsulation branch from 2497082 to 30b2ae3 Compare March 21, 2025 16:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
store Issues and pull requests concerning the Nix store
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants