libstore: initialize build hooks via rpc
this removes another file descriptor from the build hook. we are now using only rpc to communicate between build hook and daemon, setting the stage for a future world in which we don't even need build hooks Change-Id: I706d9d3f2a420abd42fc7762c205931a45d3c8c5
This commit is contained in:
+37
-16
@@ -6,11 +6,14 @@
|
||||
#include "lix/libutil/logging.hh"
|
||||
#include "lix/libutil/rpc.hh"
|
||||
#include "lix/libutil/types-rpc.hh"
|
||||
#include "lix/libutil/types.hh"
|
||||
#include <algorithm>
|
||||
#include <capnp/rpc-twoparty.h>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <future>
|
||||
#include <kj/async.h>
|
||||
#include <kj/time.h>
|
||||
#include <set>
|
||||
#include <memory>
|
||||
@@ -44,8 +47,9 @@ namespace {
|
||||
struct Instance final : rpc::build_remote::HookInstance::Server
|
||||
{
|
||||
unsigned int maxBuildJobs;
|
||||
bool initialized = false;
|
||||
|
||||
Instance(unsigned int maxBuildJobs) : maxBuildJobs(maxBuildJobs) {}
|
||||
kj::Promise<void> init(InitContext context) override;
|
||||
|
||||
kj::Promise<void> build(BuildContext context) override;
|
||||
};
|
||||
@@ -375,30 +379,47 @@ static int main_build_remote(AsyncIoRoot & aio, std::string programName, Strings
|
||||
|
||||
verbosity = (Verbosity) std::stoll(argv.front());
|
||||
|
||||
FdSource source(STDIN_FILENO);
|
||||
|
||||
/* Read the parent's settings. */
|
||||
while (readNum<unsigned>(source)) {
|
||||
auto name = readString(source);
|
||||
auto value = readString(source);
|
||||
settings.set(name, value);
|
||||
}
|
||||
|
||||
auto maxBuildJobs = settings.maxBuildJobs;
|
||||
settings.maxBuildJobs.set("1"); // hack to make tests with local?root= work
|
||||
|
||||
initPlugins();
|
||||
|
||||
auto conn = aio.kj.lowLevelProvider->wrapUnixSocketFd(1);
|
||||
capnp::TwoPartyServer srv(kj::heap<Instance>(maxBuildJobs));
|
||||
capnp::TwoPartyServer srv(kj::heap<Instance>());
|
||||
srv.accept(*conn, 1).wait(aio.kj.waitScope);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
kj::Promise<void> Instance::init(InitContext context)
|
||||
{
|
||||
try {
|
||||
if (initialized) {
|
||||
throw Error("build hook can only be initialized once");
|
||||
}
|
||||
|
||||
/* Read the parent's settings. */
|
||||
for (const auto & [name, value] : rpc::to<StringMap>(context.getParams().getSettings())) {
|
||||
settings.set(name, value);
|
||||
}
|
||||
|
||||
maxBuildJobs = settings.maxBuildJobs;
|
||||
settings.maxBuildJobs.set("1"); // hack to make tests with local?root= work
|
||||
|
||||
initPlugins();
|
||||
|
||||
initialized = true;
|
||||
|
||||
context.getResults().initResult().setGood();
|
||||
} catch (...) {
|
||||
RPC_FILL(context.getResults(), initResult, std::current_exception());
|
||||
}
|
||||
|
||||
return kj::READY_NOW;
|
||||
}
|
||||
|
||||
kj::Promise<void> Instance::build(BuildContext context)
|
||||
{
|
||||
try {
|
||||
if (!initialized) {
|
||||
throw Error("build hook not fully initialized");
|
||||
}
|
||||
|
||||
// FIXME this does not open a daemon connection for historical reasons.
|
||||
// we may create a lot of build hook instances, and having each of them
|
||||
// also create a daemon instance is inefficient and wasteful. in future
|
||||
|
||||
@@ -1118,7 +1118,7 @@ try {
|
||||
|
||||
// close the rpc connection to have the hook exit
|
||||
hook->rpc = nullptr;
|
||||
hook->client = std::nullopt;
|
||||
hook->client = nullptr;
|
||||
hook->conn = nullptr;
|
||||
|
||||
if (auto error = TRY_AWAIT(output)) {
|
||||
|
||||
@@ -31,7 +31,10 @@ interface HookInstance {
|
||||
}
|
||||
}
|
||||
|
||||
build @0 (
|
||||
init @0 (
|
||||
settings :Types.Settings
|
||||
) -> (result :Types.ResultV);
|
||||
build @1 (
|
||||
amWilling :Bool,
|
||||
neededSystem :Data,
|
||||
drvPath :StoreTypes.StorePath,
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
#include "lix/libutil/file-system.hh"
|
||||
#include "lix/libstore/globals.hh"
|
||||
#include "lix/libstore/build/hook-instance.hh"
|
||||
#include "lix/libutil/rpc.hh"
|
||||
#include "lix/libutil/strings.hh"
|
||||
#include "lix/libutil/types-rpc.hh" // IWYU pragma: keep
|
||||
#include <memory>
|
||||
|
||||
namespace nix {
|
||||
|
||||
@@ -33,9 +36,6 @@ try {
|
||||
fromHook_.create();
|
||||
|
||||
/* Create the communication pipes. */
|
||||
Pipe toHook_;
|
||||
toHook_.create();
|
||||
|
||||
auto [selfRPC, hookRPC] = SocketPair::stream();
|
||||
|
||||
printMsg(lvlChatty, "running build hook: %s", concatMapStringsSep(" ", args, shellEscape));
|
||||
@@ -50,9 +50,6 @@ try {
|
||||
if (chdir("/") == -1) throw SysError("changing into /");
|
||||
|
||||
/* Dup the communication pipes. */
|
||||
if (dup2(toHook_.readSide.get(), STDIN_FILENO) == -1) {
|
||||
throw SysError("dupping to-hook read side");
|
||||
}
|
||||
if (dup2(hookRPC.get(), STDOUT_FILENO) == -1) {
|
||||
throw SysError("dupping to-hook read side");
|
||||
}
|
||||
@@ -66,15 +63,23 @@ try {
|
||||
|
||||
std::map<std::string, Config::SettingInfo> settings;
|
||||
globalConfig.getSettings(settings, true);
|
||||
FdSink sink(toHook_.writeSide.get());
|
||||
for (auto & setting : settings) {
|
||||
sink << 1 << setting.first << setting.second.value;
|
||||
|
||||
auto conn = AIO().lowLevelProvider.wrapUnixSocketFd(kj::AutoCloseFd(selfRPC.release()));
|
||||
auto client = std::make_unique<capnp::TwoPartyClient>(*conn, 1);
|
||||
auto rpc = client->bootstrap().castAs<rpc::build_remote::HookInstance>();
|
||||
|
||||
{
|
||||
auto initReq = rpc.initRequest();
|
||||
RPC_FILL(initReq, initSettings, settings);
|
||||
TRY_AWAIT_RPC(initReq.send());
|
||||
}
|
||||
sink << 0;
|
||||
sink.flush();
|
||||
|
||||
co_return std::make_unique<HookInstance>(
|
||||
std::move(fromHook_.readSide), std::move(selfRPC), std::move(pid)
|
||||
std::move(fromHook_.readSide),
|
||||
std::move(conn),
|
||||
std::move(client),
|
||||
std::move(rpc),
|
||||
std::move(pid)
|
||||
);
|
||||
} catch (...) {
|
||||
co_return result::current_exception();
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "lix/libutil/serialise.hh"
|
||||
#include <capnp/rpc-twoparty.h>
|
||||
#include <kj/async-io.h>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace nix {
|
||||
@@ -19,7 +20,7 @@ struct HookInstance
|
||||
AutoCloseFD fromHook;
|
||||
|
||||
kj::Own<kj::AsyncCapabilityStream> conn;
|
||||
std::optional<capnp::TwoPartyClient> client;
|
||||
std::unique_ptr<capnp::TwoPartyClient> client;
|
||||
rpc::build_remote::HookInstance::Client rpc;
|
||||
|
||||
/**
|
||||
@@ -31,11 +32,17 @@ struct HookInstance
|
||||
|
||||
static kj::Promise<Result<std::unique_ptr<HookInstance>>> create();
|
||||
|
||||
HookInstance(AutoCloseFD fromHook, AutoCloseFD rpc, Pid pid)
|
||||
HookInstance(
|
||||
AutoCloseFD fromHook,
|
||||
kj::Own<kj::AsyncCapabilityStream> conn,
|
||||
std::unique_ptr<capnp::TwoPartyClient> client,
|
||||
rpc::build_remote::HookInstance::Client rpc,
|
||||
Pid pid
|
||||
)
|
||||
: fromHook(std::move(fromHook))
|
||||
, conn(AIO().lowLevelProvider.wrapUnixSocketFd(kj::AutoCloseFd(rpc.release())))
|
||||
, client(std::in_place, *this->conn, 1)
|
||||
, rpc(client->bootstrap().castAs<rpc::build_remote::HookInstance>())
|
||||
, conn(std::move(conn))
|
||||
, client(std::move(client))
|
||||
, rpc(std::move(rpc))
|
||||
, pid(std::move(pid))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
///@file RPC helper functions for `types.hh`
|
||||
|
||||
#include "error.hh"
|
||||
#include "lix/libutil/config.hh"
|
||||
#include "lix/libutil/result.hh"
|
||||
#include "lix/libutil/types.capnp.h"
|
||||
#include "rpc.hh"
|
||||
@@ -127,4 +128,42 @@ struct Fill<Result<T>, std::exception_ptr>
|
||||
detail::makeBadResult(rb, e);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct Fill<Settings::Setting, std::pair<const std::string, Config::SettingInfo>>
|
||||
{
|
||||
static void fill(
|
||||
Settings::Setting::Builder sb,
|
||||
const std::pair<const std::string, Config::SettingInfo> & s,
|
||||
auto &&... args
|
||||
)
|
||||
{
|
||||
LIX_RPC_FILL(sb, setName, s.first);
|
||||
LIX_RPC_FILL(sb, setValue, s.second.value);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct Fill<Settings, std::map<std::string, Config::SettingInfo>>
|
||||
{
|
||||
static void fill(
|
||||
Settings::Builder sb, const std::map<std::string, Config::SettingInfo> & s, auto &&... args
|
||||
)
|
||||
{
|
||||
LIX_RPC_FILL(sb, initMap, s);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct Convert<Settings, std::map<std::string, std::string>>
|
||||
{
|
||||
static std::map<std::string, std::string> convert(const Settings::Reader & t, auto &&...)
|
||||
{
|
||||
std::map<std::string, std::string> result;
|
||||
for (const auto & s : t.getMap()) {
|
||||
result[rpc::to<std::string>(s.getName())] = rpc::to<std::string>(s.getValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -34,3 +34,13 @@ struct ResultV {
|
||||
bad @1 :Error;
|
||||
}
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
struct Setting {
|
||||
name @0 :Data;
|
||||
value @1 :Data;
|
||||
}
|
||||
|
||||
# actually a map, but will treat it as a last-value-wins list of pairs for now.
|
||||
map @0 :List(Setting);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user