Skip to content

Separate derivation building from the scheduler#12663

Merged
mergify[bot] merged 4 commits intoNixOS:masterfrom
obsidiansystems:local-derivation-goal-encapsulation
Apr 16, 2025
Merged

Separate derivation building from the scheduler#12663
mergify[bot] merged 4 commits intoNixOS:masterfrom
obsidiansystems:local-derivation-goal-encapsulation

Conversation

@Ericson2314
Copy link
Copy Markdown
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.

Depends on #12658
Depends on #12692
Depends on #12697


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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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.

}


void appendLogTailErrorMsg(
Copy link
Copy Markdown
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
Copy Markdown
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.

@dpulls
Copy link
Copy Markdown

dpulls bot commented Mar 19, 2025

🎉 All dependencies have been resolved !

@Ericson2314 Ericson2314 force-pushed the local-derivation-goal-encapsulation branch 5 times, most recently from 99f9331 to 380369d Compare March 26, 2025 19:06
@Ericson2314 Ericson2314 force-pushed the local-derivation-goal-encapsulation branch from 380369d to 681947c Compare April 9, 2025 20:04
@roberth
Copy link
Copy Markdown
Member

roberth commented Apr 9, 2025

First commit LGTM

@roberth
Copy link
Copy Markdown
Member

roberth commented Apr 14, 2025

Second (fe915ad) LGTM

The other parameters it took were somewhat implementation-specific.
@Ericson2314 Ericson2314 force-pushed the local-derivation-goal-encapsulation branch from 681947c to e21057a Compare April 16, 2025 21:01
Copy link
Copy Markdown
Member

@roberth roberth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Third commit

Comment on lines +47 to +56
DerivationBuilderParams {
DerivationGoal::drvPath,
DerivationGoal::buildMode,
DerivationGoal::buildResult,
DerivationGoal::drv,
DerivationGoal::parsedDrv,
DerivationGoal::drvOptions,
DerivationGoal::inputPaths,
DerivationGoal::initialOutputs,
})}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use { .drvPath = DerivationGoal::drvPath, etc syntax?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same in the other constructor

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't work with references :(

This is unreleated to the other commits in this PR.
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.
@Ericson2314 Ericson2314 force-pushed the local-derivation-goal-encapsulation branch from e21057a to 5fbd6cb Compare April 16, 2025 21:41
@Ericson2314 Ericson2314 force-pushed the local-derivation-goal-encapsulation branch from 5fbd6cb to d31e6c4 Compare April 16, 2025 21:49
@Ericson2314
Copy link
Copy Markdown
Member Author

@mergify queue

@mergify
Copy link
Copy Markdown
Contributor

mergify bot commented Apr 16, 2025

queue

✅ The pull request has been merged automatically

Details

The pull request has been merged automatically at b509502

@Ericson2314
Copy link
Copy Markdown
Member Author

With @roberth's approval, I'll just do the commits he didn't get to yet in a separate PR!

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!
@Ericson2314 Ericson2314 force-pushed the local-derivation-goal-encapsulation branch from d31e6c4 to d98c0db Compare April 16, 2025 21:55
@mergify mergify bot merged commit b509502 into NixOS:master Apr 16, 2025
13 checks passed
@Ericson2314 Ericson2314 deleted the local-derivation-goal-encapsulation branch April 16, 2025 22:58
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.

3 participants