Hacker News new | past | comments | ask | show | jobs | submit login

Off-topic question about Nix. I understand it works by redirecting symlinks from, say, one version of a package's files to another.

Isn't there a race-condition here -- like if I invoke a program at the wrong time while it's in the process of changing symlinks, could it pick up the wrong libraries or something? Does Linux OS allow a "changeset" of files to be locked and altered together in a batch, or anything like that?




I don't think "redirecting symlinks" is a totally accurate way of describing how Nix works.

Each Nix package, as packaged, has a hard-coded dependency on the specific hashed versions of all its dependencies. For instance, if you're building NPM and its build scripts hash to abcd1234, and it depends on Node.js whose build scripts hash to dcba4321, then /nix/store/abcd1234-npm-1.0/bin/npm has a hard-coded reference to /nix/store/dbcd4321-nodejs-1.0/bin/node.

Symlinks come into play with user profiles - you don't want to type in that full path to npm every time, so you put ~/.nix-profile/bin on PATH and you tell Nix to make ~/.nix-profile/bin/npm a symlink to /nix/store/abcd1234-npm-1.0/bin/npm.

But there isn't a race condition in the underlying packages, which are all immutable and more importantly co-installable. If you want to upgrade to Node.js 1.1 and its hash is fedc9876, then it gets installed to /nix/store/fedc9876-nodejs-1.1. And if you rebuild npm against that (even without changing its version), the hash of npm's build scripts changes as a result, and it ends up in, say, /nix/store/aaaa1111-npm-1.0.

If you upgrade your personal profile to the latest version of everything, then Nix will install those two new directories into /nix/store, repoint the symlink in ~/.nix-profile/bin/npm, and then (eventually) garbage-collect the two old directories.

But at no point does the execution of code within Nix rely on mutable symlinks. (As far as I know.)


The reason that there's no race condition is that you're not "repointing ~/.nix-profile/bin/npm", instead, .nix-profile itself is a symlink, so the entire PATH is changed atomically.


Nix doesn't work by redirecting symlinks, much less between different versions of the same package. So there is no race condition as you describe.

Simply, if package A depends on package B then A's files will end up mentioning the absolute path to B directly (say, /nix/store/abcdef-b-1.0/bin/some-bin-file, where "abcdef" is a hash).

If package C depends on a different version of package B, then C's files will contain the path to a version of B with another hash and possibly a different version number (say, /nix/store/zywxabc-b-1.1/bin/some-bin-file).


Thanks, let me clarify my question with a proper example. Let's say I install A-1.0 and A-1.1. Both versions will depend on say a resource file they expect is at /usr/share/A.res

I was thinking that nix would symlink /bin/A -> /nix/store/abcdef-A-1.0/bin/A, and also /usr/share/A.res -> /nix/store/abcdef-A-1.0/usr/share/A.res

So when I'm upgrading to A-1.1, maybe the /bin/A symlink would update a moment before the /usr/share/A.res symlink, which means invoking A at around the same time as the upgrade could pick up the wrong resource.

Do we just try to make sure that binaries know to look for resources relative to their binary-path? Or do we use chroot/containers? Sorry if this is a dumb question :)


So, in Nix (and in NixOS) there is no /bin and there is no /usr/share.

So either A.res gets installed at /nix/store/abcdef-A-1.0/share/A.res and at /nix/store/xyw123-A-1.1/share/A.res, in case A.res is part of A, or, if it's considered a dependency, then it gets installed at /nix/store/jgh456-some-other-package-2.3/share/A.res which both A-1.0 and A-1.1 depend on.

Also, A-1.0 and A-1.1 may depend on exactly the same "some-other-package" or they may depend on different versions, which would be OK since the hash of some-other-package will be different for different versions (or different variations of the same version), so there would never be a conflict.

As other posters mentioned, there is something called a user profile (and a system profile for NixOS) which does contain symlinks to the final binaries that are supposed to be in $PATH for some user (or all users, in the case of the system profile).

But these are only symlinks to the top-level binaries. The binaries themselves (and all their dependencies, including data and other binaries) contain hardcoded paths, they don't get redirected through symlinks. So once the top-level symlink gets resolved by your shell, everything is hardcoded from then on, no package sees some half-between state of their data or their dependencies' data or binaries.

You could even have multiple versions of the same package running at the same time without any conflict, as long as their mutable data is stored in different directories. For example, you can have different versions of Firefox running at the same time, as long as they use different Firefox profiles (if you try to run the same Firefox profile with different versions of Firefox at the same time, the second Firefox will complain that it is already running, as it should -- this is not very different from trying to run the same program twice at the same time).

And also, these user profiles are themselves updated/modified atomically through a single symlink, so your effective $PATH is either the old one or the new one, but never anything in-between.

PS: to answer your question, yes, we do indeed make sure that binaries know to look for resources in the right place.

Rather than look for the files relative to their path, usually packages have a "./configure" script which allows someone to tell where to install resources such as data files, man pages, etc. Usually people install resources in /usr/share or /usr/local/share (or even $HOME/share in some cases). In Nix, we just tell the package to install their files in /nix/store/abcdef-A-1.0/share and binaries will usually know where to find these resources based on the path provided to ./configure (and if for some reason they don't, we do patch source files to make sure that they do).


Ah thank you SO much, this is extremely helpful. I really appreciate you taking the time to explain that!!


Nix uses symlinks to create user environments, mostly. The rest all points to unique paths in `/nix/store`. Sometimes, environments are also used for applications, mostly modular stuff.

Take a Python service started by systemd. The systemd ExecStart points directly to the immutable Nix path of the script, and the script also has a shebang that points directly to the immutable Nix path of the Python interpreter. (The Python interp in turn also links to libraries via direct Nix paths, etc.)


After the profile is built, there is only one link that needs to be changed to activate it, so it's atomic.


not sure if I completely understand the race you describe, but I don't think there's any race. the paths and links are created at build/install time. nothing is switching around after the expression has been applied.

edit: maybe you're describing what happens if a program is run while a Nix expression is being applied; I believe what happens in that case is that the program that was being run will work fine since its environment is pointing to the previous generation, and applying the new expression creates a new generation


Isn’t that a possibility with any package manager?




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: