From unknown Mon Aug 18 00:06:21 2025 Content-Disposition: inline Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 X-Mailer: MIME-tools 5.509 (Entity 5.509) Content-Type: text/plain; charset=utf-8 From: bug#43340 <43340@debbugs.gnu.org> To: bug#43340 <43340@debbugs.gnu.org> Subject: Status: [PATCH 0/5] Speed up archive export/import Reply-To: bug#43340 <43340@debbugs.gnu.org> Date: Mon, 18 Aug 2025 07:06:21 +0000 retitle 43340 [PATCH 0/5] Speed up archive export/import reassign 43340 guix-patches submitter 43340 Ludovic Court=C3=A8s severity 43340 normal tag 43340 patch thanks From debbugs-submit-bounces@debbugs.gnu.org Fri Sep 11 10:41:14 2020 Received: (at submit) by debbugs.gnu.org; 11 Sep 2020 14:41:14 +0000 Received: from localhost ([127.0.0.1]:45048 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kGkEf-00046i-5l for submit@debbugs.gnu.org; Fri, 11 Sep 2020 10:41:14 -0400 Received: from lists.gnu.org ([209.51.188.17]:53504) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kGkEa-00046W-9Y for submit@debbugs.gnu.org; Fri, 11 Sep 2020 10:41:07 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:57724) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kGkEZ-0007ls-VC for guix-patches@gnu.org; Fri, 11 Sep 2020 10:41:03 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:40154) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kGkEZ-00067O-9T; Fri, 11 Sep 2020 10:41:03 -0400 Received: from [2a01:e0a:1d:7270:af76:b9b:ca24:c465] (port=39260 helo=gnu.org) by fencepost.gnu.org with esmtpsa (TLS1.2:DHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1kGkEW-0003Bj-Dd; Fri, 11 Sep 2020 10:41:01 -0400 From: =?UTF-8?q?Ludovic=20Court=C3=A8s?= To: guix-patches@gnu.org Subject: [PATCH 0/5] Speed up archive export/import Date: Fri, 11 Sep 2020 16:40:49 +0200 Message-Id: <20200911144049.14632-1-ludo@gnu.org> X-Mailer: git-send-email 2.28.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: submit Cc: =?UTF-8?q?Ludovic=20Court=C3=A8s?= X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -3.3 (---) Hi! This patch series goes on top of . It addresses the performance issue described at: https://lists.gnu.org/archive/html/guix-devel/2020-09/msg00073.html Specifically, it implements option #4 (spawning ‘guix authenticate’ once for the whole session, instead of spawning it every time a store item needs to be signed or authenticated), achieving a ~15x speedup, which is not bad. :-) There’s way more C++ code than I would like, and it’s probably not pretty code (I always end up typing things like “C++ list append” in a search engine to find the obscure incantation that does that). There’s now a query/reply protocol between the daemon and ‘guix authenticate’. Nothing fancy, but it allows for strings that contain whitespace or newlines, which is necessary here. That’s it! Ludo’. Ludovic Courtès (5): daemon: Generalize 'HookInstance' to 'Agent'. daemon: Isolate signing and signature verification functions. daemon: Move 'Agent' to libutil. daemon: Spawn 'guix authenticate' once for all. authenticate: Cache the ACL and key pairs. guix/scripts/authenticate.scm | 163 ++++++++++++++++++++++++++-------- nix/libstore/build.cc | 146 +++++------------------------- nix/libstore/local-store.cc | 103 +++++++++++++++++---- nix/libutil/util.cc | 84 ++++++++++++++++++ nix/libutil/util.hh | 25 ++++++ tests/guix-authenticate.sh | 45 ++++++---- tests/store.scm | 8 +- 7 files changed, 372 insertions(+), 202 deletions(-) -- 2.28.0 From debbugs-submit-bounces@debbugs.gnu.org Fri Sep 11 10:52:23 2020 Received: (at 43340) by debbugs.gnu.org; 11 Sep 2020 14:52:23 +0000 Received: from localhost ([127.0.0.1]:45102 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kGkPW-0006be-M3 for submit@debbugs.gnu.org; Fri, 11 Sep 2020 10:52:23 -0400 Received: from eggs.gnu.org ([209.51.188.92]:57656) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kGkPU-0006bO-Fd for 43340@debbugs.gnu.org; Fri, 11 Sep 2020 10:52:21 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:40473) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kGkPP-0007vw-64; Fri, 11 Sep 2020 10:52:15 -0400 Received: from [2a01:e0a:1d:7270:af76:b9b:ca24:c465] (port=39314 helo=gnu.org) by fencepost.gnu.org with esmtpsa (TLS1.2:DHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1kGkPO-0004Uo-2Y; Fri, 11 Sep 2020 10:52:14 -0400 From: =?UTF-8?q?Ludovic=20Court=C3=A8s?= To: 43340@debbugs.gnu.org Subject: [PATCH 1/5] daemon: Generalize 'HookInstance' to 'Agent'. Date: Fri, 11 Sep 2020 16:51:50 +0200 Message-Id: <20200911145154.15057-1-ludo@gnu.org> X-Mailer: git-send-email 2.28.0 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 43340 Cc: =?UTF-8?q?Ludovic=20Court=C3=A8s?= X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -3.3 (---) * nix/libstore/build.cc (HookInstance): Rename to... (Agent): ... this. Rename 'toHook' and 'fromHook' similarly and update users. Change constructor to require a command and an argument list. (DerivationGoal::tryBuildHook): Pass arguments to the 'Agent' constructor. --- nix/libstore/build.cc | 90 ++++++++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 39 deletions(-) diff --git a/nix/libstore/build.cc b/nix/libstore/build.cc index 29266f1dd6..b6fad493a9 100644 --- a/nix/libstore/build.cc +++ b/nix/libstore/build.cc @@ -85,7 +85,7 @@ static string pathNullDevice = "/dev/null"; /* Forward definition. */ class Worker; -struct HookInstance; +struct Agent; /* A pointer to a goal. */ @@ -265,7 +265,7 @@ public: LocalStore & store; - std::shared_ptr hook; + std::shared_ptr hook; Worker(LocalStore & store); ~Worker(); @@ -590,37 +590,41 @@ void UserLock::kill() ////////////////////////////////////////////////////////////////////// -struct HookInstance +/* An "agent" is a helper program that runs in the background and that we talk + to over pipes, such as the "guix offload" program. */ +struct Agent { - /* Pipes for talking to the build hook. */ - Pipe toHook; + /* Pipes for talking to the agent. */ + Pipe toAgent; - /* Pipe for the hook's standard output/error. */ - Pipe fromHook; + /* Pipe for the agent's standard output/error. */ + Pipe fromAgent; - /* Pipe for the builder's standard output/error. */ + /* Pipe for build standard output/error--e.g., for build processes started + by "guix offload". */ Pipe builderOut; - /* The process ID of the hook. */ + /* The process ID of the agent. */ Pid pid; - HookInstance(); + /* The 'guix' sub-command and arguments passed to the agent. */ + Agent(const string &command, const Strings &args); - ~HookInstance(); + ~Agent(); }; -HookInstance::HookInstance() +Agent::Agent(const string &command, const Strings &args) { - debug("starting build hook"); + debug(format("starting agent '%1%'") % command); const Path &buildHook = settings.guixProgram; /* Create a pipe to get the output of the child. */ - fromHook.create(); + fromAgent.create(); /* Create the communication pipes. */ - toHook.create(); + toAgent.create(); /* Create a pipe to get the output of the builder. */ builderOut.create(); @@ -628,38 +632,38 @@ HookInstance::HookInstance() /* Fork the hook. */ pid = startProcess([&]() { - commonChildInit(fromHook); + commonChildInit(fromAgent); if (chdir("/") == -1) throw SysError("changing into `/"); /* Dup the communication pipes. */ - if (dup2(toHook.readSide, STDIN_FILENO) == -1) + if (dup2(toAgent.readSide, STDIN_FILENO) == -1) throw SysError("dupping to-hook read side"); /* Use fd 4 for the builder's stdout/stderr. */ if (dup2(builderOut.writeSide, 4) == -1) throw SysError("dupping builder's stdout/stderr"); - execl(buildHook.c_str(), buildHook.c_str(), "offload", - settings.thisSystem.c_str(), - (format("%1%") % settings.maxSilentTime).str().c_str(), - (format("%1%") % settings.printBuildTrace).str().c_str(), - (format("%1%") % settings.buildTimeout).str().c_str(), - NULL); + Strings allArgs; + allArgs.push_back(buildHook); + allArgs.push_back(command); + allArgs.insert(allArgs.end(), args.begin(), args.end()); // append - throw SysError(format("executing `%1% offload'") % buildHook); + execv(buildHook.c_str(), stringsToCharPtrs(allArgs).data()); + + throw SysError(format("executing `%1% %2%'") % buildHook % command); }); pid.setSeparatePG(true); - fromHook.writeSide.close(); - toHook.readSide.close(); + fromAgent.writeSide.close(); + toAgent.readSide.close(); } -HookInstance::~HookInstance() +Agent::~Agent() { try { - toHook.writeSide.close(); + toAgent.writeSide.close(); pid.kill(true); } catch (...) { ignoreException(); @@ -760,7 +764,7 @@ private: Pipe builderOut; /* The build hook. */ - std::shared_ptr hook; + std::shared_ptr hook; /* Whether we're currently doing a chroot build. */ bool useChroot; @@ -1440,7 +1444,7 @@ void DerivationGoal::buildDone() /* Close the read side of the logger pipe. */ if (hook) { hook->builderOut.readSide.close(); - hook->fromHook.readSide.close(); + hook->fromAgent.readSide.close(); } else builderOut.readSide.close(); @@ -1587,8 +1591,16 @@ HookReply DerivationGoal::tryBuildHook() { if (!settings.useBuildHook) return rpDecline; - if (!worker.hook) - worker.hook = std::shared_ptr(new HookInstance); + if (!worker.hook) { + Strings args = { + settings.thisSystem.c_str(), + (format("%1%") % settings.maxSilentTime).str().c_str(), + (format("%1%") % settings.printBuildTrace).str().c_str(), + (format("%1%") % settings.buildTimeout).str().c_str() + }; + + worker.hook = std::shared_ptr(new Agent("offload", args)); + } /* Tell the hook about system features (beyond the system type) required from the build machine. (The hook could parse the @@ -1597,7 +1609,7 @@ HookReply DerivationGoal::tryBuildHook() foreach (Strings::iterator, i, features) checkStoreName(*i); /* !!! abuse */ /* Send the request to the hook. */ - writeLine(worker.hook->toHook.writeSide, (format("%1% %2% %3% %4%") + writeLine(worker.hook->toAgent.writeSide, (format("%1% %2% %3% %4%") % (worker.getNrLocalBuilds() < settings.maxBuildJobs ? "1" : "0") % drv.platform % drvPath % concatStringsSep(",", features)).str()); @@ -1605,7 +1617,7 @@ HookReply DerivationGoal::tryBuildHook() whether the hook wishes to perform the build. */ string reply; while (true) { - string s = readLine(worker.hook->fromHook.readSide); + string s = readLine(worker.hook->fromAgent.readSide); if (string(s, 0, 2) == "# ") { reply = string(s, 2); break; @@ -1637,21 +1649,21 @@ HookReply DerivationGoal::tryBuildHook() string s; foreach (PathSet::iterator, i, allInputs) { s += *i; s += ' '; } - writeLine(hook->toHook.writeSide, s); + writeLine(hook->toAgent.writeSide, s); /* Tell the hooks the missing outputs that have to be copied back from the remote system. */ s = ""; foreach (PathSet::iterator, i, missingPaths) { s += *i; s += ' '; } - writeLine(hook->toHook.writeSide, s); + writeLine(hook->toAgent.writeSide, s); - hook->toHook.writeSide.close(); + hook->toAgent.writeSide.close(); /* Create the log file and pipe. */ Path logFile = openLogFile(); set fds; - fds.insert(hook->fromHook.readSide); + fds.insert(hook->fromAgent.readSide); fds.insert(hook->builderOut.readSide); worker.childStarted(shared_from_this(), hook->pid, fds, false, true); @@ -2785,7 +2797,7 @@ void DerivationGoal::handleChildOutput(int fd, const string & data) writeFull(fdLogFile, data); } - if (hook && fd == hook->fromHook.readSide) + if (hook && fd == hook->fromAgent.readSide) writeToStderr(prefix + data); } -- 2.28.0 From debbugs-submit-bounces@debbugs.gnu.org Fri Sep 11 10:52:26 2020 Received: (at 43340) by debbugs.gnu.org; 11 Sep 2020 14:52:27 +0000 Received: from localhost ([127.0.0.1]:45107 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kGkPa-0006bu-4C for submit@debbugs.gnu.org; Fri, 11 Sep 2020 10:52:26 -0400 Received: from eggs.gnu.org ([209.51.188.92]:57668) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kGkPY-0006bS-Bx for 43340@debbugs.gnu.org; Fri, 11 Sep 2020 10:52:24 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:40476) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kGkPS-0007wC-Nq; Fri, 11 Sep 2020 10:52:18 -0400 Received: from [2a01:e0a:1d:7270:af76:b9b:ca24:c465] (port=39314 helo=gnu.org) by fencepost.gnu.org with esmtpsa (TLS1.2:DHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1kGkPP-0004Uo-GV; Fri, 11 Sep 2020 10:52:18 -0400 From: =?UTF-8?q?Ludovic=20Court=C3=A8s?= To: 43340@debbugs.gnu.org Subject: [PATCH 2/5] daemon: Isolate signing and signature verification functions. Date: Fri, 11 Sep 2020 16:51:51 +0200 Message-Id: <20200911145154.15057-2-ludo@gnu.org> X-Mailer: git-send-email 2.28.0 In-Reply-To: <20200911145154.15057-1-ludo@gnu.org> References: <20200911145154.15057-1-ludo@gnu.org> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 43340 Cc: =?UTF-8?q?Ludovic=20Court=C3=A8s?= X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -3.3 (---) * nix/libstore/local-store.cc (signHash, verifySignature): New functions. (LocalStore::exportPath): Use 'signHash' instead of inline code. (LocalStore::importPath): Use 'verifySignature' instead of inline code. --- nix/libstore/local-store.cc | 43 ++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/nix/libstore/local-store.cc b/nix/libstore/local-store.cc index e6badd3721..cbbd8e901d 100644 --- a/nix/libstore/local-store.cc +++ b/nix/libstore/local-store.cc @@ -1238,6 +1238,34 @@ static std::string runAuthenticationProgram(const Strings & args) return runProgram(settings.guixProgram, false, fullArgs); } +/* Sign HASH with the key stored in file SECRETKEY. Return the signature as a + string, or raise an exception upon error. */ +static std::string signHash(const string &secretKey, const Hash &hash) +{ + Strings args; + args.push_back("sign"); + args.push_back(secretKey); + args.push_back(printHash(hash)); + + return runAuthenticationProgram(args); +} + +/* Verify SIGNATURE and return the base16-encoded hash over which it was + computed. */ +static std::string verifySignature(const string &signature) +{ + Path tmpDir = createTempDir("", "guix", true, true, 0700); + AutoDelete delTmp(tmpDir); + + Path sigFile = tmpDir + "/sig"; + writeFile(sigFile, signature); + + Strings args; + args.push_back("verify"); + args.push_back(sigFile); + return runAuthenticationProgram(args); +} + void LocalStore::exportPath(const Path & path, bool sign, Sink & sink) { @@ -1280,12 +1308,7 @@ void LocalStore::exportPath(const Path & path, bool sign, Path secretKey = settings.nixConfDir + "/signing-key.sec"; checkSecrecy(secretKey); - Strings args; - args.push_back("sign"); - args.push_back(secretKey); - args.push_back(printHash(hash)); - - string signature = runAuthenticationProgram(args); + string signature = signHash(secretKey, hash); writeString(signature, hashAndWriteSink); @@ -1364,13 +1387,7 @@ Path LocalStore::importPath(bool requireSignature, Source & source) string signature = readString(hashAndReadSource); if (requireSignature) { - Path sigFile = tmpDir + "/sig"; - writeFile(sigFile, signature); - - Strings args; - args.push_back("verify"); - args.push_back(sigFile); - string hash2 = runAuthenticationProgram(args); + string hash2 = verifySignature(signature); /* Note: runProgram() throws an exception if the signature is invalid. */ -- 2.28.0 From debbugs-submit-bounces@debbugs.gnu.org Fri Sep 11 10:52:30 2020 Received: (at 43340) by debbugs.gnu.org; 11 Sep 2020 14:52:30 +0000 Received: from localhost ([127.0.0.1]:45110 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kGkPd-0006cG-TZ for submit@debbugs.gnu.org; Fri, 11 Sep 2020 10:52:30 -0400 Received: from eggs.gnu.org ([209.51.188.92]:57684) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kGkPZ-0006bW-LX for 43340@debbugs.gnu.org; Fri, 11 Sep 2020 10:52:26 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:40478) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kGkPU-0007wX-Bi; Fri, 11 Sep 2020 10:52:20 -0400 Received: from [2a01:e0a:1d:7270:af76:b9b:ca24:c465] (port=39314 helo=gnu.org) by fencepost.gnu.org with esmtpsa (TLS1.2:DHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1kGkPT-0004Uo-Sd; Fri, 11 Sep 2020 10:52:20 -0400 From: =?UTF-8?q?Ludovic=20Court=C3=A8s?= To: 43340@debbugs.gnu.org Subject: [PATCH 4/5] daemon: Spawn 'guix authenticate' once for all. Date: Fri, 11 Sep 2020 16:51:53 +0200 Message-Id: <20200911145154.15057-4-ludo@gnu.org> X-Mailer: git-send-email 2.28.0 In-Reply-To: <20200911145154.15057-1-ludo@gnu.org> References: <20200911145154.15057-1-ludo@gnu.org> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 43340 Cc: =?UTF-8?q?Ludovic=20Court=C3=A8s?= X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -3.3 (---) Previously, we'd spawn 'guix authenticate' once for each item that has to be signed (when exporting) or authenticated (when importing). Now, we spawn it once for all and then follow a request/reply protocol. This reduces the wall-clock time of: guix archive --export -r $(guix build coreutils -d) from 30s to 2s. * guix/scripts/authenticate.scm (sign-with-key): Return the signature instead of displaying it. Raise a &formatted-message instead of calling 'leave'. (validate-signature): Likewise. (read-command): New procedure. (guix-authenticate)[send-reply]: New procedure. Change to read commands from current-input-port. * nix/libstore/local-store.cc (runAuthenticationProgram): Remove. (authenticationAgent, readInteger, readAuthenticateReply): New functions. (signHash, verifySignature): Rewrite in terms of the agent. * tests/store.scm ("import not signed"): Remove 'pk' call. ("import signed by unauthorized key"): Check the error message of C. * tests/guix-authenticate.sh: Rewrite using the new protocol. --- guix/scripts/authenticate.scm | 119 ++++++++++++++++++++++++++-------- nix/libstore/local-store.cc | 86 +++++++++++++++++++----- tests/guix-authenticate.sh | 45 +++++++------ tests/store.scm | 8 +-- 4 files changed, 190 insertions(+), 68 deletions(-) diff --git a/guix/scripts/authenticate.scm b/guix/scripts/authenticate.scm index fceac13a84..34737481d5 100644 --- a/guix/scripts/authenticate.scm +++ b/guix/scripts/authenticate.scm @@ -21,6 +21,10 @@ #:use-module (gcrypt pk-crypto) #:use-module (guix pki) #:use-module (guix ui) + #:use-module (guix diagnostics) + #:use-module (srfi srfi-34) + #:use-module (srfi srfi-35) + #:use-module (rnrs bytevectors) #:use-module (ice-9 binary-ports) #:use-module (ice-9 rdelim) #:use-module (ice-9 match) @@ -39,41 +43,77 @@ (compose string->canonical-sexp read-string)) (define (sign-with-key key-file sha256) - "Sign the hash SHA256 (a bytevector) with KEY-FILE, and write an sexp that -includes both the hash and the actual signature." + "Sign the hash SHA256 (a bytevector) with KEY-FILE, and return the signature +as a canonical sexp that includes both the hash and the actual signature." (let* ((secret-key (call-with-input-file key-file read-canonical-sexp)) (public-key (if (string-suffix? ".sec" key-file) (call-with-input-file (string-append (string-drop-right key-file 4) ".pub") read-canonical-sexp) - (leave - (G_ "cannot find public key for secret key '~a'~%") - key-file))) + (raise + (formatted-message + (G_ "cannot find public key for secret key '~a'~%") + key-file)))) (data (bytevector->hash-data sha256 #:key-type (key-type public-key))) (signature (signature-sexp data secret-key public-key))) - (display (canonical-sexp->string signature)) - #t)) + signature)) (define (validate-signature signature) "Validate SIGNATURE, a canonical sexp. Check whether its public key is -authorized, verify the signature, and print the signed data to stdout upon -success." +authorized, verify the signature, and return the signed data (a bytevector) +upon success." (let* ((subject (signature-subject signature)) (data (signature-signed-data signature))) (if (and data subject) (if (authorized-key? subject) (if (valid-signature? signature) - (let ((hash (hash-data->bytevector data))) - (display (bytevector->base16-string hash)) - #t) ; success - (leave (G_ "error: invalid signature: ~a~%") - (canonical-sexp->string signature))) - (leave (G_ "error: unauthorized public key: ~a~%") - (canonical-sexp->string subject))) - (leave (G_ "error: corrupt signature data: ~a~%") - (canonical-sexp->string signature))))) + (hash-data->bytevector data) ; success + (raise + (formatted-message (G_ "invalid signature: ~a") + (canonical-sexp->string signature)))) + (raise + (formatted-message (G_ "unauthorized public key: ~a") + (canonical-sexp->string subject)))) + (raise + (formatted-message (G_ "corrupt signature data: ~a") + (canonical-sexp->string signature)))))) + +(define (read-command port) + "Read a command from PORT and return the command and arguments as a list of +strings. Return the empty list when the end-of-file is reached. + +Commands are newline-terminated and must look something like this: + + COMMAND 3:abc 5:abcde 1:x + +where COMMAND is an alphanumeric sequence and the remainder is the command +arguments. Each argument is written as its length (in characters), followed +by colon, followed by the given number of characters." + (define (consume-whitespace port) + (let ((chr (lookahead-u8 port))) + (when (eqv? chr (char->integer #\space)) + (get-u8 port) + (consume-whitespace port)))) + + (match (read-delimited " \t\n\r" port) + ((? eof-object?) + '()) + (command + (let loop ((result (list command))) + (consume-whitespace port) + (let ((next (lookahead-u8 port))) + (cond ((eqv? next (char->integer #\newline)) + (get-u8 port) + (reverse result)) + ((eof-object? next) + (reverse result)) + (else + (let* ((len (string->number (read-delimited ":" port))) + (str (utf8->string + (get-bytevector-n port len)))) + (loop (cons str result)))))))))) ;;; @@ -81,6 +121,13 @@ success." ;;; (define (guix-authenticate . args) + (define (send-reply code str) + ;; Send CODE and STR as a reply to our client. + (let ((bv (string->utf8 str))) + (format #t "~a ~a:" code (bytevector-length bv)) + (put-bytevector (current-output-port) bv) + (force-output (current-output-port)))) + ;; Signature sexps written to stdout may contain binary data, so force ;; ISO-8859-1 encoding so that things are not mangled. See ;; for details. @@ -91,21 +138,37 @@ success." (with-fluids ((%default-port-encoding "ISO-8859-1") (%default-port-conversion-strategy 'error)) (match args - (("sign" key-file hash) - (sign-with-key key-file (base16-string->bytevector hash))) - (("verify" signature-file) - (call-with-input-file signature-file - (lambda (port) - (validate-signature (string->canonical-sexp - (read-string port)))))) - (("--help") (display (G_ "Usage: guix authenticate OPTION... Sign or verify the signature on the given file. This tool is meant to be used internally by 'guix-daemon'.\n"))) (("--version") (show-version-and-exit "guix authenticate")) - (else - (leave (G_ "wrong arguments")))))) + (() + (let loop () + (guard (c ((formatted-message? c) + (send-reply 500 + (apply format #f + (G_ (formatted-message-string c)) + (formatted-message-arguments c))))) + ;; Read a request on standard input and reply. + (match (read-command (current-input-port)) + (("sign" signing-key (= base16-string->bytevector hash)) + (let ((signature (sign-with-key signing-key hash))) + (send-reply 0 (canonical-sexp->string signature)))) + (("verify" signature) + (send-reply 0 + (bytevector->base16-string + (validate-signature + (string->canonical-sexp signature))))) + (() + (exit 0)) + (commands + (warning (G_ "~s: invalid command; ignoring~%") commands) + (send-reply 404 "invalid command")))) + + (loop))) + (_ + (leave (G_ "wrong arguments~%")))))) ;;; authenticate.scm ends here diff --git a/nix/libstore/local-store.cc b/nix/libstore/local-store.cc index cbbd8e901d..9bc7a0bc4f 100644 --- a/nix/libstore/local-store.cc +++ b/nix/libstore/local-store.cc @@ -1231,39 +1231,91 @@ static void checkSecrecy(const Path & path) } -static std::string runAuthenticationProgram(const Strings & args) +/* Return the authentication agent, a "guix authenticate" process started + lazily. */ +static std::shared_ptr authenticationAgent() { - Strings fullArgs = { "authenticate" }; - fullArgs.insert(fullArgs.end(), args.begin(), args.end()); // append - return runProgram(settings.guixProgram, false, fullArgs); + static std::shared_ptr agent; + + if (!agent) { + Strings args = { "authenticate" }; + agent = std::shared_ptr(new Agent(settings.guixProgram, args)); + } + + return agent; +} + +/* Read an integer and the byte that immediately follows it from FD. Return + the integer. */ +static int readInteger(int fd) +{ + string str; + + while (1) { + char ch; + ssize_t rd = read(fd, &ch, 1); + if (rd == -1) { + if (errno != EINTR) + throw SysError("reading an integer"); + } else if (rd == 0) + throw EndOfFile("unexpected EOF reading an integer"); + else { + if (strchr("0123456789", ch)) { + str += ch; + } else { + break; + } + } + } + + return stoi(str); +} + +/* Read from FD a reply coming from 'guix authenticate'. The reply has the + form "CODE LEN:STR". CODE is an integer, where zero indicates success. + LEN specifies the length in bytes of the string that immediately + follows. */ +static std::string readAuthenticateReply(int fd) +{ + int code = readInteger(fd); + int len = readInteger(fd); + + string str; + str.resize(len); + readFull(fd, (unsigned char *) &str[0], len); + + if (code == 0) + return str; + else + throw Error(str); } /* Sign HASH with the key stored in file SECRETKEY. Return the signature as a string, or raise an exception upon error. */ static std::string signHash(const string &secretKey, const Hash &hash) { - Strings args; - args.push_back("sign"); - args.push_back(secretKey); - args.push_back(printHash(hash)); + auto agent = authenticationAgent(); + auto hexHash = printHash(hash); - return runAuthenticationProgram(args); + writeLine(agent->toAgent.writeSide, + (format("sign %1%:%2% %3%:%4%") + % secretKey.size() % secretKey + % hexHash.size() % hexHash).str()); + + return readAuthenticateReply(agent->fromAgent.readSide); } /* Verify SIGNATURE and return the base16-encoded hash over which it was computed. */ static std::string verifySignature(const string &signature) { - Path tmpDir = createTempDir("", "guix", true, true, 0700); - AutoDelete delTmp(tmpDir); + auto agent = authenticationAgent(); - Path sigFile = tmpDir + "/sig"; - writeFile(sigFile, signature); + writeLine(agent->toAgent.writeSide, + (format("verify %1%:%2%") + % signature.size() % signature).str()); - Strings args; - args.push_back("verify"); - args.push_back(sigFile); - return runAuthenticationProgram(args); + return readAuthenticateReply(agent->fromAgent.readSide); } void LocalStore::exportPath(const Path & path, bool sign, diff --git a/tests/guix-authenticate.sh b/tests/guix-authenticate.sh index 773443453d..f3b36ee41d 100644 --- a/tests/guix-authenticate.sh +++ b/tests/guix-authenticate.sh @@ -28,33 +28,38 @@ rm -f "$sig" "$hash" trap 'rm -f "$sig" "$hash"' EXIT +key="$abs_top_srcdir/tests/signing-key.sec" +key_len="`echo -n $key | wc -c`" + # A hexadecimal string as long as a sha256 hash. hash="2749f0ea9f26c6c7be746a9cff8fa4c2f2a02b000070dba78429e9a11f87c6eb" +hash_len="`echo -n $hash | wc -c`" -guix authenticate sign \ - "$abs_top_srcdir/tests/signing-key.sec" \ - "$hash" > "$sig" +echo "sign $key_len:$key $hash_len:$hash" | guix authenticate > "$sig" test -f "$sig" +case "$(cat $sig)" in + "0 "*) ;; + *) echo "broken signature: $(cat $sig)" + exit 42;; +esac -hash2="`guix authenticate verify "$sig"`" -test "$hash2" = "$hash" +# Remove the leading "0". +sed -i "$sig" -e's/^0 //g' + +hash2="$(echo verify $(cat "$sig") | guix authenticate)" +test "$(echo $hash2 | cut -d : -f 2)" = "$hash" # Detect corrupt signatures. -if guix authenticate verify /dev/null -then false -else true -fi +code="$(echo "verify 5:wrong" | guix authenticate | cut -f1 -d ' ')" +test "$code" -ne 0 # Detect invalid signatures. # The signature has (payload (data ... (hash sha256 #...#))). We proceed by # modifying this hash. sed -i "$sig" \ -e's|#[A-Z0-9]\{64\}#|#0000000000000000000000000000000000000000000000000000000000000000#|g' -if guix authenticate verify "$sig" -then false -else true -fi - +code="$(echo "verify $(cat $sig)" | guix authenticate | cut -f1 -d ' ')" +test "$code" -ne 0 # Test for : make sure 'guix authenticate' produces # valid signatures when run in the C locale. @@ -63,9 +68,11 @@ hash="5eff0b55c9c5f5e87b4e34cd60a2d5654ca1eb78c7b3c67c3179fed1cff07b4c" LC_ALL=C export LC_ALL -guix authenticate sign "$abs_top_srcdir/tests/signing-key.sec" "$hash" \ - > "$sig" +echo "sign $key_len:$key $hash_len:$hash" | guix authenticate > "$sig" -guix authenticate verify "$sig" -hash2="`guix authenticate verify "$sig"`" -test "$hash2" = "$hash" +# Remove the leading "0". +sed -i "$sig" -e's/^0 //g' + +echo "verify $(cat $sig)" | guix authenticate +hash2="$(echo "verify $(cat $sig)" | guix authenticate | cut -f2 -d ' ')" +test "$(echo $hash2 | cut -d : -f 2)" = "$hash" diff --git a/tests/store.scm b/tests/store.scm index 8ff76e8f98..3a2a21a250 100644 --- a/tests/store.scm +++ b/tests/store.scm @@ -990,7 +990,7 @@ ;; Ensure 'import-paths' raises an exception. (guard (c ((store-protocol-error? c) - (and (not (zero? (store-protocol-error-status (pk 'C c)))) + (and (not (zero? (store-protocol-error-status c))) (string-contains (store-protocol-error-message c) "lacks a signature")))) (let* ((source (open-bytevector-input-port dump)) @@ -1030,9 +1030,9 @@ ;; Ensure 'import-paths' raises an exception. (guard (c ((store-protocol-error? c) - ;; XXX: The daemon-provided error message currently doesn't - ;; mention the reason of the failure. - (not (zero? (store-protocol-error-status c))))) + (and (not (zero? (store-protocol-error-status c))) + (string-contains (store-protocol-error-message c) + "unauthorized public key")))) (let* ((source (open-bytevector-input-port dump)) (imported (import-paths %store source))) (pk 'unauthorized-imported imported) -- 2.28.0 From debbugs-submit-bounces@debbugs.gnu.org Fri Sep 11 10:52:31 2020 Received: (at 43340) by debbugs.gnu.org; 11 Sep 2020 14:52:31 +0000 Received: from localhost ([127.0.0.1]:45112 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kGkPe-0006cJ-K1 for submit@debbugs.gnu.org; Fri, 11 Sep 2020 10:52:31 -0400 Received: from eggs.gnu.org ([209.51.188.92]:57696) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kGkPa-0006bc-I9 for 43340@debbugs.gnu.org; Fri, 11 Sep 2020 10:52:27 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:40479) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kGkPV-0007we-87; Fri, 11 Sep 2020 10:52:21 -0400 Received: from [2a01:e0a:1d:7270:af76:b9b:ca24:c465] (port=39314 helo=gnu.org) by fencepost.gnu.org with esmtpsa (TLS1.2:DHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1kGkPU-0004Uo-MB; Fri, 11 Sep 2020 10:52:21 -0400 From: =?UTF-8?q?Ludovic=20Court=C3=A8s?= To: 43340@debbugs.gnu.org Subject: [PATCH 5/5] authenticate: Cache the ACL and key pairs. Date: Fri, 11 Sep 2020 16:51:54 +0200 Message-Id: <20200911145154.15057-5-ludo@gnu.org> X-Mailer: git-send-email 2.28.0 In-Reply-To: <20200911145154.15057-1-ludo@gnu.org> References: <20200911145154.15057-1-ludo@gnu.org> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 43340 Cc: =?UTF-8?q?Ludovic=20Court=C3=A8s?= X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -3.3 (---) In practice we're always using the same key pair, /etc/guix/signing-key.{pub,sec}. Keeping them in cache allows us to avoid redundant I/O and parsing when signing multiple store items in a row. * guix/scripts/authenticate.scm (load-key-pair): New procedure. (sign-with-key): Remove 'key-file' parameter and add 'public-key' and 'secret-key'. Adjust accordingly. (validate-signature): Add 'acl' parameter and pass it to 'authorized-key?'. (guix-authenticate): Call 'current-acl' upfront and cache its result. Add 'key-pairs' as an argument to 'loop' and use it as a cache of key pairs. --- guix/scripts/authenticate.scm | 108 +++++++++++++++++++++------------- 1 file changed, 66 insertions(+), 42 deletions(-) diff --git a/guix/scripts/authenticate.scm b/guix/scripts/authenticate.scm index 34737481d5..95005641c4 100644 --- a/guix/scripts/authenticate.scm +++ b/guix/scripts/authenticate.scm @@ -24,10 +24,12 @@ #:use-module (guix diagnostics) #:use-module (srfi srfi-34) #:use-module (srfi srfi-35) + #:use-module (srfi srfi-71) #:use-module (rnrs bytevectors) #:use-module (ice-9 binary-ports) #:use-module (ice-9 rdelim) #:use-module (ice-9 match) + #:use-module (ice-9 vlist) #:export (guix-authenticate)) ;;; Commentary: @@ -42,32 +44,40 @@ ;; Read a gcrypt sexp from a port and return it. (compose string->canonical-sexp read-string)) -(define (sign-with-key key-file sha256) - "Sign the hash SHA256 (a bytevector) with KEY-FILE, and return the signature -as a canonical sexp that includes both the hash and the actual signature." - (let* ((secret-key (call-with-input-file key-file read-canonical-sexp)) - (public-key (if (string-suffix? ".sec" key-file) - (call-with-input-file +(define (load-key-pair key-file) + "Load the key pair whose secret key lives at KEY-FILE. Return a pair of +canonical sexps representing those keys." + (catch 'system-error + (lambda () + (let* ((secret-key (call-with-input-file key-file read-canonical-sexp)) + (public-key (call-with-input-file (string-append (string-drop-right key-file 4) ".pub") - read-canonical-sexp) - (raise - (formatted-message - (G_ "cannot find public key for secret key '~a'~%") - key-file)))) - (data (bytevector->hash-data sha256 - #:key-type (key-type public-key))) - (signature (signature-sexp data secret-key public-key))) - signature)) + read-canonical-sexp))) + (cons public-key secret-key))) + (lambda args + (let ((errno (system-error-errno args))) + (raise + (formatted-message + (G_ "failed to load key pair at '~a': ~a~%") + key-file (strerror errno))))))) -(define (validate-signature signature) +(define (sign-with-key public-key secret-key sha256) + "Sign the hash SHA256 (a bytevector) with SECRET-KEY (a canonical sexp), and +return the signature as a canonical sexp that includes SHA256, PUBLIC-KEY, and +the actual signature." + (let ((data (bytevector->hash-data sha256 + #:key-type (key-type public-key)))) + (signature-sexp data secret-key public-key))) + +(define (validate-signature signature acl) "Validate SIGNATURE, a canonical sexp. Check whether its public key is -authorized, verify the signature, and return the signed data (a bytevector) -upon success." +authorized in ACL, verify the signature, and return the signed data (a +bytevector) upon success." (let* ((subject (signature-subject signature)) (data (signature-signed-data signature))) (if (and data subject) - (if (authorized-key? subject) + (if (authorized-key? subject acl) (if (valid-signature? signature) (hash-data->bytevector data) ; success (raise @@ -145,29 +155,43 @@ be used internally by 'guix-daemon'.\n"))) (("--version") (show-version-and-exit "guix authenticate")) (() - (let loop () - (guard (c ((formatted-message? c) - (send-reply 500 - (apply format #f - (G_ (formatted-message-string c)) - (formatted-message-arguments c))))) - ;; Read a request on standard input and reply. - (match (read-command (current-input-port)) - (("sign" signing-key (= base16-string->bytevector hash)) - (let ((signature (sign-with-key signing-key hash))) - (send-reply 0 (canonical-sexp->string signature)))) - (("verify" signature) - (send-reply 0 - (bytevector->base16-string - (validate-signature - (string->canonical-sexp signature))))) - (() - (exit 0)) - (commands - (warning (G_ "~s: invalid command; ignoring~%") commands) - (send-reply 404 "invalid command")))) - - (loop))) + (let ((acl (current-acl))) + (let loop ((key-pairs vlist-null)) + (guard (c ((formatted-message? c) + (send-reply 500 + (apply format #f + (G_ (formatted-message-string c)) + (formatted-message-arguments c))))) + ;; Read a request on standard input and reply. + (match (read-command (current-input-port)) + (("sign" signing-key (= base16-string->bytevector hash)) + (let* ((key-pairs keys + (match (vhash-assoc signing-key key-pairs) + ((_ . keys) + (values key-pairs keys)) + (#f + (let ((keys (load-key-pair signing-key))) + (values (vhash-cons signing-key keys + key-pairs) + keys))))) + (signature (match keys + ((public . secret) + (sign-with-key public secret hash))))) + (send-reply 0 (canonical-sexp->string signature)) + (loop key-pairs))) + (("verify" signature) + (send-reply 0 + (bytevector->base16-string + (validate-signature + (string->canonical-sexp signature) + acl))) + (loop key-pairs)) + (() + (exit 0)) + (commands + (warning (G_ "~s: invalid command; ignoring~%") commands) + (send-reply 404 "invalid command") + (loop key-pairs))))))) (_ (leave (G_ "wrong arguments~%")))))) -- 2.28.0 From debbugs-submit-bounces@debbugs.gnu.org Fri Sep 11 10:52:34 2020 Received: (at 43340) by debbugs.gnu.org; 11 Sep 2020 14:52:34 +0000 Received: from localhost ([127.0.0.1]:45114 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kGkPi-0006cg-3F for submit@debbugs.gnu.org; Fri, 11 Sep 2020 10:52:34 -0400 Received: from eggs.gnu.org ([209.51.188.92]:57674) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kGkPY-0006bU-RW for 43340@debbugs.gnu.org; Fri, 11 Sep 2020 10:52:32 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:40477) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kGkPT-0007wR-IH; Fri, 11 Sep 2020 10:52:19 -0400 Received: from [2a01:e0a:1d:7270:af76:b9b:ca24:c465] (port=39314 helo=gnu.org) by fencepost.gnu.org with esmtpsa (TLS1.2:DHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1kGkPT-0004Uo-21; Fri, 11 Sep 2020 10:52:19 -0400 From: =?UTF-8?q?Ludovic=20Court=C3=A8s?= To: 43340@debbugs.gnu.org Subject: [PATCH 3/5] daemon: Move 'Agent' to libutil. Date: Fri, 11 Sep 2020 16:51:52 +0200 Message-Id: <20200911145154.15057-3-ludo@gnu.org> X-Mailer: git-send-email 2.28.0 In-Reply-To: <20200911145154.15057-1-ludo@gnu.org> References: <20200911145154.15057-1-ludo@gnu.org> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 43340 Cc: =?UTF-8?q?Ludovic=20Court=C3=A8s?= X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -3.3 (---) * nix/libstore/build.cc (DerivationGoal::tryBuildHook): Add "offload" to 'args' and pass settings.guixProgram as the first argument to Agent::Agent. (pathNullDevice, commonChildInit, Agent, Agent::Agent) (Agent::~Agent): Move to... * nix/libutil/util.cc: ... here. * nix/libutil/util.hh (struct Agent, commonChildInit): New declarations. --- nix/libstore/build.cc | 118 +----------------------------------------- nix/libutil/util.cc | 84 ++++++++++++++++++++++++++++++ nix/libutil/util.hh | 25 +++++++++ 3 files changed, 111 insertions(+), 116 deletions(-) diff --git a/nix/libstore/build.cc b/nix/libstore/build.cc index b6fad493a9..73532dd24f 100644 --- a/nix/libstore/build.cc +++ b/nix/libstore/build.cc @@ -80,9 +80,6 @@ namespace nix { using std::map; -static string pathNullDevice = "/dev/null"; - - /* Forward definition. */ class Worker; struct Agent; @@ -397,33 +394,6 @@ void Goal::trace(const format & f) ////////////////////////////////////////////////////////////////////// -/* Common initialisation performed in child processes. */ -static void commonChildInit(Pipe & logPipe) -{ - /* Put the child in a separate session (and thus a separate - process group) so that it has no controlling terminal (meaning - that e.g. ssh cannot open /dev/tty) and it doesn't receive - terminal signals. */ - if (setsid() == -1) - throw SysError(format("creating a new session")); - - /* Dup the write side of the logger pipe into stderr. */ - if (dup2(logPipe.writeSide, STDERR_FILENO) == -1) - throw SysError("cannot pipe standard error into log file"); - - /* Dup stderr to stdout. */ - if (dup2(STDERR_FILENO, STDOUT_FILENO) == -1) - throw SysError("cannot dup stderr into stdout"); - - /* Reroute stdin to /dev/null. */ - int fdDevNull = open(pathNullDevice.c_str(), O_RDWR); - if (fdDevNull == -1) - throw SysError(format("cannot open `%1%'") % pathNullDevice); - if (dup2(fdDevNull, STDIN_FILENO) == -1) - throw SysError("cannot dup null device into stdin"); - close(fdDevNull); -} - /* Restore default handling of SIGPIPE, otherwise some programs will randomly say "Broken pipe". */ static void restoreSIGPIPE() @@ -586,91 +556,6 @@ void UserLock::kill() killUser(uid); } - -////////////////////////////////////////////////////////////////////// - - -/* An "agent" is a helper program that runs in the background and that we talk - to over pipes, such as the "guix offload" program. */ -struct Agent -{ - /* Pipes for talking to the agent. */ - Pipe toAgent; - - /* Pipe for the agent's standard output/error. */ - Pipe fromAgent; - - /* Pipe for build standard output/error--e.g., for build processes started - by "guix offload". */ - Pipe builderOut; - - /* The process ID of the agent. */ - Pid pid; - - /* The 'guix' sub-command and arguments passed to the agent. */ - Agent(const string &command, const Strings &args); - - ~Agent(); -}; - - -Agent::Agent(const string &command, const Strings &args) -{ - debug(format("starting agent '%1%'") % command); - - const Path &buildHook = settings.guixProgram; - - /* Create a pipe to get the output of the child. */ - fromAgent.create(); - - /* Create the communication pipes. */ - toAgent.create(); - - /* Create a pipe to get the output of the builder. */ - builderOut.create(); - - /* Fork the hook. */ - pid = startProcess([&]() { - - commonChildInit(fromAgent); - - if (chdir("/") == -1) throw SysError("changing into `/"); - - /* Dup the communication pipes. */ - if (dup2(toAgent.readSide, STDIN_FILENO) == -1) - throw SysError("dupping to-hook read side"); - - /* Use fd 4 for the builder's stdout/stderr. */ - if (dup2(builderOut.writeSide, 4) == -1) - throw SysError("dupping builder's stdout/stderr"); - - Strings allArgs; - allArgs.push_back(buildHook); - allArgs.push_back(command); - allArgs.insert(allArgs.end(), args.begin(), args.end()); // append - - execv(buildHook.c_str(), stringsToCharPtrs(allArgs).data()); - - throw SysError(format("executing `%1% %2%'") % buildHook % command); - }); - - pid.setSeparatePG(true); - fromAgent.writeSide.close(); - toAgent.readSide.close(); -} - - -Agent::~Agent() -{ - try { - toAgent.writeSide.close(); - pid.kill(true); - } catch (...) { - ignoreException(); - } -} - - ////////////////////////////////////////////////////////////////////// @@ -1593,13 +1478,14 @@ HookReply DerivationGoal::tryBuildHook() if (!worker.hook) { Strings args = { + "offload", settings.thisSystem.c_str(), (format("%1%") % settings.maxSilentTime).str().c_str(), (format("%1%") % settings.printBuildTrace).str().c_str(), (format("%1%") % settings.buildTimeout).str().c_str() }; - worker.hook = std::shared_ptr(new Agent("offload", args)); + worker.hook = std::shared_ptr(new Agent(settings.guixProgram, args)); } /* Tell the hook about system features (beyond the system type) diff --git a/nix/libutil/util.cc b/nix/libutil/util.cc index 17d145b4c6..59a2981359 100644 --- a/nix/libutil/util.cc +++ b/nix/libutil/util.cc @@ -1142,5 +1142,89 @@ void ignoreException() } } +static const string pathNullDevice = "/dev/null"; + +/* Common initialisation performed in child processes. */ +void commonChildInit(Pipe & logPipe) +{ + /* Put the child in a separate session (and thus a separate + process group) so that it has no controlling terminal (meaning + that e.g. ssh cannot open /dev/tty) and it doesn't receive + terminal signals. */ + if (setsid() == -1) + throw SysError(format("creating a new session")); + + /* Dup the write side of the logger pipe into stderr. */ + if (dup2(logPipe.writeSide, STDERR_FILENO) == -1) + throw SysError("cannot pipe standard error into log file"); + + /* Dup stderr to stdout. */ + if (dup2(STDERR_FILENO, STDOUT_FILENO) == -1) + throw SysError("cannot dup stderr into stdout"); + + /* Reroute stdin to /dev/null. */ + int fdDevNull = open(pathNullDevice.c_str(), O_RDWR); + if (fdDevNull == -1) + throw SysError(format("cannot open `%1%'") % pathNullDevice); + if (dup2(fdDevNull, STDIN_FILENO) == -1) + throw SysError("cannot dup null device into stdin"); + close(fdDevNull); +} + +////////////////////////////////////////////////////////////////////// + +Agent::Agent(const string &command, const Strings &args) +{ + debug(format("starting agent '%1%'") % command); + + /* Create a pipe to get the output of the child. */ + fromAgent.create(); + + /* Create the communication pipes. */ + toAgent.create(); + + /* Create a pipe to get the output of the builder. */ + builderOut.create(); + + /* Fork the hook. */ + pid = startProcess([&]() { + + commonChildInit(fromAgent); + + if (chdir("/") == -1) throw SysError("changing into `/"); + + /* Dup the communication pipes. */ + if (dup2(toAgent.readSide, STDIN_FILENO) == -1) + throw SysError("dupping to-hook read side"); + + /* Use fd 4 for the builder's stdout/stderr. */ + if (dup2(builderOut.writeSide, 4) == -1) + throw SysError("dupping builder's stdout/stderr"); + + Strings allArgs; + allArgs.push_back(command); + allArgs.insert(allArgs.end(), args.begin(), args.end()); // append + + execv(command.c_str(), stringsToCharPtrs(allArgs).data()); + + throw SysError(format("executing `%1%'") % command); + }); + + pid.setSeparatePG(true); + fromAgent.writeSide.close(); + toAgent.readSide.close(); +} + + +Agent::~Agent() +{ + try { + toAgent.writeSide.close(); + pid.kill(true); + } catch (...) { + ignoreException(); + } +} + } diff --git a/nix/libutil/util.hh b/nix/libutil/util.hh index 9e3c14bdd4..13cff44316 100644 --- a/nix/libutil/util.hh +++ b/nix/libutil/util.hh @@ -264,6 +264,29 @@ public: void setKillSignal(int signal); }; +/* An "agent" is a helper program that runs in the background and that we talk + to over pipes, such as the "guix offload" program. */ +struct Agent +{ + /* Pipes for talking to the agent. */ + Pipe toAgent; + + /* Pipe for the agent's standard output/error. */ + Pipe fromAgent; + + /* Pipe for build standard output/error--e.g., for build processes started + by "guix offload". */ + Pipe builderOut; + + /* The process ID of the agent. */ + Pid pid; + + /* The command and arguments passed to the agent. */ + Agent(const string &command, const Strings &args); + + ~Agent(); +}; + /* Kill all processes running under the specified uid by sending them a SIGKILL. */ @@ -295,6 +318,8 @@ void closeMostFDs(const set & exceptions); /* Set the close-on-exec flag for the given file descriptor. */ void closeOnExec(int fd); +/* Common initialisation performed in child processes. */ +void commonChildInit(Pipe & logPipe); /* User interruption. */ -- 2.28.0 From debbugs-submit-bounces@debbugs.gnu.org Fri Sep 11 11:01:58 2020 Received: (at 43340) by debbugs.gnu.org; 11 Sep 2020 15:01:58 +0000 Received: from localhost ([127.0.0.1]:45134 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kGkYn-0006ta-V5 for submit@debbugs.gnu.org; Fri, 11 Sep 2020 11:01:58 -0400 Received: from eggs.gnu.org ([209.51.188.92]:60782) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kGkYm-0006tL-9B for 43340@debbugs.gnu.org; Fri, 11 Sep 2020 11:01:56 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:40687) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kGkYe-00015M-Ct for 43340@debbugs.gnu.org; Fri, 11 Sep 2020 11:01:50 -0400 Received: from [2a01:e0a:1d:7270:af76:b9b:ca24:c465] (port=39364 helo=ribbon) by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1kGkYd-0007m3-Ga for 43340@debbugs.gnu.org; Fri, 11 Sep 2020 11:01:48 -0400 From: =?utf-8?Q?Ludovic_Court=C3=A8s?= To: 43340@debbugs.gnu.org Subject: Re: [bug#43340] [PATCH 0/5] Speed up archive export/import References: <20200911144049.14632-1-ludo@gnu.org> Date: Fri, 11 Sep 2020 17:01:39 +0200 In-Reply-To: <20200911144049.14632-1-ludo@gnu.org> ("Ludovic =?utf-8?Q?Cou?= =?utf-8?Q?rt=C3=A8s=22's?= message of "Fri, 11 Sep 2020 16:40:49 +0200") Message-ID: <87een8tl0s.fsf@gnu.org> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/27.1 (gnu/linux) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 43340 X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -3.3 (---) --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Ludovic Court=C3=A8s skribis: > This patch series goes on top of . > It addresses the performance issue described at: > > https://lists.gnu.org/archive/html/guix-devel/2020-09/msg00073.html > > Specifically, it implements option #4 (spawning =E2=80=98guix authenticat= e=E2=80=99 > once for the whole session, instead of spawning it every time a > store item needs to be signed or authenticated), achieving a ~15x > speedup, which is not bad. :-) Below is the new Gantt chart for: perf timechart record guix archive --export -r $(guix build coreutils -d)= -v3 >/tmp/dump Most of the work happens in =E2=80=98guix authenticate=E2=80=99. --=-=-= Content-Type: image/png Content-Disposition: inline; filename=guix-archive-export-auth-agent-timechart.png Content-Transfer-Encoding: base64 Content-Description: new timechart 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 5QC8d7R27V+XLv1Rb+/m9vbDp0w5bmDg7f+557Jstne/CD/77B8Fg42vvbZ0587Kj1tM07ztttvK 5fLixYvfc2v03HPPXXvttZ2dnbNmzZozZ87WrVsvv/zyvr6+mmp33nnnypUrvV7vUUcddfDBB8fj 8fvuu+/qq6+2LMtZrbOz85vf/OZjjz0my/Kxxx47YcKEtWvX/sd//Mdzzz1XI5AxdvvtVz/xxBPN zc3jx0/u6ur6yU9+smbNq0899dRNN91kmrmZM2cyxpcvf/KWW25xNnzxxRevu+7yV155pbm57cQT T3S51Gefferb3/72rl07RnZxxx3XPvTQQ3V19TNmzEinE/fff/9vf/tbZ52lS3+/YMGC3/72l/tm /J07dz7++IMLFiyYNeuID+LEcrkMAIFAoKY8EKgDAHtooprPV/u9yrq6OgDYsWPH2L28/vo/AWDG jMP2QUMhfOrUqTXl06ZNA4B33+1yauj376OGNbz22ivFYrGtbeqkSZNGOvfHP/7xE088EY22TJo0 affurp/85CerVq1avvyp66+/vlQqzJw5k3P+9NNP/+xntzk+pNh8883fWblyZSQSnjNn3iGHHEII PP744/39PU7hM2ceCgBr1qzGdIwgCIIgyMfOgXbvKJEYfP75O2TZ9dWv/rax8QhV1TXNfO65n6xf /+x+ke/xBD//+Rvvv/+yxx+/fvLkPwPAkiVLtm7desIJJ5xxxhnvpVvi17/+taIot912m7i9Y5rm z3/+83/84x81NS+77LKjjz7a/kpVoVC45ZZbXn/99WXLlp1++umisFQq3XTTTYVC4Yc//OH8+fNF 4TvvvPPDH/7wV7/61ezZs6Pih+QAALB169ZJkw669957A4Fwuay8+uqTd95555IlvykUstdd95+r Vn3zqqs2DwzEvvvd761evXrHjnfC4SkAkEwm77zzLsb49ddfP3Pm3HCYJRLkoYd+/+ijD9999y1/ +MPvnDpv3bq1o2PyPffcoygt4TB7443t1113+ZNPPvm5z10UjdZ9cMszxu666y6Px3vppZea5gcS JTYPg4ODNeWx2AAAxOP9zmqJRO3308Q31vr7+8fuZdOmDQAwadK0fdBQ6Ob0oKCxsREA7O/+iW8P 2ofvV8MaVqxYBgBHH/25kae2bt06bdq0e++9N5drnTBBe+SR537/+zt++9vfZrO5G264Yfz4eW1t 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 --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: base64 DQpMdWRv4oCZLg0K --=-=-=-- From debbugs-submit-bounces@debbugs.gnu.org Sat Sep 12 03:12:20 2020 Received: (at 43340) by debbugs.gnu.org; 12 Sep 2020 07:12:20 +0000 Received: from localhost ([127.0.0.1]:46094 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kGzhs-0000up-Gf for submit@debbugs.gnu.org; Sat, 12 Sep 2020 03:12:20 -0400 Received: from eggs.gnu.org ([209.51.188.92]:57042) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kGzhq-0000ua-FP for 43340@debbugs.gnu.org; Sat, 12 Sep 2020 03:12:18 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:58936) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kGzhk-0001fS-Ii; Sat, 12 Sep 2020 03:12:12 -0400 Received: from [2a01:e0a:19b:d9a0:9d9d:97cc:d92a:8ac0] (port=48100 helo=cervin) by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1kGzhj-0001AX-St; Sat, 12 Sep 2020 03:12:12 -0400 From: Mathieu Othacehe To: Ludovic =?utf-8?Q?Court=C3=A8s?= Subject: Re: [bug#43340] [PATCH 0/5] Speed up archive export/import References: <20200911144049.14632-1-ludo@gnu.org> <87een8tl0s.fsf@gnu.org> Date: Sat, 12 Sep 2020 09:12:09 +0200 In-Reply-To: <87een8tl0s.fsf@gnu.org> ("Ludovic =?utf-8?Q?Court=C3=A8s=22'?= =?utf-8?Q?s?= message of "Fri, 11 Sep 2020 17:01:39 +0200") Message-ID: <87v9gjh3jq.fsf@gnu.org> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/27.1 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 43340 Cc: 43340@debbugs.gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -3.3 (---) Hey Ludo, >> Specifically, it implements option #4 (spawning =E2=80=98guix authentica= te=E2=80=99 >> once for the whole session, instead of spawning it every time a >> store item needs to be signed or authenticated), achieving a ~15x >> speedup, which is not bad. :-) Woo, congrats! > Below is the new Gantt chart for: > > perf timechart record guix archive --export -r $(guix build coreutils -= d) -v3 >/tmp/dump > > Most of the work happens in =E2=80=98guix authenticate=E2=80=99. I never used the "timechart" sub-command but it sounds really nice. Regarding the option you chose, I think it's the more appropriate right now. It's very delicate to dedicate time and effort to tweak guix-daemon and how we use it, having in mind that we'd like to get rid of it. However, the potential short term gains can be so huge, that for now it's the best thing to do. I should just do like you, and dive into it to see what can be done for contention and locking when using 'build-paths' RPC via Cuirass. In the meantime, a short review that I hope to complete next week. Thanks for your efforts, Mathieu From debbugs-submit-bounces@debbugs.gnu.org Sat Sep 12 03:20:15 2020 Received: (at 43340) by debbugs.gnu.org; 12 Sep 2020 07:20:15 +0000 Received: from localhost ([127.0.0.1]:46102 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kGzpX-00016e-Ig for submit@debbugs.gnu.org; Sat, 12 Sep 2020 03:20:15 -0400 Received: from eggs.gnu.org ([209.51.188.92]:58200) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kGzpW-00016R-DL for 43340@debbugs.gnu.org; Sat, 12 Sep 2020 03:20:14 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:59035) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kGzpQ-0002Vl-MX; Sat, 12 Sep 2020 03:20:08 -0400 Received: from [2a01:e0a:19b:d9a0:9d9d:97cc:d92a:8ac0] (port=48206 helo=cervin) by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1kGzpO-0001lU-Cp; Sat, 12 Sep 2020 03:20:07 -0400 From: Mathieu Othacehe To: Ludovic =?utf-8?Q?Court=C3=A8s?= Subject: Re: [bug#43340] [PATCH 4/5] daemon: Spawn 'guix authenticate' once for all. References: <20200911145154.15057-1-ludo@gnu.org> <20200911145154.15057-4-ludo@gnu.org> Date: Sat, 12 Sep 2020 09:20:04 +0200 In-Reply-To: <20200911145154.15057-4-ludo@gnu.org> ("Ludovic =?utf-8?Q?Cou?= =?utf-8?Q?rt=C3=A8s=22's?= message of "Fri, 11 Sep 2020 16:51:53 +0200") Message-ID: <87r1r7h36j.fsf@gnu.org> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/27.1 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 43340 Cc: 43340@debbugs.gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -3.3 (---) > + (send-reply 500 Reply codes could be factorized in an enum. > + if (!agent) { > + Strings args = { "authenticate" }; > + agent = std::shared_ptr(new Agent(settings.guixProgram, args)); > + } make_shared should be preferred to the direct use of new. > + if (strchr("0123456789", ch)) { You can maybe use isdigit? Thanks, Mathieu From debbugs-submit-bounces@debbugs.gnu.org Sat Sep 12 03:21:26 2020 Received: (at 43340) by debbugs.gnu.org; 12 Sep 2020 07:21:26 +0000 Received: from localhost ([127.0.0.1]:46106 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kGzqf-00018q-Up for submit@debbugs.gnu.org; Sat, 12 Sep 2020 03:21:26 -0400 Received: from eggs.gnu.org ([209.51.188.92]:58482) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kGzqe-00018e-Vi for 43340@debbugs.gnu.org; Sat, 12 Sep 2020 03:21:25 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:59053) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kGzqZ-0002nM-Oo; Sat, 12 Sep 2020 03:21:19 -0400 Received: from [2a01:e0a:19b:d9a0:9d9d:97cc:d92a:8ac0] (port=48212 helo=cervin) by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1kGzqX-0001wz-IU; Sat, 12 Sep 2020 03:21:19 -0400 From: Mathieu Othacehe To: Ludovic =?utf-8?Q?Court=C3=A8s?= Subject: Re: [bug#43340] [PATCH 3/5] daemon: Move 'Agent' to libutil. References: <20200911145154.15057-1-ludo@gnu.org> <20200911145154.15057-3-ludo@gnu.org> Date: Sat, 12 Sep 2020 09:21:03 +0200 In-Reply-To: <20200911145154.15057-3-ludo@gnu.org> ("Ludovic =?utf-8?Q?Cou?= =?utf-8?Q?rt=C3=A8s=22's?= message of "Fri, 11 Sep 2020 16:51:52 +0200") Message-ID: <87mu1vh34w.fsf@gnu.org> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/27.1 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 43340 Cc: 43340@debbugs.gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -3.3 (---) This one and the two previous ones look good to me. Thanks, Mathieu From debbugs-submit-bounces@debbugs.gnu.org Sun Sep 13 09:07:46 2020 Received: (at 43340) by debbugs.gnu.org; 13 Sep 2020 13:07:46 +0000 Received: from localhost ([127.0.0.1]:49709 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kHRjO-0001Wv-1c for submit@debbugs.gnu.org; Sun, 13 Sep 2020 09:07:46 -0400 Received: from eggs.gnu.org ([209.51.188.92]:46922) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kHRjL-0001Wh-70 for 43340@debbugs.gnu.org; Sun, 13 Sep 2020 09:07:44 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:57881) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kHRjG-0004y7-1E for 43340@debbugs.gnu.org; Sun, 13 Sep 2020 09:07:38 -0400 Received: from [2a01:e0a:1d:7270:af76:b9b:ca24:c465] (port=47048 helo=ribbon) by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1kHRjD-00082x-BQ; Sun, 13 Sep 2020 09:07:35 -0400 From: =?utf-8?Q?Ludovic_Court=C3=A8s?= To: Mathieu Othacehe Subject: Re: [bug#43340] [PATCH 0/5] Speed up archive export/import References: <20200911144049.14632-1-ludo@gnu.org> <87een8tl0s.fsf@gnu.org> <87v9gjh3jq.fsf@gnu.org> X-URL: http://www.fdn.fr/~lcourtes/ X-Revolutionary-Date: 28 Fructidor an 228 de la =?utf-8?Q?R=C3=A9volution?= X-PGP-Key-ID: 0x090B11993D9AEBB5 X-PGP-Key: http://www.fdn.fr/~lcourtes/ludovic.asc X-PGP-Fingerprint: 3CE4 6455 8A84 FDC6 9DB4 0CFB 090B 1199 3D9A EBB5 X-OS: x86_64-pc-linux-gnu Date: Sun, 13 Sep 2020 15:07:26 +0200 In-Reply-To: <87v9gjh3jq.fsf@gnu.org> (Mathieu Othacehe's message of "Sat, 12 Sep 2020 09:12:09 +0200") Message-ID: <875z8h7rld.fsf@gnu.org> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/27.1 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 43340 Cc: 43340@debbugs.gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -3.3 (---) Hi! Mathieu Othacehe skribis: >>> Specifically, it implements option #4 (spawning =E2=80=98guix authentic= ate=E2=80=99 >>> once for the whole session, instead of spawning it every time a >>> store item needs to be signed or authenticated), achieving a ~15x >>> speedup, which is not bad. :-) > > Woo, congrats! To be clear, it=E2=80=99s 15x on the pathological case where we send only s= mall store items; the difference is obviously less significant if few store items are exported or if the store items are large. Now, I expect it to make a noticeable difference on the typical build farm workload. >> Below is the new Gantt chart for: >> >> perf timechart record guix archive --export -r $(guix build coreutils = -d) -v3 >/tmp/dump >> >> Most of the work happens in =E2=80=98guix authenticate=E2=80=99. > > I never used the "timechart" sub-command but it sounds really > nice. Regarding the option you chose, I think it's the more appropriate > right now. It's very delicate to dedicate time and effort to tweak > guix-daemon and how we use it, having in mind that we'd like to get rid > of it. Yeah. > However, the potential short term gains can be so huge, that for now > it's the best thing to do. I should just do like you, and dive into it > to see what can be done for contention and locking when using > 'build-paths' RPC via Cuirass. Yup, makes sense! > In the meantime, a short review that I hope to complete next week. Thanks for your feedback! Ludo=E2=80=99. From debbugs-submit-bounces@debbugs.gnu.org Mon Sep 14 09:47:45 2020 Received: (at 43340-done) by debbugs.gnu.org; 14 Sep 2020 13:47:45 +0000 Received: from localhost ([127.0.0.1]:53331 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kHopd-0001cb-7Z for submit@debbugs.gnu.org; Mon, 14 Sep 2020 09:47:45 -0400 Received: from eggs.gnu.org ([209.51.188.92]:57990) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kHopb-0001Yl-67 for 43340-done@debbugs.gnu.org; Mon, 14 Sep 2020 09:47:43 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:52148) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kHopV-0003zt-S8 for 43340-done@debbugs.gnu.org; Mon, 14 Sep 2020 09:47:37 -0400 Received: from [2001:660:6102:320:e120:2c8f:8909:cdfe] (port=34768 helo=ribbon) by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1kHopS-00030w-Oz; Mon, 14 Sep 2020 09:47:35 -0400 From: =?utf-8?Q?Ludovic_Court=C3=A8s?= To: Mathieu Othacehe Subject: Re: [bug#43340] [PATCH 0/5] Speed up archive export/import References: <20200911144049.14632-1-ludo@gnu.org> <87een8tl0s.fsf@gnu.org> <87v9gjh3jq.fsf@gnu.org> Date: Mon, 14 Sep 2020 15:47:24 +0200 In-Reply-To: <87v9gjh3jq.fsf@gnu.org> (Mathieu Othacehe's message of "Sat, 12 Sep 2020 09:12:09 +0200") Message-ID: <87r1r4zd03.fsf@gnu.org> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/27.1 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 43340-done Cc: 43340-done@debbugs.gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -3.3 (---) Hello! I went ahead and pushed this series as 7d516c17da50dfc8ce635a21c37533d1fe27b43b. Changes compared to what I posted: =E2=80=A2 Use =E2=80=98make_shared=E2=80=99 and =E2=80=98isdigit=E2=80=99= (I=E2=80=99d never get a C++ developer position=E2=80=A6). =E2=80=A2 Add an enum for reply codes in =E2=80=98guix authenticate=E2=80= =99. =E2=80=A2 Fix a thinko in the last patch of the series: ensure =E2=80=98l= oop=E2=80=99 is called even when an exception is caught. To people who use offloading: please give it a try by running the daemon from a recent checkout with: sudo -E ./pre-inst-env guix-daemon --build-users-group=3Dguixbuild Thanks, Ludo=E2=80=99. From unknown Mon Aug 18 00:06:21 2025 Received: (at fakecontrol) by fakecontrolmessage; To: internal_control@debbugs.gnu.org From: Debbugs Internal Request Subject: Internal Control Message-Id: bug archived. Date: Tue, 13 Oct 2020 11:24:06 +0000 User-Agent: Fakemail v42.6.9 # This is a fake control message. # # The action: # bug archived. thanks # This fakemail brought to you by your local debbugs # administrator