Merge pull request #8049 from edolstra/unexpected-eof

Fix "unexpected EOF" errors on macOS
This commit is contained in:
Eelco Dolstra 2023-03-16 16:13:42 +01:00 committed by GitHub
commit 7f46ebcf90
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 48 additions and 64 deletions

View file

@ -27,18 +27,6 @@ run_test () {
run_test run_test
# Hack: Retry the test if it fails with “unexpected EOF reading a line” as these
# appear randomly without anyone knowing why.
# See https://github.com/NixOS/nix/issues/3605 for more info
if [[ $status -ne 0 && $status -ne 99 && \
"$(uname)" == "Darwin" && \
"$log" =~ "unexpected EOF reading a line" \
]]; then
echo "$post_run_msg [${yellow}FAIL$normal] (possibly flaky, so will be retried)"
echo "$log" | sed 's/^/ /'
run_test
fi
if [ $status -eq 0 ]; then if [ $status -eq 0 ]; then
echo "$post_run_msg [${green}PASS$normal]" echo "$post_run_msg [${green}PASS$normal]"
elif [ $status -eq 99 ]; then elif [ $status -eq 99 ]; then

View file

@ -35,7 +35,7 @@ HookInstance::HookInstance()
/* Fork the hook. */ /* Fork the hook. */
pid = startProcess([&]() { pid = startProcess([&]() {
commonChildInit(fromHook); commonChildInit(fromHook.writeSide.get());
if (chdir("/") == -1) throw SysError("changing into /"); if (chdir("/") == -1) throw SysError("changing into /");

View file

@ -292,7 +292,7 @@ void LocalDerivationGoal::closeReadPipes()
if (hook) { if (hook) {
DerivationGoal::closeReadPipes(); DerivationGoal::closeReadPipes();
} else } else
builderOut.readSide = -1; builderOut.close();
} }
@ -802,15 +802,13 @@ void LocalDerivationGoal::startBuilder()
/* Create the log file. */ /* Create the log file. */
Path logFile = openLogFile(); Path logFile = openLogFile();
/* Create a pipe to get the output of the builder. */ /* Create a pseudoterminal to get the output of the builder. */
//builderOut.create(); builderOut = posix_openpt(O_RDWR | O_NOCTTY);
if (!builderOut)
builderOut.readSide = posix_openpt(O_RDWR | O_NOCTTY);
if (!builderOut.readSide)
throw SysError("opening pseudoterminal master"); throw SysError("opening pseudoterminal master");
// FIXME: not thread-safe, use ptsname_r // FIXME: not thread-safe, use ptsname_r
std::string slaveName(ptsname(builderOut.readSide.get())); std::string slaveName = ptsname(builderOut.get());
if (buildUser) { if (buildUser) {
if (chmod(slaveName.c_str(), 0600)) if (chmod(slaveName.c_str(), 0600))
@ -821,35 +819,14 @@ void LocalDerivationGoal::startBuilder()
} }
#if __APPLE__ #if __APPLE__
else { else {
if (grantpt(builderOut.readSide.get())) if (grantpt(builderOut.get()))
throw SysError("granting access to pseudoterminal slave"); throw SysError("granting access to pseudoterminal slave");
} }
#endif #endif
#if 0 if (unlockpt(builderOut.get()))
// Mount the pt in the sandbox so that the "tty" command works.
// FIXME: this doesn't work with the new devpts in the sandbox.
if (useChroot)
dirsInChroot[slaveName] = {slaveName, false};
#endif
if (unlockpt(builderOut.readSide.get()))
throw SysError("unlocking pseudoterminal"); throw SysError("unlocking pseudoterminal");
builderOut.writeSide = open(slaveName.c_str(), O_RDWR | O_NOCTTY);
if (!builderOut.writeSide)
throw SysError("opening pseudoterminal slave");
// Put the pt into raw mode to prevent \n -> \r\n translation.
struct termios term;
if (tcgetattr(builderOut.writeSide.get(), &term))
throw SysError("getting pseudoterminal attributes");
cfmakeraw(&term);
if (tcsetattr(builderOut.writeSide.get(), TCSANOW, &term))
throw SysError("putting pseudoterminal into raw mode");
buildResult.startTime = time(0); buildResult.startTime = time(0);
/* Fork a child to build the package. */ /* Fork a child to build the package. */
@ -897,7 +874,11 @@ void LocalDerivationGoal::startBuilder()
usingUserNamespace = userNamespacesSupported(); usingUserNamespace = userNamespacesSupported();
Pipe sendPid;
sendPid.create();
Pid helper = startProcess([&]() { Pid helper = startProcess([&]() {
sendPid.readSide.close();
/* Drop additional groups here because we can't do it /* Drop additional groups here because we can't do it
after we've created the new user namespace. FIXME: after we've created the new user namespace. FIXME:
@ -917,13 +898,14 @@ void LocalDerivationGoal::startBuilder()
if (usingUserNamespace) if (usingUserNamespace)
options.cloneFlags |= CLONE_NEWUSER; options.cloneFlags |= CLONE_NEWUSER;
pid_t child = startProcess([&]() { runChild(); }, options); pid_t child = startProcess([&]() { runChild(slaveName); }, options);
writeFull(builderOut.writeSide.get(), writeFull(sendPid.writeSide.get(), fmt("%d\n", child));
fmt("%d %d\n", usingUserNamespace, child));
_exit(0); _exit(0);
}); });
sendPid.writeSide.close();
if (helper.wait() != 0) if (helper.wait() != 0)
throw Error("unable to start build process"); throw Error("unable to start build process");
@ -935,10 +917,9 @@ void LocalDerivationGoal::startBuilder()
userNamespaceSync.writeSide = -1; userNamespaceSync.writeSide = -1;
}); });
auto ss = tokenizeString<std::vector<std::string>>(readLine(builderOut.readSide.get())); auto ss = tokenizeString<std::vector<std::string>>(readLine(sendPid.readSide.get()));
assert(ss.size() == 2); assert(ss.size() == 1);
usingUserNamespace = ss[0] == "1"; pid = string2Int<pid_t>(ss[0]).value();
pid = string2Int<pid_t>(ss[1]).value();
if (usingUserNamespace) { if (usingUserNamespace) {
/* Set the UID/GID mapping of the builder's user namespace /* Set the UID/GID mapping of the builder's user namespace
@ -993,21 +974,20 @@ void LocalDerivationGoal::startBuilder()
#endif #endif
{ {
pid = startProcess([&]() { pid = startProcess([&]() {
runChild(); runChild(slaveName);
}); });
} }
/* parent */ /* parent */
pid.setSeparatePG(true); pid.setSeparatePG(true);
builderOut.writeSide = -1; worker.childStarted(shared_from_this(), {builderOut.get()}, true, true);
worker.childStarted(shared_from_this(), {builderOut.readSide.get()}, true, true);
/* Check if setting up the build environment failed. */ /* Check if setting up the build environment failed. */
std::vector<std::string> msgs; std::vector<std::string> msgs;
while (true) { while (true) {
std::string msg = [&]() { std::string msg = [&]() {
try { try {
return readLine(builderOut.readSide.get()); return readLine(builderOut.get());
} catch (Error & e) { } catch (Error & e) {
auto status = pid.wait(); auto status = pid.wait();
e.addTrace({}, "while waiting for the build environment for '%s' to initialize (%s, previous messages: %s)", e.addTrace({}, "while waiting for the build environment for '%s' to initialize (%s, previous messages: %s)",
@ -1019,7 +999,7 @@ void LocalDerivationGoal::startBuilder()
}(); }();
if (msg.substr(0, 1) == "\2") break; if (msg.substr(0, 1) == "\2") break;
if (msg.substr(0, 1) == "\1") { if (msg.substr(0, 1) == "\1") {
FdSource source(builderOut.readSide.get()); FdSource source(builderOut.get());
auto ex = readError(source); auto ex = readError(source);
ex.addTrace({}, "while setting up the build environment"); ex.addTrace({}, "while setting up the build environment");
throw ex; throw ex;
@ -1640,7 +1620,7 @@ void setupSeccomp()
} }
void LocalDerivationGoal::runChild() void LocalDerivationGoal::runChild(const Path & slaveName)
{ {
/* Warning: in the child we should absolutely not make any SQLite /* Warning: in the child we should absolutely not make any SQLite
calls! */ calls! */
@ -1649,7 +1629,22 @@ void LocalDerivationGoal::runChild()
try { /* child */ try { /* child */
commonChildInit(builderOut); /* Open the slave side of the pseudoterminal. */
AutoCloseFD builderOut = open(slaveName.c_str(), O_RDWR | O_NOCTTY);
if (!builderOut)
throw SysError("opening pseudoterminal slave");
// Put the pt into raw mode to prevent \n -> \r\n translation.
struct termios term;
if (tcgetattr(builderOut.get(), &term))
throw SysError("getting pseudoterminal attributes");
cfmakeraw(&term);
if (tcsetattr(builderOut.get(), TCSANOW, &term))
throw SysError("putting pseudoterminal into raw mode");
commonChildInit(builderOut.get());
try { try {
setupSeccomp(); setupSeccomp();
@ -2891,7 +2886,7 @@ void LocalDerivationGoal::deleteTmpDir(bool force)
bool LocalDerivationGoal::isReadDesc(int fd) bool LocalDerivationGoal::isReadDesc(int fd)
{ {
return (hook && DerivationGoal::isReadDesc(fd)) || return (hook && DerivationGoal::isReadDesc(fd)) ||
(!hook && fd == builderOut.readSide.get()); (!hook && fd == builderOut.get());
} }

View file

@ -24,8 +24,9 @@ struct LocalDerivationGoal : public DerivationGoal
/* The path of the temporary directory in the sandbox. */ /* The path of the temporary directory in the sandbox. */
Path tmpDirInSandbox; Path tmpDirInSandbox;
/* Pipe for the builder's standard output/error. */ /* Master side of the pseudoterminal used for the builder's
Pipe builderOut; standard output/error. */
AutoCloseFD builderOut;
/* Pipe for synchronising updates to the builder namespaces. */ /* Pipe for synchronising updates to the builder namespaces. */
Pipe userNamespaceSync; Pipe userNamespaceSync;
@ -168,7 +169,7 @@ struct LocalDerivationGoal : public DerivationGoal
int getChildStatus() override; int getChildStatus() override;
/* Run the builder's process. */ /* Run the builder's process. */
void runChild(); void runChild(const std::string & slaveName);
/* Check that the derivation outputs all exist and register them /* Check that the derivation outputs all exist and register them
as valid. */ as valid. */

View file

@ -1968,7 +1968,7 @@ std::string showBytes(uint64_t bytes)
// FIXME: move to libstore/build // FIXME: move to libstore/build
void commonChildInit(Pipe & logPipe) void commonChildInit(int stderrFd)
{ {
logger = makeSimpleLogger(); logger = makeSimpleLogger();
@ -1983,7 +1983,7 @@ void commonChildInit(Pipe & logPipe)
throw SysError("creating a new session"); throw SysError("creating a new session");
/* Dup the write side of the logger pipe into stderr. */ /* Dup the write side of the logger pipe into stderr. */
if (dup2(logPipe.writeSide.get(), STDERR_FILENO) == -1) if (dup2(stderrFd, STDERR_FILENO) == -1)
throw SysError("cannot pipe standard error into log file"); throw SysError("cannot pipe standard error into log file");
/* Dup stderr to stdout. */ /* Dup stderr to stdout. */

View file

@ -704,7 +704,7 @@ typedef std::function<bool(const Path & path)> PathFilter;
extern PathFilter defaultPathFilter; extern PathFilter defaultPathFilter;
/* Common initialisation performed in child processes. */ /* Common initialisation performed in child processes. */
void commonChildInit(Pipe & logPipe); void commonChildInit(int stderrFd);
/* Create a Unix domain socket. */ /* Create a Unix domain socket. */
AutoCloseFD createUnixDomainSocket(); AutoCloseFD createUnixDomainSocket();