Merge changes I1fa30114,I3ca208b6,Ide4c6e00,I74c46b9f,I05fa6a9d, ... into main
* changes: Revert "libutil: drop Pool resources on exceptional free" Revert "libutil: remove Pool::Handle::bad" Revert "libstore: remove one Resource::good flag" Revert "libstore: using throwing finally in withFramedSink" Revert "libutil: allow graceful dropping of Pool::Handle" Revert "libutil: drop Fs{Source,Sink}::good" libutil: guard Finally against invalid exception throws
This commit is contained in:
commit
e9e1b6963c
7 changed files with 104 additions and 60 deletions
|
@ -46,6 +46,7 @@ struct LegacySSHStore : public virtual LegacySSHStoreConfig, public virtual Stor
|
||||||
FdSink to;
|
FdSink to;
|
||||||
FdSource from;
|
FdSource from;
|
||||||
ServeProto::Version remoteVersion;
|
ServeProto::Version remoteVersion;
|
||||||
|
bool good = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Coercion to `ServeProto::ReadConn`. This makes it easy to use the
|
* Coercion to `ServeProto::ReadConn`. This makes it easy to use the
|
||||||
|
@ -96,7 +97,8 @@ struct LegacySSHStore : public virtual LegacySSHStoreConfig, public virtual Stor
|
||||||
, host(host)
|
, host(host)
|
||||||
, connections(make_ref<Pool<Connection>>(
|
, connections(make_ref<Pool<Connection>>(
|
||||||
std::max(1, (int) maxConnections),
|
std::max(1, (int) maxConnections),
|
||||||
[this]() { return openConnection(); }
|
[this]() { return openConnection(); },
|
||||||
|
[](const ref<Connection> & r) { return r->good; }
|
||||||
))
|
))
|
||||||
, master(
|
, master(
|
||||||
host,
|
host,
|
||||||
|
@ -194,7 +196,12 @@ struct LegacySSHStore : public virtual LegacySSHStoreConfig, public virtual Stor
|
||||||
<< info.ultimate
|
<< info.ultimate
|
||||||
<< info.sigs
|
<< info.sigs
|
||||||
<< renderContentAddress(info.ca);
|
<< renderContentAddress(info.ca);
|
||||||
|
try {
|
||||||
copyNAR(source, conn->to);
|
copyNAR(source, conn->to);
|
||||||
|
} catch (...) {
|
||||||
|
conn->good = false;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
conn->to.flush();
|
conn->to.flush();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -202,7 +209,12 @@ struct LegacySSHStore : public virtual LegacySSHStoreConfig, public virtual Stor
|
||||||
conn->to
|
conn->to
|
||||||
<< ServeProto::Command::ImportPaths
|
<< ServeProto::Command::ImportPaths
|
||||||
<< 1;
|
<< 1;
|
||||||
|
try {
|
||||||
copyNAR(source, conn->to);
|
copyNAR(source, conn->to);
|
||||||
|
} catch (...) {
|
||||||
|
conn->good = false;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
conn->to
|
conn->to
|
||||||
<< exportMagic
|
<< exportMagic
|
||||||
<< printStorePath(info.path);
|
<< printStorePath(info.path);
|
||||||
|
|
|
@ -39,7 +39,9 @@ RemoteStore::RemoteStore(const Params & params)
|
||||||
},
|
},
|
||||||
[this](const ref<Connection> & r) {
|
[this](const ref<Connection> & r) {
|
||||||
return
|
return
|
||||||
std::chrono::duration_cast<std::chrono::seconds>(
|
r->to.good()
|
||||||
|
&& r->from.good()
|
||||||
|
&& std::chrono::duration_cast<std::chrono::seconds>(
|
||||||
std::chrono::steady_clock::now() - r->startTime).count() < maxConnectionAge;
|
std::chrono::steady_clock::now() - r->startTime).count() < maxConnectionAge;
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
|
@ -153,6 +155,7 @@ void RemoteStore::setOptions(Connection & conn)
|
||||||
RemoteStore::ConnectionHandle::~ConnectionHandle()
|
RemoteStore::ConnectionHandle::~ConnectionHandle()
|
||||||
{
|
{
|
||||||
if (!daemonException && std::uncaught_exceptions()) {
|
if (!daemonException && std::uncaught_exceptions()) {
|
||||||
|
handle.markBad();
|
||||||
debug("closing daemon connection because of an exception");
|
debug("closing daemon connection because of an exception");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -178,10 +181,6 @@ void RemoteStore::ConnectionHandle::processStderr(Sink * sink, Source * source,
|
||||||
m.find("Derive([") != std::string::npos)
|
m.find("Derive([") != std::string::npos)
|
||||||
throw Error("%s, this might be because the daemon is too old to understand dependencies on dynamic derivations. Check to see if the raw derivation is in the form '%s'", std::move(m), "DrvWithVersion(..)");
|
throw Error("%s, this might be because the daemon is too old to understand dependencies on dynamic derivations. Check to see if the raw derivation is in the form '%s'", std::move(m), "DrvWithVersion(..)");
|
||||||
}
|
}
|
||||||
// the daemon can still handle more requests, so the connection itself
|
|
||||||
// is still valid. the current *handle* however should be considered a
|
|
||||||
// lost cause and abandoned entirely.
|
|
||||||
handle.release();
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1069,15 +1068,27 @@ void RemoteStore::ConnectionHandle::withFramedSink(std::function<void(Sink & sin
|
||||||
|
|
||||||
Finally joinStderrThread([&]()
|
Finally joinStderrThread([&]()
|
||||||
{
|
{
|
||||||
|
if (stderrThread.joinable()) {
|
||||||
stderrThread.join();
|
stderrThread.join();
|
||||||
if (ex) {
|
if (ex) {
|
||||||
|
try {
|
||||||
std::rethrow_exception(ex);
|
std::rethrow_exception(ex);
|
||||||
|
} catch (...) {
|
||||||
|
ignoreException();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
{
|
||||||
FramedSink sink((*this)->to, ex);
|
FramedSink sink((*this)->to, ex);
|
||||||
fun(sink);
|
fun(sink);
|
||||||
sink.flush();
|
sink.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stderrThread.join();
|
||||||
|
if (ex)
|
||||||
|
std::rethrow_exception(ex);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <exception>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A trivial class to run a function at the end of a scope.
|
* A trivial class to run a function at the end of a scope.
|
||||||
*/
|
*/
|
||||||
|
@ -19,5 +22,25 @@ public:
|
||||||
Finally(Finally &&other) : fun(std::move(other.fun)) {
|
Finally(Finally &&other) : fun(std::move(other.fun)) {
|
||||||
other.movedFrom = true;
|
other.movedFrom = true;
|
||||||
}
|
}
|
||||||
~Finally() noexcept(false) { if (!movedFrom) fun(); }
|
~Finally() noexcept(false)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (!movedFrom)
|
||||||
|
fun();
|
||||||
|
} catch (...) {
|
||||||
|
// finally may only throw an exception if exception handling is not already
|
||||||
|
// in progress. if handling *is* in progress we have to return cleanly here
|
||||||
|
// but are still prohibited from doing so since eating the exception would,
|
||||||
|
// in almost all cases, mess up error handling even more. the only good way
|
||||||
|
// to handle this is to abort entirely and leave a message, so we'll assert
|
||||||
|
// (and rethrow anyway, just as a defense against possible NASSERT builds.)
|
||||||
|
if (std::uncaught_exceptions()) {
|
||||||
|
assert(false &&
|
||||||
|
"Finally function threw an exception during exception handling. "
|
||||||
|
"this is not what you want, please use some other methods (like "
|
||||||
|
"std::promise or async) instead.");
|
||||||
|
}
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
#include <exception>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
@ -103,24 +102,12 @@ public:
|
||||||
private:
|
private:
|
||||||
Pool & pool;
|
Pool & pool;
|
||||||
std::shared_ptr<R> r;
|
std::shared_ptr<R> r;
|
||||||
|
bool bad = false;
|
||||||
|
|
||||||
friend Pool;
|
friend Pool;
|
||||||
|
|
||||||
Handle(Pool & pool, std::shared_ptr<R> r) : pool(pool), r(r) { }
|
Handle(Pool & pool, std::shared_ptr<R> r) : pool(pool), r(r) { }
|
||||||
|
|
||||||
void drop(bool stillValid)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
auto state_(pool.state.lock());
|
|
||||||
if (stillValid)
|
|
||||||
state_->idle.emplace_back(std::move(r));
|
|
||||||
assert(state_->inUse);
|
|
||||||
state_->inUse--;
|
|
||||||
}
|
|
||||||
pool.wakeup.notify_one();
|
|
||||||
r = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Handle(Handle && h) : pool(h.pool), r(h.r) { h.r.reset(); }
|
Handle(Handle && h) : pool(h.pool), r(h.r) { h.r.reset(); }
|
||||||
|
|
||||||
|
@ -128,27 +115,25 @@ public:
|
||||||
|
|
||||||
~Handle()
|
~Handle()
|
||||||
{
|
{
|
||||||
if (r)
|
if (!r) return;
|
||||||
drop(std::uncaught_exceptions() == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void release()
|
|
||||||
{
|
{
|
||||||
drop(true);
|
auto state_(pool.state.lock());
|
||||||
|
if (!bad)
|
||||||
|
state_->idle.push_back(ref<R>(r));
|
||||||
|
assert(state_->inUse);
|
||||||
|
state_->inUse--;
|
||||||
|
}
|
||||||
|
pool.wakeup.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
R * operator -> () { return &*r; }
|
R * operator -> () { return &*r; }
|
||||||
R & operator * () { return *r; }
|
R & operator * () { return *r; }
|
||||||
|
|
||||||
|
void markBad() { bad = true; }
|
||||||
};
|
};
|
||||||
|
|
||||||
Handle get()
|
Handle get()
|
||||||
{
|
{
|
||||||
// we do not want to handle the complexity that comes with allocating
|
|
||||||
// resources during stack unwinding. it would be possible to do this,
|
|
||||||
// but doing so requires more per-handle bookkeeping to properly free
|
|
||||||
// resources allocated during unwinding. that effort is not worth it.
|
|
||||||
assert(std::uncaught_exceptions() == 0);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
auto state_(state.lock());
|
auto state_(state.lock());
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,18 @@ FdSink::~FdSink()
|
||||||
void FdSink::writeUnbuffered(std::string_view data)
|
void FdSink::writeUnbuffered(std::string_view data)
|
||||||
{
|
{
|
||||||
written += data.size();
|
written += data.size();
|
||||||
|
try {
|
||||||
writeFull(fd, data);
|
writeFull(fd, data);
|
||||||
|
} catch (SysError & e) {
|
||||||
|
_good = false;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool FdSink::good()
|
||||||
|
{
|
||||||
|
return _good;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -117,13 +128,19 @@ size_t FdSource::readUnbuffered(char * data, size_t len)
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
n = ::read(fd, data, len);
|
n = ::read(fd, data, len);
|
||||||
} while (n == -1 && errno == EINTR);
|
} while (n == -1 && errno == EINTR);
|
||||||
if (n == -1) { throw SysError("reading from file"); }
|
if (n == -1) { _good = false; throw SysError("reading from file"); }
|
||||||
if (n == 0) { throw EndOfFile(std::string(*endOfFileError)); }
|
if (n == 0) { _good = false; throw EndOfFile(std::string(*endOfFileError)); }
|
||||||
read += n;
|
read += n;
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool FdSource::good()
|
||||||
|
{
|
||||||
|
return _good;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
size_t StringSource::read(char * data, size_t len)
|
size_t StringSource::read(char * data, size_t len)
|
||||||
{
|
{
|
||||||
if (pos == s.size()) throw EndOfFile("end of string reached");
|
if (pos == s.size()) throw EndOfFile("end of string reached");
|
||||||
|
|
|
@ -18,6 +18,7 @@ struct Sink
|
||||||
{
|
{
|
||||||
virtual ~Sink() { }
|
virtual ~Sink() { }
|
||||||
virtual void operator () (std::string_view data) = 0;
|
virtual void operator () (std::string_view data) = 0;
|
||||||
|
virtual bool good() { return true; }
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -79,6 +80,8 @@ struct Source
|
||||||
*/
|
*/
|
||||||
virtual size_t read(char * data, size_t len) = 0;
|
virtual size_t read(char * data, size_t len) = 0;
|
||||||
|
|
||||||
|
virtual bool good() { return true; }
|
||||||
|
|
||||||
void drainInto(Sink & sink);
|
void drainInto(Sink & sink);
|
||||||
|
|
||||||
std::string drain();
|
std::string drain();
|
||||||
|
@ -133,6 +136,11 @@ struct FdSink : BufferedSink
|
||||||
~FdSink();
|
~FdSink();
|
||||||
|
|
||||||
void writeUnbuffered(std::string_view data) override;
|
void writeUnbuffered(std::string_view data) override;
|
||||||
|
|
||||||
|
bool good() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool _good = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -157,8 +165,11 @@ struct FdSource : BufferedSource
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool good() override;
|
||||||
protected:
|
protected:
|
||||||
size_t readUnbuffered(char * data, size_t len) override;
|
size_t readUnbuffered(char * data, size_t len) override;
|
||||||
|
private:
|
||||||
|
bool _good = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -109,19 +109,4 @@ namespace nix {
|
||||||
ASSERT_NE(h->num, counter);
|
ASSERT_NE(h->num, counter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Pool, throwingOperationDropsResource)
|
|
||||||
{
|
|
||||||
auto createResource = []() { return make_ref<TestResource>(); };
|
|
||||||
|
|
||||||
Pool<TestResource> pool = Pool<TestResource>((size_t)1, createResource);
|
|
||||||
|
|
||||||
ASSERT_THROW({
|
|
||||||
auto _r = pool.get();
|
|
||||||
ASSERT_EQ(pool.count(), 1);
|
|
||||||
throw 1;
|
|
||||||
}, int);
|
|
||||||
|
|
||||||
ASSERT_EQ(pool.count(), 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue