Merge changes Ib20e9aa0,I178a038b,I29c7de04 into main

* changes:
  docs: document the actual comparison rules instead of lies
  daemon: remove workaround for macOS kernel bug that seems fixed
  daemon: fix a crash bug "FATAL: exception not rethrown"
This commit is contained in:
jade 2024-07-18 17:40:32 +00:00 committed by Gerrit Code Review
commit 5ee1e6ea98
2 changed files with 108 additions and 25 deletions

View file

@ -145,19 +145,71 @@ All comparison operators are implemented in terms of `<`, and the following equi
| *a* `>` *b* | *b* `<` *a* | | *a* `>` *b* | *b* `<` *a* |
| *a* `>=` *b* | `! (` *a* `<` *b* `)` | | *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 [Comparison]: #comparison-operators
## Equality ## Equality
- [Attribute sets][attribute set] and [list]s are compared recursively, and therefore are fully evaluated. The following equality comparison rules are followed in order:
- Comparison of [function]s always returns `false`.
- Numbers are type-compatible, see [arithmetic] operators. - 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.
- Floating point numbers only differ up to a limited precision. 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 [function]: ./constructs.md#functions
[Equality]: #equality [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 ## Logical implication
Equivalent to `!`*b1* `||` *b2*. Equivalent to `!`*b1* `||` *b2*.

View file

@ -10,6 +10,8 @@
#include <unistd.h> #include <unistd.h>
#include <signal.h> #include <signal.h>
#include "error.hh"
#include "file-descriptor.hh"
#include "signals.hh" #include "signals.hh"
namespace nix { namespace nix {
@ -19,28 +21,46 @@ class MonitorFdHup
{ {
private: private:
std::thread thread; std::thread thread;
/**
* Pipe used to interrupt the poll()ing in the monitoring thread.
*/
Pipe terminatePipe;
std::atomic_bool quit = false;
public: public:
MonitorFdHup(int fd) MonitorFdHup(int fd)
{ {
thread = std::thread([fd]() { terminatePipe.create();
while (true) { auto &quit_ = this->quit;
int terminateFd = terminatePipe.readSide.get();
thread = std::thread([fd, terminateFd, &quit_]() {
while (!quit_) {
/* Wait indefinitely until a POLLHUP occurs. */ /* Wait indefinitely until a POLLHUP occurs. */
struct pollfd fds[1]; struct pollfd fds[2];
fds[0].fd = fd; fds[0].fd = fd;
/* Polling for no specific events (i.e. just waiting // There is a POSIX violation on macOS: you have to listen for
for an error/hangup) doesn't work on macOS // at least POLLHUP to receive HUP events for a FD. POSIX says
anymore. So wait for read events and ignore // this is not so, and you should just receive them regardless,
them. */ // however, as of our testing on macOS 14.5, the events do not
fds[0].events = // get delivered in such a case.
#ifdef __APPLE__ //
POLLRDNORM // This is allegedly filed as rdar://37537852.
#else //
0 // Relevant code, which backs this up:
#endif // https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/bsd/kern/sys_generic.c#L1751-L1758
; fds[0].events = POLLHUP;
auto count = poll(fds, 1, -1); fds[1].fd = terminateFd;
if (count == -1) abort(); // can't happen 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. /* This shouldn't happen, but can on macOS due to a bug.
See rdar://37550628. See rdar://37550628.
@ -53,9 +73,16 @@ public:
triggerInterrupt(); triggerInterrupt();
break; break;
} }
/* This will only happen on macOS. We sleep a bit to // No reason to actually look at the pipe FD if that's what
avoid waking up too often if the client is sending // woke us, the only thing that actually matters is the quit
input. */ // 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); sleep(1);
} }
}); });
@ -63,8 +90,12 @@ public:
~MonitorFdHup() ~MonitorFdHup()
{ {
pthread_cancel(thread.native_handle()); quit = true;
thread.join(); // Poke the thread out of its poll wait
writeFull(terminatePipe.writeSide.get(), "*", false);
if (thread.joinable()) {
thread.join();
}
} }
}; };