diff --git a/doc/manual/src/language/operators.md b/doc/manual/src/language/operators.md index ff09e739f..6dcdc6eb0 100644 --- a/doc/manual/src/language/operators.md +++ b/doc/manual/src/language/operators.md @@ -145,19 +145,71 @@ All comparison operators are implemented in terms of `<`, and the following equi | *a* `>` *b* | *b* `<` *a* | | *a* `>=` *b* | `! (` *a* `<` *b* `)` | +Note that the above behaviour violates IEEE 754 for floating point numbers with respect to NaN, for instance. +This may be fixed in a future major language revision. + [Comparison]: #comparison-operators ## Equality -- [Attribute sets][attribute set] and [list]s are compared recursively, and therefore are fully evaluated. -- Comparison of [function]s always returns `false`. -- Numbers are type-compatible, see [arithmetic] operators. -- Floating point numbers only differ up to a limited precision. +The following equality comparison rules are followed in order: + +- Comparisons are first, sometimes, performed by identity (pointer value), and whether or not this occurs varies depending on the context in which the comparison is performed; for example, through `builtins.elem`, comparison of lists, or other cases. + The exact instances in which this occurs, aside from direct list and attribute set comparisons as discussed below, are too dependent on implementation details to meaningfully document. + + See [note on identity comparison](#identity-comparison) below. +- Comparisons between a combination of integers and floating point numbers are first converted to floating point then compared as floating point. +- Comparisons between values of differing types, besides the ones mentioned in the above rule, are unequal. +- Strings are compared as their string values, disregarding string contexts. +- Paths are compared as their absolute form (since they are stored as such). +- [Functions][function] are always considered unequal, including with themselves. +- The following are compared in the typical manner: + - Integers + - Floating point numbers have equality comparison per IEEE 754. + + Note that this means that just like in most languages, floating point arithmetic results are not typically equality comparable, and should instead be compared by checking that the absolute difference is less than some error margin. + - Booleans + - Null +- [Attribute sets][attribute set] are compared following these rules in order: + - If both attribute sets have the same identity (via pointer equality), they are considered equal, regardless of whether the contents have reflexive equality (e.g. even if there are functions contained within). + + See [note on identity comparison](#identity-comparison) below. + - If both attribute sets have `type = "derivation"` and have an attribute `outPath` that is equal, they are considered equal. + + This means that two results of `builtins.derivation`, regardless of other things added to their attributes via `//` afterwards (or `passthru` in nixpkgs), will compare equal if they passed the same arguments to `builtins.derivation`. + - Otherwise, they are compared element-wise in an unspecified order. + Although this order *may* be deterministic in some cases, this is not guaranteed, and correct code must not rely on this ordering behaviour. + + The order determines which elements are evaluated first and thus, if there are throwing values in the attribute set, which of those get evaluated, if any, before the comparison returns an unequal result. +- Lists are compared following these rules in order: + - If both lists have the same identity (via pointer equality), they are considered equal, regardless of whether the contents have reflexive equality (e.g. even if there are functions contained within). + + See [note on identity comparison](#identity-comparison) below. + - Otherwise, they are compared element-wise in list order. [function]: ./constructs.md#functions [Equality]: #equality +### Identity comparison + +In the current revision of the Nix language, values are first compared by identity (pointer equality). +This means that values that are not reflexively equal (that is, they do not satisfy `a == a`), such as functions, are nonetheless sometimes compared as equal with themselves if they are placed in attribute sets or lists, or are compared through other indirect means. + +Whether identity comparison applies to a given usage of the language aside from direct list and attribute set comparison is strongly dependent on implementation details to the point it is not feasible to document the exact instances. + +This is rather unfortunate behaviour which is regrettably load-bearing on nixpkgs (such as with the `type` attribute of NixOS options) and cannot be changed for the time being. +It may be changed in a future major language revision. + +Correct code must not rely on this behaviour. + +For example: + +``` +nix-repl> let f = x: 1; s = { func = f; }; in [ (f == f) (s == s) ] +[ false true ] +``` + ## Logical implication Equivalent to `!`*b1* `||` *b2*. diff --git a/src/libutil/monitor-fd.hh b/src/libutil/monitor-fd.hh index 228fb13f8..ea6e89017 100644 --- a/src/libutil/monitor-fd.hh +++ b/src/libutil/monitor-fd.hh @@ -10,6 +10,8 @@ #include #include +#include "error.hh" +#include "file-descriptor.hh" #include "signals.hh" namespace nix { @@ -19,28 +21,46 @@ class MonitorFdHup { private: std::thread thread; + /** + * Pipe used to interrupt the poll()ing in the monitoring thread. + */ + Pipe terminatePipe; + std::atomic_bool quit = false; public: MonitorFdHup(int fd) { - thread = std::thread([fd]() { - while (true) { + terminatePipe.create(); + auto &quit_ = this->quit; + int terminateFd = terminatePipe.readSide.get(); + thread = std::thread([fd, terminateFd, &quit_]() { + while (!quit_) { /* Wait indefinitely until a POLLHUP occurs. */ - struct pollfd fds[1]; + struct pollfd fds[2]; fds[0].fd = fd; - /* Polling for no specific events (i.e. just waiting - for an error/hangup) doesn't work on macOS - anymore. So wait for read events and ignore - them. */ - fds[0].events = - #ifdef __APPLE__ - POLLRDNORM - #else - 0 - #endif - ; - auto count = poll(fds, 1, -1); - if (count == -1) abort(); // can't happen + // There is a POSIX violation on macOS: you have to listen for + // at least POLLHUP to receive HUP events for a FD. POSIX says + // this is not so, and you should just receive them regardless, + // however, as of our testing on macOS 14.5, the events do not + // get delivered in such a case. + // + // This is allegedly filed as rdar://37537852. + // + // Relevant code, which backs this up: + // https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/bsd/kern/sys_generic.c#L1751-L1758 + fds[0].events = POLLHUP; + fds[1].fd = terminateFd; + fds[1].events = POLLIN; + + auto count = poll(fds, 2, -1); + if (count == -1) { + if (errno == EINTR || errno == EAGAIN) { + // These are best dealt with by just trying again. + continue; + } else { + throw SysError("in MonitorFdHup poll()"); + } + } /* This shouldn't happen, but can on macOS due to a bug. See rdar://37550628. @@ -53,9 +73,16 @@ public: triggerInterrupt(); break; } - /* This will only happen on macOS. We sleep a bit to - avoid waking up too often if the client is sending - input. */ + // No reason to actually look at the pipe FD if that's what + // woke us, the only thing that actually matters is the quit + // flag. + if (quit_) { + break; + } + // On macOS, it is possible (although not observed on macOS + // 14.5) that in some limited cases on buggy kernel versions, + // all the non-POLLHUP events for the socket get delivered. + // Sleeping avoids pointlessly spinning a thread on those. sleep(1); } }); @@ -63,8 +90,12 @@ public: ~MonitorFdHup() { - pthread_cancel(thread.native_handle()); - thread.join(); + quit = true; + // Poke the thread out of its poll wait + writeFull(terminatePipe.writeSide.get(), "*", false); + if (thread.joinable()) { + thread.join(); + } } };