* Simplify communication with the hook a bit (don't use file

descriptors 3/4, just use stdin/stderr).
This commit is contained in:
Eelco Dolstra 2009-03-28 19:29:55 +00:00
parent 7fb548aa26
commit 3a2bbe7f8a
6 changed files with 68 additions and 117 deletions

View file

@ -151,12 +151,12 @@ $ mount -o bind /mnt/otherdisk/nix /nix</screen>
<para>On the basis of this information, and whatever persistent <para>On the basis of this information, and whatever persistent
state the build hook keeps about other machines and their current state the build hook keeps about other machines and their current
load, it has to decide what to do with the build. It should print load, it has to decide what to do with the build. It should print
out on file descriptor 3 one of the following responses (terminated out on standard error one of the following responses (terminated by
by a newline, <literal>"\n"</literal>): a newline, <literal>"\n"</literal>):
<variablelist> <variablelist>
<varlistentry><term><literal>decline</literal></term> <varlistentry><term><literal># decline</literal></term>
<listitem><para>The build hook is not willing or able to perform <listitem><para>The build hook is not willing or able to perform
the build; the calling Nix process should do the build itself, the build; the calling Nix process should do the build itself,
@ -164,7 +164,7 @@ $ mount -o bind /mnt/otherdisk/nix /nix</screen>
</varlistentry> </varlistentry>
<varlistentry><term><literal>postpone</literal></term> <varlistentry><term><literal># postpone</literal></term>
<listitem><para>The build hook cannot perform the build now, but <listitem><para>The build hook cannot perform the build now, but
can do so in the future (e.g., because all available build slots can do so in the future (e.g., because all available build slots
@ -174,7 +174,7 @@ $ mount -o bind /mnt/otherdisk/nix /nix</screen>
</varlistentry> </varlistentry>
<varlistentry><term><literal>accept</literal></term> <varlistentry><term><literal># accept</literal></term>
<listitem><para>The build hook has accepted the <listitem><para>The build hook has accepted the
build.</para></listitem> build.</para></listitem>
@ -185,37 +185,12 @@ $ mount -o bind /mnt/otherdisk/nix /nix</screen>
</para> </para>
<para>If the build hook accepts the build, it is possible that it is <para>After sending <literal># accept</literal>, the hook should
no longer necessary to do the build because some other process has read one line from standard input, which will be the string
performed the build in the meantime. To prevent races, the hook <literal>okay</literal>. It can then proceed with the build.
must read from file descriptor 4 a single line that tells it whether Before sending <literal>okay</literal>, Nix will store in the hooks
to continue: current directory a number of text files that contain information
about the derivation:
<variablelist>
<varlistentry><term><literal>cancel</literal></term>
<listitem><para>The build has already been done, so the hook
should exit.</para></listitem>
</varlistentry>
<varlistentry><term><literal>okay</literal></term>
<listitem><para>The hook should proceed with the build. At this
point, the calling Nix process has acquired locks on the output
path, so no other Nix process will perform the
build.</para></listitem>
</varlistentry>
</variablelist>
</para>
<para>If the hook has been told to proceed, Nix will store in the
hooks current directory a number of text files that contain
information about the derivation:
<variablelist> <variablelist>
@ -255,7 +230,9 @@ $ mount -o bind /mnt/otherdisk/nix /nix</screen>
<para>The hook should copy the inputs to the remote machine, <para>The hook should copy the inputs to the remote machine,
register the validity of the inputs, perform the remote build, and register the validity of the inputs, perform the remote build, and
copy the outputs back to the local machine. An exit code other than copy the outputs back to the local machine. An exit code other than
<literal>0</literal> indicates that the hook has failed.</para> <literal>0</literal> indicates that the hook has failed. An exit
code equal to 100 means that the remote build failed (as opposed to,
e.g., a network error).</para>
</listitem> </listitem>

View file

@ -29,9 +29,7 @@ $maxSilentTime = 0 unless defined $maxSilentTime;
sub sendReply { sub sendReply {
my $reply = shift; my $reply = shift;
open OUT, ">&3" or die; print STDERR "# $reply\n";
print OUT "$reply\n";
close OUT;
} }
sub decline { sub decline {
@ -121,11 +119,8 @@ if (!defined $machine) {
# Yes we did, accept. # Yes we did, accept.
sendReply "accept"; sendReply "accept";
open IN, "<&4" or die; my $x = <STDIN>;
my $x = <IN>;
chomp $x; chomp $x;
#print "got $x\n";
close IN;
if ($x ne "okay") { if ($x ne "okay") {
exit 0; exit 0;

View file

@ -660,7 +660,6 @@ private:
/* Pipes for talking to the build hook (if any). */ /* Pipes for talking to the build hook (if any). */
Pipe toHook; Pipe toHook;
Pipe fromHook;
/* Whether we're currently doing a chroot build. */ /* Whether we're currently doing a chroot build. */
bool useChroot; bool useChroot;
@ -1207,49 +1206,6 @@ void DerivationGoal::buildDone()
} }
static string readLine(int fd)
{
string s;
while (1) {
checkInterrupt();
char ch;
ssize_t rd = read(fd, &ch, 1);
if (rd == -1) {
if (errno != EINTR)
throw SysError("reading a line");
} else if (rd == 0)
throw Error("unexpected EOF reading a line");
else {
if (ch == '\n') return s;
s += ch;
}
}
}
static void writeLine(int fd, string s)
{
s += '\n';
writeFull(fd, (const unsigned char *) s.c_str(), s.size());
}
/* !!! ugly hack */
static void drain(int fd)
{
unsigned char buffer[1024];
while (1) {
checkInterrupt();
ssize_t rd = read(fd, buffer, sizeof buffer);
if (rd == -1) {
if (errno != EINTR)
throw SysError("draining");
} else if (rd == 0) break;
else writeToStderr(buffer, rd);
}
}
DerivationGoal::HookReply DerivationGoal::tryBuildHook() DerivationGoal::HookReply DerivationGoal::tryBuildHook()
{ {
if (!useBuildHook) return rpDecline; if (!useBuildHook) return rpDecline;
@ -1266,7 +1222,6 @@ DerivationGoal::HookReply DerivationGoal::tryBuildHook()
/* Create the communication pipes. */ /* Create the communication pipes. */
toHook.create(); toHook.create();
fromHook.create();
/* Fork the hook. */ /* Fork the hook. */
pid = fork(); pid = fork();
@ -1310,16 +1265,20 @@ DerivationGoal::HookReply DerivationGoal::tryBuildHook()
worker.childStarted(shared_from_this(), worker.childStarted(shared_from_this(),
pid, singleton<set<int> >(logPipe.readSide), false); pid, singleton<set<int> >(logPipe.readSide), false);
fromHook.writeSide.close();
toHook.readSide.close(); toHook.readSide.close();
/* Read the first line of input, which should be a word indicating /* Read the first line of input, which should be a word indicating
whether the hook wishes to perform the build. !!! potential whether the hook wishes to perform the build. */
for deadlock here: we should also read from the child's logger
pipe. */
string reply; string reply;
try { try {
reply = readLine(fromHook.readSide); while (true) {
string s = readLine(logPipe.readSide);
if (string(s, 0, 2) == "# ") {
reply = string(s, 2);
break;
}
handleChildOutput(logPipe.readSide, s + "\n");
}
} catch (Error & e) { } catch (Error & e) {
terminateBuildHook(true); terminateBuildHook(true);
throw; throw;
@ -1335,7 +1294,7 @@ DerivationGoal::HookReply DerivationGoal::tryBuildHook()
else if (reply == "accept") { else if (reply == "accept") {
printMsg(lvlInfo, format("running hook to build path(s) %1%") printMsg(lvlInfo, format("using hook to build path(s) %1%")
% showPaths(outputPaths(drv.outputs))); % showPaths(outputPaths(drv.outputs)));
/* Write the information that the hook needs to perform the /* Write the information that the hook needs to perform the
@ -1379,11 +1338,11 @@ DerivationGoal::HookReply DerivationGoal::tryBuildHook()
/* Tell the hook to proceed. */ /* Tell the hook to proceed. */
writeLine(toHook.writeSide, "okay"); writeLine(toHook.writeSide, "okay");
toHook.writeSide.close();
if (printBuildTrace) { if (printBuildTrace)
printMsg(lvlError, format("@ build-started %1% %2% %3% %4%") printMsg(lvlError, format("@ build-started %1% %2% %3% %4%")
% drvPath % drv.outputs["out"].path % drv.platform % logFile); % drvPath % drv.outputs["out"].path % drv.platform % logFile);
}
return rpAccept; return rpAccept;
} }
@ -1394,7 +1353,6 @@ DerivationGoal::HookReply DerivationGoal::tryBuildHook()
void DerivationGoal::terminateBuildHook(bool kill) void DerivationGoal::terminateBuildHook(bool kill)
{ {
/* !!! drain stdout of hook */
debug("terminating build hook"); debug("terminating build hook");
pid_t savedPid = pid; pid_t savedPid = pid;
if (kill) if (kill)
@ -1404,10 +1362,8 @@ void DerivationGoal::terminateBuildHook(bool kill)
/* `false' means don't wake up waiting goals, since we want to /* `false' means don't wake up waiting goals, since we want to
keep this build slot ourselves. */ keep this build slot ourselves. */
worker.childTerminated(savedPid, false); worker.childTerminated(savedPid, false);
fromHook.readSide.close();
toHook.writeSide.close(); toHook.writeSide.close();
fdLogFile.close(); fdLogFile.close();
drain(logPipe.readSide);
logPipe.readSide.close(); logPipe.readSide.close();
deleteTmpDir(true); /* get rid of the hook's temporary directory */ deleteTmpDir(true); /* get rid of the hook's temporary directory */
} }
@ -1993,24 +1949,14 @@ void DerivationGoal::initChild()
throw SysError(format("changing into `%1%'") % tmpDir); throw SysError(format("changing into `%1%'") % tmpDir);
/* When running a hook, dup the communication pipes. */ /* When running a hook, dup the communication pipes. */
bool inHook = fromHook.writeSide.isOpen(); if (usingBuildHook) {
if (inHook) {
fromHook.readSide.close();
if (dup2(fromHook.writeSide, 3) == -1)
throw SysError("dupping from-hook write side");
toHook.writeSide.close(); toHook.writeSide.close();
if (dup2(toHook.readSide, 4) == -1) if (dup2(toHook.readSide, STDIN_FILENO) == -1)
throw SysError("dupping to-hook read side"); throw SysError("dupping to-hook read side");
} }
/* Close all other file descriptors. */ /* Close all other file descriptors. */
set<int> exceptions; closeMostFDs(set<int>());
if (inHook) {
exceptions.insert(3);
exceptions.insert(4);
}
closeMostFDs(exceptions);
} }

View file

@ -229,6 +229,33 @@ void writeFile(const Path & path, const string & s)
} }
string readLine(int fd)
{
string s;
while (1) {
checkInterrupt();
char ch;
ssize_t rd = read(fd, &ch, 1);
if (rd == -1) {
if (errno != EINTR)
throw SysError("reading a line");
} else if (rd == 0)
throw Error("unexpected EOF reading a line");
else {
if (ch == '\n') return s;
s += ch;
}
}
}
void writeLine(int fd, string s)
{
s += '\n';
writeFull(fd, (const unsigned char *) s.c_str(), s.size());
}
static void _computePathSize(const Path & path, static void _computePathSize(const Path & path,
unsigned long long & bytes, unsigned long long & blocks) unsigned long long & bytes, unsigned long long & blocks)
{ {

View file

@ -60,6 +60,12 @@ string readFile(const Path & path);
/* Write a string to a file. */ /* Write a string to a file. */
void writeFile(const Path & path, const string & s); void writeFile(const Path & path, const string & s);
/* Read a line from a file descriptor. */
string readLine(int fd);
/* Write a line to a file descriptor. */
void writeLine(int fd, string s);
/* Compute the sum of the sizes of all files in `path'. */ /* Compute the sum of the sizes of all files in `path'. */
void computePathSize(const Path & path, void computePathSize(const Path & path,
unsigned long long & bytes, unsigned long long & blocks); unsigned long long & bytes, unsigned long long & blocks);

View file

@ -11,11 +11,11 @@ outPath=$(sed 's/Derive(\[("out",\"\([^\"]*\)\".*/\1/' $drv)
echo "output path is $outPath" >&2 echo "output path is $outPath" >&2
if $(echo $outPath | grep -q input-1); then if $(echo $outPath | grep -q input-1); then
echo "accept" >&3 echo "# accept" >&2
read x <&4 read x
echo "got $x" echo "got $x"
mkdir $outPath mkdir $outPath
echo "BAR" > $outPath/foo echo "BAR" > $outPath/foo
else else
echo "decline" >&3 echo "# decline" >&2
fi fi