mirror of
https://github.com/NixOS/nix.git
synced 2025-11-11 13:06:01 +01:00
Introduce a "failed" value type
In the multithreaded evaluator, it's possible for multiple threads to wait on the same thunk. If evaluation of the thunk results in an exception, the waiting threads shouldn't try to re-force the thunk. Instead, they should rethrow the same exception, without duplicating any work. Therefore, there is now a new value type `tFailed` that stores an std::exception_ptr. If evaluation of a thunk/app results in an exception, `forceValue()` overwrites the value with a `tFailed`. If `forceValue()` encounters a `tFailed`, it rethrows the exception. So you normally never need to check for failed values (since forcing them causes a rethrow).
This commit is contained in:
parent
8c789db05b
commit
b13143280c
10 changed files with 85 additions and 13 deletions
|
|
@ -177,6 +177,8 @@ ValueType nix_get_type(nix_c_context * context, const nix_value * value)
|
||||||
switch (v.type()) {
|
switch (v.type()) {
|
||||||
case nThunk:
|
case nThunk:
|
||||||
return NIX_TYPE_THUNK;
|
return NIX_TYPE_THUNK;
|
||||||
|
case nFailed:
|
||||||
|
return NIX_TYPE_FAILED;
|
||||||
case nInt:
|
case nInt:
|
||||||
return NIX_TYPE_INT;
|
return NIX_TYPE_INT;
|
||||||
case nFloat:
|
case nFloat:
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,8 @@ typedef enum {
|
||||||
NIX_TYPE_ATTRS,
|
NIX_TYPE_ATTRS,
|
||||||
NIX_TYPE_LIST,
|
NIX_TYPE_LIST,
|
||||||
NIX_TYPE_FUNCTION,
|
NIX_TYPE_FUNCTION,
|
||||||
NIX_TYPE_EXTERNAL
|
NIX_TYPE_EXTERNAL,
|
||||||
|
NIX_TYPE_FAILED,
|
||||||
} ValueType;
|
} ValueType;
|
||||||
|
|
||||||
// forward declarations
|
// forward declarations
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,8 @@ std::string_view showType(ValueType type, bool withArticle)
|
||||||
return WA("a", "float");
|
return WA("a", "float");
|
||||||
case nThunk:
|
case nThunk:
|
||||||
return WA("a", "thunk");
|
return WA("a", "thunk");
|
||||||
|
case nFailed:
|
||||||
|
return WA("a", "failure");
|
||||||
}
|
}
|
||||||
unreachable();
|
unreachable();
|
||||||
}
|
}
|
||||||
|
|
@ -2693,8 +2695,11 @@ void EvalState::assertEqValues(Value & v1, Value & v2, const PosIdx pos, std::st
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case nThunk: // Must not be left by forceValue
|
// Cannot be returned by forceValue().
|
||||||
assert(false);
|
case nThunk:
|
||||||
|
case nFailed:
|
||||||
|
unreachable();
|
||||||
|
|
||||||
default: // Note that we pass compiler flags that should make `default:` unreachable.
|
default: // Note that we pass compiler flags that should make `default:` unreachable.
|
||||||
// Also note that this probably ran after `eqValues`, which implements
|
// Also note that this probably ran after `eqValues`, which implements
|
||||||
// the same logic more efficiently (without having to unwind stacks),
|
// the same logic more efficiently (without having to unwind stacks),
|
||||||
|
|
@ -2786,8 +2791,11 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
|
||||||
// !!!
|
// !!!
|
||||||
return v1.fpoint() == v2.fpoint();
|
return v1.fpoint() == v2.fpoint();
|
||||||
|
|
||||||
case nThunk: // Must not be left by forceValue
|
// Cannot be returned by forceValue().
|
||||||
assert(false);
|
case nThunk:
|
||||||
|
case nFailed:
|
||||||
|
unreachable();
|
||||||
|
|
||||||
default: // Note that we pass compiler flags that should make `default:` unreachable.
|
default: // Note that we pass compiler flags that should make `default:` unreachable.
|
||||||
error<EvalError>("eqValues: cannot compare %1% with %2%", showType(v1), showType(v2))
|
error<EvalError>("eqValues: cannot compare %1% with %2%", showType(v1), showType(v2))
|
||||||
.withTrace(pos, errorCtx)
|
.withTrace(pos, errorCtx)
|
||||||
|
|
|
||||||
|
|
@ -97,12 +97,19 @@ void EvalState::forceValue(Value & v, const PosIdx pos)
|
||||||
else
|
else
|
||||||
ExprBlackHole::throwInfiniteRecursionError(*this, v);
|
ExprBlackHole::throwInfiniteRecursionError(*this, v);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
v.mkThunk(env, expr);
|
|
||||||
tryFixupBlackHolePos(v, pos);
|
tryFixupBlackHolePos(v, pos);
|
||||||
|
v.mkFailed();
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
} else if (v.isApp())
|
} else if (v.isApp()) {
|
||||||
callFunction(*v.app().left, *v.app().right, v, pos);
|
try {
|
||||||
|
callFunction(*v.app().left, *v.app().right, v, pos);
|
||||||
|
} catch (...) {
|
||||||
|
v.mkFailed();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
} else if (v.isFailed())
|
||||||
|
std::rethrow_exception(v.failed()->ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[gnu::always_inline]]
|
[[gnu::always_inline]]
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ typedef enum {
|
||||||
tBool,
|
tBool,
|
||||||
tNull,
|
tNull,
|
||||||
tFloat,
|
tFloat,
|
||||||
|
tFailed,
|
||||||
tExternal,
|
tExternal,
|
||||||
tPrimOp,
|
tPrimOp,
|
||||||
tAttrs,
|
tAttrs,
|
||||||
|
|
@ -57,6 +58,7 @@ typedef enum {
|
||||||
*/
|
*/
|
||||||
typedef enum {
|
typedef enum {
|
||||||
nThunk,
|
nThunk,
|
||||||
|
nFailed,
|
||||||
nInt,
|
nInt,
|
||||||
nFloat,
|
nFloat,
|
||||||
nBool,
|
nBool,
|
||||||
|
|
@ -265,6 +267,11 @@ struct ValueBase
|
||||||
size_t size;
|
size_t size;
|
||||||
Value * const * elems;
|
Value * const * elems;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Failed : gc
|
||||||
|
{
|
||||||
|
std::exception_ptr ex;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
|
|
@ -291,6 +298,7 @@ struct PayloadTypeToInternalType
|
||||||
MACRO(PrimOp *, primOp, tPrimOp) \
|
MACRO(PrimOp *, primOp, tPrimOp) \
|
||||||
MACRO(ValueBase::PrimOpApplicationThunk, primOpApp, tPrimOpApp) \
|
MACRO(ValueBase::PrimOpApplicationThunk, primOpApp, tPrimOpApp) \
|
||||||
MACRO(ExternalValueBase *, external, tExternal) \
|
MACRO(ExternalValueBase *, external, tExternal) \
|
||||||
|
MACRO(ValueBase::Failed *, failed, tFailed) \
|
||||||
MACRO(NixFloat, fpoint, tFloat)
|
MACRO(NixFloat, fpoint, tFloat)
|
||||||
|
|
||||||
#define NIX_VALUE_PAYLOAD_TYPE(T, FIELD_NAME, DISCRIMINATOR) \
|
#define NIX_VALUE_PAYLOAD_TYPE(T, FIELD_NAME, DISCRIMINATOR) \
|
||||||
|
|
@ -595,6 +603,11 @@ protected:
|
||||||
path.path = std::bit_cast<const char *>(payload[1]);
|
path.path = std::bit_cast<const char *>(payload[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void getStorage(Failed *& failed) const noexcept
|
||||||
|
{
|
||||||
|
failed = std::bit_cast<Failed *>(payload[1]);
|
||||||
|
}
|
||||||
|
|
||||||
void setStorage(NixInt integer) noexcept
|
void setStorage(NixInt integer) noexcept
|
||||||
{
|
{
|
||||||
setSingleDWordPayload<tInt>(integer.value);
|
setSingleDWordPayload<tInt>(integer.value);
|
||||||
|
|
@ -644,6 +657,11 @@ protected:
|
||||||
{
|
{
|
||||||
setUntaggablePayload<pdPath>(path.accessor, path.path);
|
setUntaggablePayload<pdPath>(path.accessor, path.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setStorage(Failed * failed) noexcept
|
||||||
|
{
|
||||||
|
setSingleDWordPayload<tFailed>(std::bit_cast<PackedPointer>(failed));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -866,12 +884,12 @@ public:
|
||||||
inline bool isThunk() const
|
inline bool isThunk() const
|
||||||
{
|
{
|
||||||
return isa<tThunk>();
|
return isa<tThunk>();
|
||||||
};
|
}
|
||||||
|
|
||||||
inline bool isApp() const
|
inline bool isApp() const
|
||||||
{
|
{
|
||||||
return isa<tApp>();
|
return isa<tApp>();
|
||||||
};
|
}
|
||||||
|
|
||||||
inline bool isBlackhole() const;
|
inline bool isBlackhole() const;
|
||||||
|
|
||||||
|
|
@ -879,17 +897,22 @@ public:
|
||||||
inline bool isLambda() const
|
inline bool isLambda() const
|
||||||
{
|
{
|
||||||
return isa<tLambda>();
|
return isa<tLambda>();
|
||||||
};
|
}
|
||||||
|
|
||||||
inline bool isPrimOp() const
|
inline bool isPrimOp() const
|
||||||
{
|
{
|
||||||
return isa<tPrimOp>();
|
return isa<tPrimOp>();
|
||||||
};
|
}
|
||||||
|
|
||||||
inline bool isPrimOpApp() const
|
inline bool isPrimOpApp() const
|
||||||
{
|
{
|
||||||
return isa<tPrimOpApp>();
|
return isa<tPrimOpApp>();
|
||||||
};
|
}
|
||||||
|
|
||||||
|
inline bool isFailed() const
|
||||||
|
{
|
||||||
|
return isa<tFailed>();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the normal type of a Value. This only returns nThunk if
|
* Returns the normal type of a Value. This only returns nThunk if
|
||||||
|
|
@ -926,6 +949,8 @@ public:
|
||||||
return nExternal;
|
return nExternal;
|
||||||
case tFloat:
|
case tFloat:
|
||||||
return nFloat;
|
return nFloat;
|
||||||
|
case tFailed:
|
||||||
|
return nFailed;
|
||||||
case tThunk:
|
case tThunk:
|
||||||
case tApp:
|
case tApp:
|
||||||
return nThunk;
|
return nThunk;
|
||||||
|
|
@ -1048,6 +1073,11 @@ public:
|
||||||
setStorage(n);
|
setStorage(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void mkFailed() noexcept
|
||||||
|
{
|
||||||
|
setStorage(new Value::Failed{.ex = std::current_exception()});
|
||||||
|
}
|
||||||
|
|
||||||
bool isList() const noexcept
|
bool isList() const noexcept
|
||||||
{
|
{
|
||||||
return isa<tListSmall, tListN>();
|
return isa<tListSmall, tListN>();
|
||||||
|
|
@ -1151,6 +1181,11 @@ public:
|
||||||
{
|
{
|
||||||
return getStorage<Path>().accessor;
|
return getStorage<Path>().accessor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Failed * failed() const noexcept
|
||||||
|
{
|
||||||
|
return getStorage<Failed *>();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
extern ExprBlackHole eBlackHole;
|
extern ExprBlackHole eBlackHole;
|
||||||
|
|
|
||||||
|
|
@ -515,6 +515,7 @@ static void prim_typeOf(EvalState & state, const PosIdx pos, Value ** args, Valu
|
||||||
v.mkStringNoCopy("float");
|
v.mkStringNoCopy("float");
|
||||||
break;
|
break;
|
||||||
case nThunk:
|
case nThunk:
|
||||||
|
case nFailed:
|
||||||
unreachable();
|
unreachable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,9 @@ void printAmbiguous(
|
||||||
str << "«potential infinite recursion»";
|
str << "«potential infinite recursion»";
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case nFailed:
|
||||||
|
str << "«failed»";
|
||||||
|
break;
|
||||||
case nFunction:
|
case nFunction:
|
||||||
if (v.isLambda()) {
|
if (v.isLambda()) {
|
||||||
str << "<LAMBDA>";
|
str << "<LAMBDA>";
|
||||||
|
|
|
||||||
|
|
@ -508,6 +508,11 @@ private:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void printFailed(Value & v)
|
||||||
|
{
|
||||||
|
output << "«failed»";
|
||||||
|
}
|
||||||
|
|
||||||
void printExternal(Value & v)
|
void printExternal(Value & v)
|
||||||
{
|
{
|
||||||
v.external()->print(output);
|
v.external()->print(output);
|
||||||
|
|
@ -583,6 +588,10 @@ private:
|
||||||
printThunk(v);
|
printThunk(v);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case nFailed:
|
||||||
|
printFailed(v);
|
||||||
|
break;
|
||||||
|
|
||||||
case nExternal:
|
case nExternal:
|
||||||
printExternal(v);
|
printExternal(v);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,7 @@ json printValueAsJSON(
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case nThunk:
|
case nThunk:
|
||||||
|
case nFailed:
|
||||||
case nFunction:
|
case nFunction:
|
||||||
state.error<TypeError>("cannot convert %1% to JSON", showType(v)).atPos(v.determinePos(pos)).debugThrow();
|
state.error<TypeError>("cannot convert %1% to JSON", showType(v)).atPos(v.determinePos(pos)).debugThrow();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -170,6 +170,11 @@ static void printValueAsXML(
|
||||||
|
|
||||||
case nThunk:
|
case nThunk:
|
||||||
doc.writeEmptyElement("unevaluated");
|
doc.writeEmptyElement("unevaluated");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nFailed:
|
||||||
|
doc.writeEmptyElement("failed");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue