1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-11 04:56: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:
Eelco Dolstra 2025-09-07 11:44:15 +02:00 committed by Robert Hensing
parent 8c789db05b
commit b13143280c
10 changed files with 85 additions and 13 deletions

View file

@ -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:

View file

@ -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

View file

@ -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)

View file

@ -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()) {
try {
callFunction(*v.app().left, *v.app().right, v, pos); 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]]

View file

@ -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;

View file

@ -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();
} }
} }

View file

@ -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>";

View file

@ -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;

View file

@ -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();
} }

View file

@ -170,6 +170,11 @@ static void printValueAsXML(
case nThunk: case nThunk:
doc.writeEmptyElement("unevaluated"); doc.writeEmptyElement("unevaluated");
break;
case nFailed:
doc.writeEmptyElement("failed");
break;
} }
} }