-
-
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
Recursive nix #213
Recursive nix #213
Conversation
Ah, forgot to mention, as a quick check I ran |
Ah, just realized that this doesn't make the new paths available in chroot. I'll work on a fix to that (which will probably revert the temproots change in favor of a new way of reporting paths), but what's here is still valid. |
I've rebased and added commits to make it so new paths are available in the chroot. The recursive paths reporting protocol has changed slightly: The client can only write in chunks of at most |
cc @civodul for the slight protocol change. |
Ack, still broken, chroot builds are done in a private mount namespace so bind mounts done in the parent aren't seen. Will fix later. |
If NIX_REMOTE=recursive, the client checks NIX_REMOTE_RECURSIVE_PROTOCOL_VERSION for an integer version. If it is set and its major version matches the client major version (current protocol version 0x101), it then reads a connected unix domain socket file descriptor from NIX_REMOTE_RECURSIVE_FD. It sends a message containing the client protocol version in the message data and a connected unix domain socket in the ancillary data, and the other end of that socket is then used for the normal daemon protocol.
With recursive nix, it is possible for a build to legally gain access to paths that weren't specified as its inputs (indeed, that is the whole point). In order for the nix process that started the build to be able to scan for dependencies properly, it must be told when these new paths are (potentially) accessed. This amends version 0x101 of the recursive protocol to put the socket fd into NIX_REMOTE_RECURSIVE_SOCKET_FD and to put another fd into NIX_REMOTE_RECURSIVE_PATHS_FD. This new fd is assumed to point to a new kind of temporary gc root, where the root is kept from being considered invalid by the parent nix process staying alive and thus only a write lock is needed to avoid a race between the gc reading the root file and a process adding to it. Upcoming commits should clarify this if it's confusing.
nix-daemon now supports listening on an already-connected socket. In the normal case, nix-daemon will listen on its traditional socket (passed through systemd or not) and makes a new socketpair for listening for the new kind of connection. If, however, nix-daemon was started with NIX_RECURSIVE_FDS set, it uses the two fds listed in that env var as the two ends of the connected socket and doesn't listen on the traditional socket at all. In a future commit, LocalStores that were *not* launched from a nix-daemon child process will launch nix-daemon with that env var properly set if they need to perform a build. In either case, nix-daemon now has a connected socket to read from and possibly an unconnected socket it's listening to to connect to. When the connected socket is readable, it reads the client recursive version from the message data and the client domain socket from the ancillary data to form a new connection. If the listening socket is connectable, a connection is formed as usual. Finally, when a child process is launched, its LocalStore gets passed the other side of the connected socket so it can use the existing nix-daemon process for recursive nix instead of having to spawn a new nix-daemon process every time (to be implemented in a future commit)
This will allow one process to keep several temproots files alive, as is required for recursive nix. The semantics are as follows: * When creating its first temproot file, a process takes the gc lock, creates /nix/var/nix/temproots/{pid} directory (deleting it if it exits), takes a read lock on /nix/var/nix/temproots/{pid}/lock, and releases the gc lock * Before adding a new temproot file (including the first one), the process upgrades its read lock on /nix/var/nix/temproots/{pid}/lock to a write lock. It can then make as many temproot files in /nix/var/nix/temproots/{pid}/lock as it likes, so long as they are not named "lock", and then it downgrades the write lock to a read lock * Before adding a new root to a temproot file, the process takes a write lock on the file. Afterwards, it releases the lock. The locking process *never* holds a read lock on individual temproot files * For each directory in /nix/var/nix/temproots, the gc first tries to get a write lock on /nix/var/nix/temproots/{dirname}/lock. If it succeeds, or if the lock file does not exist, the directory must be stale so it deletes it and moves on. If it fails, it waits until it can get a read lock on /nix/var/nix/temproots/{dirname}/lock (after which the owning process can't create more temproot files until released) and then iterates through every file not named "lock" in /nix/var/nix/tmproots/{dirname}, taking a read lock on the file then reading roots out of it as before. The gc does not release any of the read locks it takes here until collection is done. For now, local-store uses /nix/var/nix/temproots/{pid}/main for its temproots file, but future commits will also put recursive nix temproots there.
This involves: * Setting up the NIX_STORE_PATH etc. env vars to match the current process * Packing settings into _NIX_OPTIONS * Setting NIX_REMOTE=recursive and NIX_REMOTE_RECURSIVE_PROTOCOL_VERSION to the protocol version * If this process is not itself a daemon worker and it has not started a recursive nix daemon instance yet, making a new unidirectional socketpair and a pipe, sending both ends of the socketpair and the read end of the pipe to a new nix-daemon process in NIX_RECURSIVE_FDS, keeping the write end of the pipe open (so that nix-daemon can know if/when we die), and using the write end of the new socketpair as the store's recursive nix daemon socket * Setting NIX_REMOTE_RECURSIVE_SOCKET_FD to the store's recursive nix daemon socket (and keeping it alive past closeMostFDs) * Creating a new temproots file with the same name as this drv's path * Setting NIX_REMOTE_RECURSIVE_PATHS to the new temproots file (and keeping it alive past closeMostFDs) * After the build (if not using the build hook, which for now must use nix-store --import if it wants to report references not in the inputs closure), read through the new temproots file, add the closure of each temproot (include outputs of derivations) to allPaths, and add each path as a temproot so we can delete the new temproots file This required adding functions to gc.cc to create and delete arbitrary temproots files (which addTempRoot uses to creat the main temproots file). nix-daemon was updated to add the third fd in NIX_RECURSIVE_FDS (the read side of the close notification pipe) to its select set so that it can exit once its parent dies (closes the write side of the pipe). Now recursive nix should work! Next up: Test case.
…ons don't step on each other
This allows the parent nix process to add any needed new paths to the chroot. The reporting protocol is changed to have a cap of 4096 bytes per write and to require reading a single confirmation byte after each write. Temp roots handling is reverted back to the pre-recursive-nix way, since we no longer create a new temp roots file for each build.
Also fix a dumb bug
Recursive path reporting is expensive, especially in the chroot case. So now we only report valid paths and are less defensive about derivations doing stupid things with paths they aren't sure are valid.
Builder processes can purposefully cause an exception to be thrown in any number of ways, so don't let a rogue builder kill an entire store process.
Rebased and pushed some fixes. Now this works with and without chroot enabled. Note that only valid recursive paths should be reported now, and that generally you only need to report a recursive path if you might reasonably later assume the path exists (so in fewer cases than before). |
BTW @edolstra I think this is ready for merge now, though of course you'll probably want to wait until after 1.7 |
Probably best to do it after 1.7. |
Any comment on my proposal at #13 ? Maybe a builder written in nix itself might make this obsolete on the long term. |
Take the example use case of a package that comes with its own nix expressions. How does your suggestion solve the problem of needing to download and unpack the tarball before those expressions can be evaluated? |
I don't know the implementation details behind nix builds. However, if you reference a package Now with a nix builder and a dynamic .nix expression:
That's certainly possible to do, after all the builder is just like any other .nix expression, except it's triggered when the derivation is needed. However that imports the .nix in the nix environment the builder is running in, just like any other callPackage. |
As discussed on IRC, I get the following on MacOSX whenever I run
Possibly unrelated, but I get the following with error: disabling reads from fdRecursiveTo: Socket is not connected |
@charleso What version of OSX? I haven't tested yet but this looks like a bug in |
OSX 10.9.2. |
@edolstra I have to bring this up to date and fix some bugs, but before I do that can you comment on whether this is something you want/the general approach looks good? |
@edolstra ping? |
Do you have any examples of how this will be used? |
Most of my experimenting also uses my
Without the recursive derivations stuff, if you replace the nix-instantiate with a nix-build the above two envs will be functionally equivalent. Besides nix-make, I'd also like to use this in conjunction with a to-be-written qemu backend to test nixops networks in hydra. If running nix commands just worked inside of a build, then you could just run nixops create and nixops deploy and have a network created. |
How would the inner build.nix find its dependencies (e.g. Nixpkgs)? |
You'd have to make it available as an input, forgot that bit. So you could add |
The output path of the outer derivation will still change, right? And if that's used as a dependency by lots of other packages, you might still be looking at a lot of rebuilds. |
Right, that's why the next step is the An illustration might help: Suppose
This way, even though the inner evaluation is done at realisation time (step 1), the dependency graph is rewritten as if it had been done as part of the initial evaluation, and so changes in nix expression dependencies that don't affect the path of the On top of this machinery, |
@edolstra ping? |
@edolstra I would really like to bring this up-to-date and get |
Guessing this is not wanted. |
:( |
I'm bummed to see this PR end this way, given the effort that went into it, in terms of both code/commits and communication. I can't speak for @shlevy, but I know that I'd much rather be told that my ideas are poorly conceived (and the reasons why) than feel left hanging. From a marketing standpoint, such silence suggests that involvement in this project is risky business - you might invest your time in some new feature, only to either abandon it or decide to fork nix/nixpkgs (if you do the latter, you're now encumbered with maintaining the fork, and it just might turn out that it was a bad idea anyway - but you could hardly be blamed, as no one told you why). @edolstra: I really, really want to see nix grow, because in purely subjective non-technical terms, it's absolutely fucking awesome. What can we - the community - do to improve communication? |
I agree with @cstrahan. Probably better to discuss it in mailing list? |
Is it worth leaving this open until the discussion is resolved? Seems like a pity for this to get lost. |
This is something I really want. Can we reopen the PR? I don't expect @shlevy will want to take care of bringing it up to date, but I (or somebody else) might. |
I suppose I can take the same branch and open a new PR for it from my own fork. Would be nice to retain the comment history though. |
@copumpkin I've added you as a collab on my fork so you can just reuse this PR. Please also check out my |
This is sad and unhealthy situation. Really demotivates to contribute. |
@nmikhailov I definitely agree about the organizational aspects of what you're saying, but on the specifics of this PR, not all is lost! Perhaps @shlevy can elaborate on what aspects of recursive nix still need to exist after the new primop, and someone (maybe me, except I never have free time) can do the work to adapt this to reflect it. |
@copumpkin Well that's nice to hear regardless of general organizational issues. |
I've tried quite hard over the last few days to resurrect this branch and make it mergeable with 2017's HEAD. Unfortunately there seems to just be too much churn in The best path forward might be to use this PR as an inspiration for a re-implementation. |
Convert the remaining toplevel files
This PR makes it possible to run nix from within a nix build. This is useful for things like using nix as a low level build tool, and is usable in places where import-from-derivation isn't (see #13 for more details).
This is done by introducing a new remote mode for nix. If
NIX_REMOTE
isrecursive
, then the remote client (e.g.RemoteStore
orguix
) should do the following:NIX_REMOTE_RECURSIVE_PROTOCOL_VERSION
as the ASCII representation of an unsigned int. This is the version of the remote protocol in use by the parent nix process (currently0x101
)NIX_REMOTE_SOCKET_FD
as the ASCII representation of a file descriptor. This is a writable unix domain socket (note that this can't be used as the daemon socket directly as multiple processes may use the same fd).socketpair
) in the ancillary data to the fd found inNIX_REMOTE_RECURSIVE_SOCKET_FD
. The socket that the sent socket is connected to can then be treated as a freshly-connected socket for the daemon protocol.NIX_REMOTE_RECURSIVE_PATHS_FD
as the ASCII representation of a file descriptor. This file descriptor should be used to tell the parent nix process of new inputs that it should scan for when looking for references in this build's outputs (which is needed since recursive nix can make store paths that aren't inputs to the build accessible to it, which is really the whole point). Any paths so reported are also guaranteed not to be garbage collected until after the reference scan. Before appending (the fd is opened withO_APPEND
), you must take anfcntl
write lock on the file. The file format is just a sequential series of null-terminated store paths. TheRemoteStore
implementation collects every newly-discovered path inPathSet
throughout its lifetime, then in the destructor reports them all at once to avoid holding the lock for a long time, but that isn't necessary.And then the daemon protocol is followed as usual. This required several changes on the part of the parent nix process:
nix-daemon
also creates a pair of unix sockets to receive recursive nix connections on. It passes the write end of the pair to all childrenLocalStore
instances.LocalStore
setsNIX_STORE_DIR
and friends to the values appropriate for this store instance,pack
s thesettings
into_NIX_OPTIONS
(seeSettings::pack
andSettings::unpack
), and setsNIX_REMOTE
torecursive
and puts the protocol version inNIX_REMOTE_RECURSIVE_PROTOCOL_VERSION
. It also creates a new temproot (named after the derivation file for guaranteed uniqueness) and puts that intoNIX_REMOTE_RECURSIVE_PATHS_FD
. If this is the first build, it then checks if it was passed a socket upon construction. If it was, that means this is a child of anix-daemon
process and it passes that socket inNIX_REMOTE_RECURSIVE_SOCKET_FD
. If it wasn't, then it creates a socket pair and a pipe, puts both ends of the socket and the read end of the pipe intoNIX_RECURSIVE_FDS
(space separated) and startsnix-daemon
. WhenNIX_RECURSIVE_FDS
is set,nix-daemon
does not listen on its traditional socket and instead uses the passed fds to listen for recursive nix connections. The read end of the pipe is used bynix-daemon
to know when the parent process has died so it knows to exit. The parentLocalStore
then closes the read end of the pipe and socket pair, purposefully leaks the write end of the pipe (though it isO_CLOEXEC
), and uses the write end of the socket pair asNIX_REMOTE_RECURSIVE_SOCKET_FD
for all builds.LocalStore
reads the temproot for that build, adds the closure of each path written there (including outputs if any are derivations) to its temproots and to thePathSet
used when scanning for references, and then deletes the temproot.cc @civodul for
guix
Fixes #13