Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Store hierarchy with a new IndirectRootStore interface #8243

Merged
merged 3 commits into from
Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/libstore/build/local-derivation-goal.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#include "local-derivation-goal.hh"
#include "gc-store.hh"
#include "indirect-root-store.hh"
#include "hook-instance.hh"
#include "worker.hh"
#include "builtins.hh"
Expand Down Expand Up @@ -1200,7 +1200,7 @@ struct RestrictedStoreConfig : virtual LocalFSStoreConfig
/* A wrapper around LocalStore that only allows building/querying of
paths that are in the input closures of the build or were added via
recursive Nix calls. */
struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual LocalFSStore, public virtual GcStore
struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual IndirectRootStore, public virtual GcStore
{
ref<LocalStore> next;

Expand Down
5 changes: 3 additions & 2 deletions src/libstore/daemon.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "store-cast.hh"
#include "gc-store.hh"
#include "log-store.hh"
#include "indirect-root-store.hh"
#include "path-with-outputs.hh"
#include "finally.hh"
#include "archive.hh"
Expand Down Expand Up @@ -675,8 +676,8 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
Path path = absPath(readString(from));

logger->startWork();
auto & gcStore = require<GcStore>(*store);
gcStore.addIndirectRoot(path);
auto & indirectRootStore = require<IndirectRootStore>(*store);
indirectRootStore.addIndirectRoot(path);
logger->stopWork();

to << 1;
Expand Down
35 changes: 26 additions & 9 deletions src/libstore/gc-store.hh
Original file line number Diff line number Diff line change
Expand Up @@ -71,19 +71,36 @@ struct GCResults
};


/**
* Mix-in class for \ref Store "stores" which expose a notion of garbage
* collection.
*
* Garbage collection will allow deleting paths which are not
* transitively "rooted".
*
* The notion of GC roots actually not part of this class.
*
* - The base `Store` class has `Store::addTempRoot()` because for a store
* that doesn't support garbage collection at all, a temporary GC root is
* safely implementable as no-op.
*
* @todo actually this is not so good because stores are *views*.
* Some views have only a no-op temp roots even though others to the
* same store allow triggering GC. For instance one can't add a root
* over ssh, but that doesn't prevent someone from gc-ing that store
* accesed via SSH locally).
*
* - The derived `LocalFSStore` class has `LocalFSStore::addPermRoot`,
* which is not part of this class because it relies on the notion of
* an ambient file system. There are stores (`ssh-ng://`, for one),
* that *do* support garbage collection but *don't* expose any file
* system, and `LocalFSStore::addPermRoot` thus does not make sense
* for them.
*/
struct GcStore : public virtual Store
{
inline static std::string operationName = "Garbage collection";

/**
* Add an indirect root, which is merely a symlink to `path` from
* `/nix/var/nix/gcroots/auto/<hash of path>`. `path` is supposed
* to be a symlink to a store path. The garbage collector will
* automatically remove the indirect root when it finds that
* `path` has disappeared.
*/
virtual void addIndirectRoot(const Path & path) = 0;

/**
* Find the roots of the garbage collector. Each root is a pair
* `(link, storepath)` where `link` is the path of the symlink
Expand Down
3 changes: 1 addition & 2 deletions src/libstore/gc.cc
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#include "derivations.hh"
#include "globals.hh"
#include "local-store.hh"
#include "local-fs-store.hh"
#include "finally.hh"

#include <functional>
Expand Down Expand Up @@ -50,7 +49,7 @@ void LocalStore::addIndirectRoot(const Path & path)
}


Path LocalFSStore::addPermRoot(const StorePath & storePath, const Path & _gcRoot)
Path IndirectRootStore::addPermRoot(const StorePath & storePath, const Path & _gcRoot)
{
Path gcRoot(canonPath(_gcRoot));

Expand Down
48 changes: 48 additions & 0 deletions src/libstore/indirect-root-store.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#pragma once
///@file

#include "local-fs-store.hh"

namespace nix {

/**
* Mix-in class for implementing permanent roots as a pair of a direct
* (strong) reference and indirect weak reference to the first
* reference.
*
* See methods for details on the operations it represents.
*/
struct IndirectRootStore : public virtual LocalFSStore
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that name is not great. The most important bit here isn't that the store can register indirect roots (any ssh-ng could in theory), but that it can register permanent roots (although addPermRoot is implemented and final here, it only makes sense for stores that have a local filesystem baking)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The important bit is in fact the indirect roots.

it only makes sense for stores that have a local filesystem baking

It is a subclass of LocalFSStore, so unless you meant something like "non ssh/NFS" by "local" that is in fact always the case.

LocalFSStore defines addPermRoot. So the point of this class isn't to define that interfaec, but to merely implement it. The point is the indirect root implementation.

{
inline static std::string operationName = "Indirect GC roots registration";

/**
* Implementation of `LocalFSStore::addPermRoot` where the permanent
* root is a pair of
*
* - The user-facing symlink which all implementations must create
*
* - An additional weak reference known as the "indirect root" that
* points to that symlink.
*
* The garbage collector will automatically remove the indirect root
* when it finds that the symlink has disappeared.
*
* The implementation of this method is concrete, but it delegates
* to `addIndirectRoot()` which is abstract.
*/
Path addPermRoot(const StorePath & storePath, const Path & gcRoot) override final;

/**
* Add an indirect root, which is a weak reference to the
* user-facing symlink created by `addPermRoot()`.
*
* @param path user-facing and user-controlled symlink to a store
* path.
*
* The form this weak-reference takes is implementation-specific.
*/
virtual void addIndirectRoot(const Path & path) = 0;
};

}
16 changes: 14 additions & 2 deletions src/libstore/local-fs-store.hh
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class LocalFSStore : public virtual LocalFSStoreConfig,
public virtual LogStore
{
public:
inline static std::string operationName = "Local Filesystem Store";

const static std::string drvsLogDir;

Expand All @@ -49,9 +50,20 @@ public:
ref<FSAccessor> getFSAccessor() override;

/**
* Register a permanent GC root.
* Creates symlink from the `gcRoot` to the `storePath` and
* registers the `gcRoot` as a permanent GC root. The `gcRoot`
* symlink lives outside the store and is created and owned by the
* user.
*
* @param gcRoot The location of the symlink.
*
* @param storePath The store object being rooted. The symlink will
* point to `toRealPath(store.printStorePath(storePath))`.
*
* How the permanent GC root corresponding to this symlink is
* managed is implementation-specific.
*/
Path addPermRoot(const StorePath & storePath, const Path & gcRoot);
virtual Path addPermRoot(const StorePath & storePath, const Path & gcRoot) = 0;

virtual Path getRealStoreDir() { return realStoreDir; }

Expand Down
13 changes: 10 additions & 3 deletions src/libstore/local-store.hh
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@

#include "pathlocks.hh"
#include "store-api.hh"
#include "local-fs-store.hh"
#include "gc-store.hh"
#include "indirect-root-store.hh"
#include "sync.hh"
#include "util.hh"

Expand Down Expand Up @@ -68,7 +67,9 @@ struct LocalStoreConfig : virtual LocalFSStoreConfig
std::string doc() override;
};

class LocalStore : public virtual LocalStoreConfig, public virtual LocalFSStore, public virtual GcStore
class LocalStore : public virtual LocalStoreConfig
, public virtual IndirectRootStore
, public virtual GcStore
{
private:

Expand Down Expand Up @@ -209,6 +210,12 @@ private:

public:

/**
* Implementation of IndirectRootStore::addIndirectRoot().
*
* The weak reference merely is a symlink to `path' from
* /nix/var/nix/gcroots/auto/<hash of `path'>.
*/
void addIndirectRoot(const Path & path) override;

private:
Expand Down
31 changes: 31 additions & 0 deletions src/libstore/remote-store-connection.hh
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "remote-store.hh"
#include "worker-protocol.hh"
#include "pool.hh"

namespace nix {

Expand Down Expand Up @@ -94,4 +95,34 @@ struct RemoteStore::Connection
std::exception_ptr processStderr(Sink * sink = 0, Source * source = 0, bool flush = true);
};

/**
* A wrapper around Pool<RemoteStore::Connection>::Handle that marks
* the connection as bad (causing it to be closed) if a non-daemon
* exception is thrown before the handle is closed. Such an exception
* causes a deviation from the expected protocol and therefore a
* desynchronization between the client and daemon.
*/
struct RemoteStore::ConnectionHandle
{
Pool<RemoteStore::Connection>::Handle handle;
bool daemonException = false;

ConnectionHandle(Pool<RemoteStore::Connection>::Handle && handle)
: handle(std::move(handle))
{ }

ConnectionHandle(ConnectionHandle && h)
: handle(std::move(h.handle))
{ }

~ConnectionHandle();

RemoteStore::Connection & operator * () { return *handle; }
RemoteStore::Connection * operator -> () { return &*handle; }

void processStderr(Sink * sink = 0, Source * source = 0, bool flush = true);

void withFramedSink(std::function<void(Sink & sink)> fun);
};

}
61 changes: 14 additions & 47 deletions src/libstore/remote-store.cc
Original file line number Diff line number Diff line change
Expand Up @@ -159,49 +159,25 @@ void RemoteStore::setOptions(Connection & conn)
}


/* A wrapper around Pool<RemoteStore::Connection>::Handle that marks
the connection as bad (causing it to be closed) if a non-daemon
exception is thrown before the handle is closed. Such an exception
causes a deviation from the expected protocol and therefore a
desynchronization between the client and daemon. */
struct ConnectionHandle
RemoteStore::ConnectionHandle::~ConnectionHandle()
{
Pool<RemoteStore::Connection>::Handle handle;
bool daemonException = false;

ConnectionHandle(Pool<RemoteStore::Connection>::Handle && handle)
: handle(std::move(handle))
{ }

ConnectionHandle(ConnectionHandle && h)
: handle(std::move(h.handle))
{ }

~ConnectionHandle()
{
if (!daemonException && std::uncaught_exceptions()) {
handle.markBad();
debug("closing daemon connection because of an exception");
}
if (!daemonException && std::uncaught_exceptions()) {
handle.markBad();
debug("closing daemon connection because of an exception");
}
}

RemoteStore::Connection * operator -> () { return &*handle; }
RemoteStore::Connection & operator * () { return *handle; }

void processStderr(Sink * sink = 0, Source * source = 0, bool flush = true)
{
auto ex = handle->processStderr(sink, source, flush);
if (ex) {
daemonException = true;
std::rethrow_exception(ex);
}
void RemoteStore::ConnectionHandle::processStderr(Sink * sink, Source * source, bool flush)
{
auto ex = handle->processStderr(sink, source, flush);
if (ex) {
daemonException = true;
std::rethrow_exception(ex);
}

void withFramedSink(std::function<void(Sink & sink)> fun);
};
}


ConnectionHandle RemoteStore::getConnection()
RemoteStore::ConnectionHandle RemoteStore::getConnection()
{
return ConnectionHandle(connections->get());
}
Expand Down Expand Up @@ -846,15 +822,6 @@ void RemoteStore::addTempRoot(const StorePath & path)
}


void RemoteStore::addIndirectRoot(const Path & path)
{
auto conn(getConnection());
conn->to << WorkerProto::Op::AddIndirectRoot << path;
conn.processStderr();
readInt(conn->from);
}


Roots RemoteStore::findRoots(bool censor)
{
auto conn(getConnection());
Expand Down Expand Up @@ -1099,7 +1066,7 @@ std::exception_ptr RemoteStore::Connection::processStderr(Sink * sink, Source *
return nullptr;
}

void ConnectionHandle::withFramedSink(std::function<void(Sink & sink)> fun)
void RemoteStore::ConnectionHandle::withFramedSink(std::function<void(Sink & sink)> fun)
{
(*this)->to.flush();

Expand Down
6 changes: 2 additions & 4 deletions src/libstore/remote-store.hh
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ class Pid;
struct FdSink;
struct FdSource;
template<typename T> class Pool;
struct ConnectionHandle;

struct RemoteStoreConfig : virtual StoreConfig
{
Expand Down Expand Up @@ -127,8 +126,6 @@ public:

void addTempRoot(const StorePath & path) override;

void addIndirectRoot(const Path & path) override;

Roots findRoots(bool censor) override;

void collectGarbage(const GCOptions & options, GCResults & results) override;
Expand Down Expand Up @@ -182,6 +179,8 @@ protected:

void setOptions() override;

struct ConnectionHandle;

ConnectionHandle getConnection();

friend struct ConnectionHandle;
Expand All @@ -199,5 +198,4 @@ private:
std::shared_ptr<Store> evalStore);
};


}
Loading