me
/
guix
Archived
1
0
Fork 0

daemon: Run 'guix substitute --substitute' as an agent.

This avoids spawning one substitute process per substitution.

* nix/libstore/build.cc (class Worker)[substituter]: New field.
[outPipe, logPipe, pid]: Remove.
(class SubstitutionGoal)[expectedHashStr, status, substituter]: New fields.
(SubstitutionGoal::timedOut): Adjust to check 'substituter'.
(SubstitutionGoal::tryToRun): Remove references to 'outPipe' and
'logPipe'.  Run "guix substitute --substitute" as an 'Agent'.  Send the
request with 'writeLine'.
(SubstitutionGoal::finished): Likewise.
(SubstitutionGoal::handleChildOutput): Change to fill in
'expectedHashStr' and 'status'.
(SubstitutionGoal::handleEOF): Call 'wakeUp' unconditionally.
(SubstitutionGoal::~SubstitutionGoal): Adjust to check 'substituter'.
* guix/scripts/substitute.scm (process-substitution): Write "success\n"
to stdout upon success.
(%error-to-file-descriptor-4?): New variable.
(guix-substitute): Set 'current-error-port' to file descriptor 4
unless (%error-to-file-descriptor-4?) is false.
Remove "--substitute" arguments.  Loop reading line from stdin.
* tests/substitute.scm <top level>: Call '%error-to-file-descriptor-4?'.
(request-substitution): New procedure.
("substitute, no signature")
("substitute, invalid hash")
("substitute, unauthorized key")
("substitute, authorized key")
("substitute, unauthorized narinfo comes first")
("substitute, unsigned narinfo comes first")
("substitute, first narinfo is unsigned and has wrong hash")
("substitute, first narinfo is unsigned and has wrong refs")
("substitute, two invalid narinfos")
("substitute, narinfo with several URLs"): Adjust to new "guix
substitute --substitute" calling convention.
master
Ludovic Courtès 2020-12-02 16:27:34 +01:00
parent a618a8c620
commit 711df9ef3c
No known key found for this signature in database
GPG Key ID: 090B11993D9AEBB5
3 changed files with 145 additions and 113 deletions

View File

@ -88,6 +88,7 @@
write-narinfo write-narinfo
%allow-unauthenticated-substitutes? %allow-unauthenticated-substitutes?
%error-to-file-descriptor-4?
substitute-urls substitute-urls
guix-substitute)) guix-substitute))
@ -1016,7 +1017,10 @@ DESTINATION as a nar file. Verify the substitute against ACL."
;; Skip a line after what 'progress-reporter/file' printed, and another ;; Skip a line after what 'progress-reporter/file' printed, and another
;; one to visually separate substitutions. ;; one to visually separate substitutions.
(display "\n\n" (current-error-port))))) (display "\n\n" (current-error-port))
;; Tell the daemon that we're done.
(display "success\n" (current-output-port)))))
;;; ;;;
@ -1127,6 +1131,11 @@ default value."
(unless (string->uri uri) (unless (string->uri uri)
(leave (G_ "~a: invalid URI~%") uri))) (leave (G_ "~a: invalid URI~%") uri)))
(define %error-to-file-descriptor-4?
;; Whether to direct 'current-error-port' to file descriptor 4 like
;; 'guix-daemon' expects.
(make-parameter #t))
(define-command (guix-substitute . args) (define-command (guix-substitute . args)
(category internal) (category internal)
(synopsis "implement the build daemon's substituter protocol") (synopsis "implement the build daemon's substituter protocol")
@ -1140,9 +1149,9 @@ default value."
;; The daemon's agent code opens file descriptor 4 for us and this is where ;; The daemon's agent code opens file descriptor 4 for us and this is where
;; stderr should go. ;; stderr should go.
(parameterize ((current-error-port (match args (parameterize ((current-error-port (if (%error-to-file-descriptor-4?)
(("--query") (fdopen 4 "wl")) (fdopen 4 "wl")
(_ (current-error-port))))) (current-error-port))))
;; Redirect diagnostics to file descriptor 4 as well. ;; Redirect diagnostics to file descriptor 4 as well.
(guix-warning-port (current-error-port)) (guix-warning-port (current-error-port))
@ -1184,15 +1193,22 @@ default value."
#:cache-urls (substitute-urls) #:cache-urls (substitute-urls)
#:acl acl) #:acl acl)
(loop (read-line))))))) (loop (read-line)))))))
(("--substitute" store-path destination) (("--substitute")
;; Download STORE-PATH and store it as a Nar in file DESTINATION. ;; Download STORE-PATH and store it as a Nar in file DESTINATION.
;; Specify the number of columns of the terminal so the progress ;; Specify the number of columns of the terminal so the progress
;; report displays nicely. ;; report displays nicely.
(parameterize ((current-terminal-columns (client-terminal-columns))) (parameterize ((current-terminal-columns (client-terminal-columns)))
(process-substitution store-path destination (let loop ()
#:cache-urls (substitute-urls) (match (read-line)
#:acl (current-acl) ((? eof-object?)
#:print-build-trace? print-build-trace?))) #t)
((= string-tokenize ("substitute" store-path destination))
(process-substitution store-path destination
#:cache-urls (substitute-urls)
#:acl (current-acl)
#:print-build-trace?
print-build-trace?)
(loop))))))
((or ("-V") ("--version")) ((or ("-V") ("--version"))
(show-version-and-exit "guix substitute")) (show-version-and-exit "guix substitute"))
(("--help") (("--help")

View File

@ -262,6 +262,7 @@ public:
LocalStore & store; LocalStore & store;
std::shared_ptr<Agent> hook; std::shared_ptr<Agent> hook;
std::shared_ptr<Agent> substituter;
Worker(LocalStore & store); Worker(LocalStore & store);
~Worker(); ~Worker();
@ -2773,15 +2774,6 @@ private:
/* Path info returned by the substituter's query info operation. */ /* Path info returned by the substituter's query info operation. */
SubstitutablePathInfo info; SubstitutablePathInfo info;
/* Pipe for the substituter's standard output. */
Pipe outPipe;
/* Pipe for the substituter's standard error. */
Pipe logPipe;
/* The process ID of the builder. */
Pid pid;
/* Lock on the store path. */ /* Lock on the store path. */
std::shared_ptr<PathLocks> outputLock; std::shared_ptr<PathLocks> outputLock;
@ -2795,6 +2787,17 @@ private:
typedef void (SubstitutionGoal::*GoalState)(); typedef void (SubstitutionGoal::*GoalState)();
GoalState state; GoalState state;
/* The substituter. */
std::shared_ptr<Agent> substituter;
/* Either the empty string, or the expected hash as returned by the
substituter. */
string expectedHashStr;
/* Either the empty string, or the status phrase returned by the
substituter. */
string status;
void tryNext(); void tryNext();
public: public:
@ -2840,7 +2843,7 @@ SubstitutionGoal::SubstitutionGoal(const Path & storePath, Worker & worker, bool
SubstitutionGoal::~SubstitutionGoal() SubstitutionGoal::~SubstitutionGoal()
{ {
if (pid != -1) worker.childTerminated(pid); if (substituter) worker.childTerminated(substituter->pid);
} }
@ -2848,9 +2851,9 @@ void SubstitutionGoal::timedOut()
{ {
if (settings.printBuildTrace) if (settings.printBuildTrace)
printMsg(lvlError, format("@ substituter-failed %1% timeout") % storePath); printMsg(lvlError, format("@ substituter-failed %1% timeout") % storePath);
if (pid != -1) { if (substituter) {
pid_t savedPid = pid; pid_t savedPid = substituter->pid;
pid.kill(); substituter.reset();
worker.childTerminated(savedPid); worker.childTerminated(savedPid);
} }
amDone(ecFailed); amDone(ecFailed);
@ -2977,45 +2980,29 @@ void SubstitutionGoal::tryToRun()
printMsg(lvlInfo, format("fetching path `%1%'...") % storePath); printMsg(lvlInfo, format("fetching path `%1%'...") % storePath);
outPipe.create();
logPipe.create();
destPath = repair ? storePath + ".tmp" : storePath; destPath = repair ? storePath + ".tmp" : storePath;
/* Remove the (stale) output path if it exists. */ /* Remove the (stale) output path if it exists. */
if (pathExists(destPath)) if (pathExists(destPath))
deletePath(destPath); deletePath(destPath);
/* Fill in the arguments. */ if (!worker.substituter) {
Strings args; const Strings args = { "substitute", "--substitute" };
args.push_back("guix"); const std::map<string, string> env = { { "_NIX_OPTIONS", settings.pack() } };
args.push_back("substitute"); worker.substituter = std::make_shared<Agent>(settings.guixProgram, args, env);
args.push_back("--substitute"); }
args.push_back(storePath);
args.push_back(destPath);
/* Fork the substitute program. */ /* Borrow the worker's substituter. */
pid = startProcess([&]() { if (!substituter) substituter.swap(worker.substituter);
/* Communicate substitute-urls & co. to 'guix substitute'. */ /* Send the request to the substituter. */
setenv("_NIX_OPTIONS", settings.pack().c_str(), 1); writeLine(substituter->toAgent.writeSide,
(format("substitute %1% %2%") % storePath % destPath).str());
commonChildInit(logPipe); set<int> fds;
fds.insert(substituter->fromAgent.readSide);
if (dup2(outPipe.writeSide, STDOUT_FILENO) == -1) fds.insert(substituter->builderOut.readSide);
throw SysError("cannot dup output pipe into stdout"); worker.childStarted(shared_from_this(), substituter->pid, fds, true, true);
execv(settings.guixProgram.c_str(), stringsToCharPtrs(args).data());
throw SysError(format("executing `%1% substitute'") % settings.guixProgram);
});
pid.setSeparatePG(true);
pid.setKillSignal(SIGTERM);
outPipe.writeSide.close();
logPipe.writeSide.close();
worker.childStarted(shared_from_this(),
pid, singleton<set<int> >(logPipe.readSide), true, true);
state = &SubstitutionGoal::finished; state = &SubstitutionGoal::finished;
@ -3030,28 +3017,25 @@ void SubstitutionGoal::finished()
{ {
trace("substitute finished"); trace("substitute finished");
/* Since we got an EOF on the logger pipe, the substitute is /* Remove the 'guix substitute' process from the list of children. */
presumed to have terminated. */ worker.childTerminated(substituter->pid);
pid_t savedPid = pid;
int status = pid.wait(true);
/* So the child is gone now. */ /* If max-jobs > 1, the worker might have created a new 'substitute'
worker.childTerminated(savedPid); process in the meantime. If that is the case, terminate ours;
otherwise, give it back to the worker. */
/* Close the read side of the logger pipe. */ if (worker.substituter) {
logPipe.readSide.close(); substituter.reset ();
} else {
/* Get the hash info from stdout. */ worker.substituter.swap(substituter);
string expectedHashStr = statusOk(status) ? readLine(outPipe.readSide) : ""; }
outPipe.readSide.close();
/* Check the exit status and the build result. */ /* Check the exit status and the build result. */
HashResult hash; HashResult hash;
try { try {
if (!statusOk(status)) if (status != "success")
throw SubstError(format("fetching path `%1%' %2%") throw SubstError(format("fetching path `%1%' (status: '%2%')")
% storePath % statusToString(status)); % storePath % status);
if (!pathExists(destPath)) if (!pathExists(destPath))
throw SubstError(format("substitute did not produce path `%1%'") % destPath); throw SubstError(format("substitute did not produce path `%1%'") % destPath);
@ -3122,16 +3106,33 @@ void SubstitutionGoal::finished()
void SubstitutionGoal::handleChildOutput(int fd, const string & data) void SubstitutionGoal::handleChildOutput(int fd, const string & data)
{ {
assert(fd == logPipe.readSide); if (verbosity >= settings.buildVerbosity
if (verbosity >= settings.buildVerbosity) writeToStderr(data); && fd == substituter->builderOut.readSide) {
/* Don't write substitution output to a log file for now. We writeToStderr(data);
probably should, though. */ /* Don't write substitution output to a log file for now. We
probably should, though. */
}
if (fd == substituter->fromAgent.readSide) {
/* Trim whitespace to the right. */
size_t end = data.find_last_not_of(" \t\n");
string trimmed = (end != string::npos) ? data.substr(0, end + 1) : data;
if (expectedHashStr == "") {
expectedHashStr = trimmed;
} else if (status == "") {
status = trimmed;
worker.wakeUp(shared_from_this());
} else {
printMsg(lvlError, format("unexpected substituter message '%1%'") % data);
}
}
} }
void SubstitutionGoal::handleEOF(int fd) void SubstitutionGoal::handleEOF(int fd)
{ {
if (fd == logPipe.readSide) worker.wakeUp(shared_from_this()); worker.wakeUp(shared_from_this());
} }

View File

@ -58,6 +58,14 @@ it writes to GUIX-WARNING-PORT a messages that matches ERROR-RX."
(let ((message (get-output-string error-output))) (let ((message (get-output-string error-output)))
(->bool (string-match error-rx message)))))))))) (->bool (string-match error-rx message))))))))))
(define (request-substitution item destination)
"Run 'guix substitute --substitute' to fetch ITEM to DESTINATION."
(parameterize ((guix-warning-port (current-error-port)))
(with-input-from-string (string-append "substitute " item " "
destination "\n")
(lambda ()
(guix-substitute "--substitute")))))
(define %public-key (define %public-key
;; This key is known to be in the ACL by default. ;; This key is known to be in the ACL by default.
(call-with-input-file (string-append %config-directory "/signing-key.pub") (call-with-input-file (string-append %config-directory "/signing-key.pub")
@ -184,6 +192,11 @@ a file for NARINFO."
;; Transmit these options to 'guix substitute'. ;; Transmit these options to 'guix substitute'.
(substitute-urls (list (getenv "GUIX_BINARY_SUBSTITUTE_URL"))) (substitute-urls (list (getenv "GUIX_BINARY_SUBSTITUTE_URL")))
;; Never use file descriptor 4, unlike what happens when invoked by the
;; daemon.
(%error-to-file-descriptor-4? #f)
(test-equal "query narinfo without signature" (test-equal "query narinfo without signature"
"" ; not substitutable "" ; not substitutable
@ -284,10 +297,12 @@ System: mips64el-linux\n")
(test-quit "substitute, no signature" (test-quit "substitute, no signature"
"no valid substitute" "no valid substitute"
(with-narinfo %narinfo (with-narinfo %narinfo
(guix-substitute "--substitute" (with-input-from-string (string-append "substitute "
(string-append (%store-prefix) (%store-prefix)
"/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo") "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo"
"foo"))) " foo\n")
(lambda ()
(guix-substitute "--substitute")))))
(test-quit "substitute, invalid hash" (test-quit "substitute, invalid hash"
"no valid substitute" "no valid substitute"
@ -295,10 +310,12 @@ System: mips64el-linux\n")
(with-narinfo (string-append %narinfo "Signature: " (with-narinfo (string-append %narinfo "Signature: "
(signature-field "different body") (signature-field "different body")
"\n") "\n")
(guix-substitute "--substitute" (with-input-from-string (string-append "substitute "
(string-append (%store-prefix) (%store-prefix)
"/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo") "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo"
"foo"))) " foo\n")
(lambda ()
(guix-substitute "--substitute")))))
(test-quit "substitute, unauthorized key" (test-quit "substitute, unauthorized key"
"no valid substitute" "no valid substitute"
@ -307,10 +324,12 @@ System: mips64el-linux\n")
%narinfo %narinfo
#:public-key %wrong-public-key) #:public-key %wrong-public-key)
"\n") "\n")
(guix-substitute "--substitute" (with-input-from-string (string-append "substitute "
(string-append (%store-prefix) (%store-prefix)
"/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo") "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo"
"foo"))) " foo\n")
(lambda ()
(guix-substitute "--substitute")))))
(test-equal "substitute, authorized key" (test-equal "substitute, authorized key"
"Substitutable data." "Substitutable data."
@ -319,10 +338,9 @@ System: mips64el-linux\n")
(dynamic-wind (dynamic-wind
(const #t) (const #t)
(lambda () (lambda ()
(guix-substitute "--substitute" (request-substitution (string-append (%store-prefix)
(string-append (%store-prefix) "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo")
"/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo") "substitute-retrieved")
"substitute-retrieved")
(call-with-input-file "substitute-retrieved" get-string-all)) (call-with-input-file "substitute-retrieved" get-string-all))
(lambda () (lambda ()
(false-if-exception (delete-file "substitute-retrieved")))))) (false-if-exception (delete-file "substitute-retrieved"))))))
@ -352,10 +370,9 @@ System: mips64el-linux\n")
(map (cut string-append "file://" <>) (map (cut string-append "file://" <>)
(list %alternate-substitute-directory (list %alternate-substitute-directory
%main-substitute-directory)))) %main-substitute-directory))))
(guix-substitute "--substitute" (request-substitution (string-append (%store-prefix)
(string-append (%store-prefix) "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo")
"/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo") "substitute-retrieved"))
"substitute-retrieved"))
(call-with-input-file "substitute-retrieved" get-string-all)) (call-with-input-file "substitute-retrieved" get-string-all))
(lambda () (lambda ()
(false-if-exception (delete-file "substitute-retrieved"))))))) (false-if-exception (delete-file "substitute-retrieved")))))))
@ -381,10 +398,9 @@ System: mips64el-linux\n")
(map (cut string-append "file://" <>) (map (cut string-append "file://" <>)
(list %alternate-substitute-directory (list %alternate-substitute-directory
%main-substitute-directory)))) %main-substitute-directory))))
(guix-substitute "--substitute" (request-substitution (string-append (%store-prefix)
(string-append (%store-prefix) "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo")
"/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo") "substitute-retrieved"))
"substitute-retrieved"))
(call-with-input-file "substitute-retrieved" get-string-all)) (call-with-input-file "substitute-retrieved" get-string-all))
(lambda () (lambda ()
(false-if-exception (delete-file "substitute-retrieved"))))))) (false-if-exception (delete-file "substitute-retrieved")))))))
@ -417,10 +433,9 @@ System: mips64el-linux\n")
(map (cut string-append "file://" <>) (map (cut string-append "file://" <>)
(list %alternate-substitute-directory (list %alternate-substitute-directory
%main-substitute-directory)))) %main-substitute-directory))))
(guix-substitute "--substitute" (request-substitution (string-append (%store-prefix)
(string-append (%store-prefix) "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo")
"/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo") "substitute-retrieved"))
"substitute-retrieved"))
(call-with-input-file "substitute-retrieved" get-string-all)) (call-with-input-file "substitute-retrieved" get-string-all))
(lambda () (lambda ()
(false-if-exception (delete-file "substitute-retrieved"))))))) (false-if-exception (delete-file "substitute-retrieved")))))))
@ -451,10 +466,9 @@ System: mips64el-linux\n")
(map (cut string-append "file://" <>) (map (cut string-append "file://" <>)
(list %alternate-substitute-directory (list %alternate-substitute-directory
%main-substitute-directory)))) %main-substitute-directory))))
(guix-substitute "--substitute" (request-substitution (string-append (%store-prefix)
(string-append (%store-prefix) "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo")
"/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo") "substitute-retrieved"))
"substitute-retrieved"))
(call-with-input-file "substitute-retrieved" get-string-all)) (call-with-input-file "substitute-retrieved" get-string-all))
(lambda () (lambda ()
(false-if-exception (delete-file "substitute-retrieved"))))))) (false-if-exception (delete-file "substitute-retrieved")))))))
@ -470,10 +484,12 @@ System: mips64el-linux\n")
#:public-key %wrong-public-key)) #:public-key %wrong-public-key))
%main-substitute-directory %main-substitute-directory
(guix-substitute "--substitute" (with-input-from-string (string-append "substitute "
(string-append (%store-prefix) (%store-prefix)
"/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo") "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo"
"substitute-retrieved")))) " substitute-retrieved\n")
(lambda ()
(guix-substitute "--substitute"))))))
(test-equal "substitute, narinfo with several URLs" (test-equal "substitute, narinfo with several URLs"
"Substitutable data." "Substitutable data."
@ -513,10 +529,9 @@ System: mips64el-linux\n")))
(parameterize ((substitute-urls (parameterize ((substitute-urls
(list (string-append "file://" (list (string-append "file://"
%main-substitute-directory)))) %main-substitute-directory))))
(guix-substitute "--substitute" (request-substitution (string-append (%store-prefix)
(string-append (%store-prefix) "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo")
"/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo") "substitute-retrieved"))
"substitute-retrieved"))
(call-with-input-file "substitute-retrieved" get-string-all)) (call-with-input-file "substitute-retrieved" get-string-all))
(lambda () (lambda ()
(false-if-exception (delete-file "substitute-retrieved"))))))) (false-if-exception (delete-file "substitute-retrieved")))))))