Skip to content

Commit 48d268b

Browse files
committed
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).
1 parent 5ae1b5f commit 48d268b

File tree

10 files changed

+85
-13
lines changed

10 files changed

+85
-13
lines changed

src/libexpr-c/nix_api_value.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ ValueType nix_get_type(nix_c_context * context, const nix_value * value)
177177
switch (v.type()) {
178178
case nThunk:
179179
return NIX_TYPE_THUNK;
180+
case nFailed:
181+
return NIX_TYPE_FAILED;
180182
case nInt:
181183
return NIX_TYPE_INT;
182184
case nFloat:

src/libexpr-c/nix_api_value.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ typedef enum {
3232
NIX_TYPE_ATTRS,
3333
NIX_TYPE_LIST,
3434
NIX_TYPE_FUNCTION,
35-
NIX_TYPE_EXTERNAL
35+
NIX_TYPE_EXTERNAL,
36+
NIX_TYPE_FAILED,
3637
} ValueType;
3738

3839
// forward declarations

src/libexpr/eval.cc

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ std::string_view showType(ValueType type, bool withArticle)
124124
return WA("a", "float");
125125
case nThunk:
126126
return WA("a", "thunk");
127+
case nFailed:
128+
return WA("a", "failure");
127129
}
128130
unreachable();
129131
}
@@ -2693,8 +2695,11 @@ void EvalState::assertEqValues(Value & v1, Value & v2, const PosIdx pos, std::st
26932695
}
26942696
return;
26952697

2696-
case nThunk: // Must not be left by forceValue
2697-
assert(false);
2698+
// Cannot be returned by forceValue().
2699+
case nThunk:
2700+
case nFailed:
2701+
unreachable();
2702+
26982703
default: // Note that we pass compiler flags that should make `default:` unreachable.
26992704
// Also note that this probably ran after `eqValues`, which implements
27002705
// 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
27862791
// !!!
27872792
return v1.fpoint() == v2.fpoint();
27882793

2789-
case nThunk: // Must not be left by forceValue
2790-
assert(false);
2794+
// Cannot be returned by forceValue().
2795+
case nThunk:
2796+
case nFailed:
2797+
unreachable();
2798+
27912799
default: // Note that we pass compiler flags that should make `default:` unreachable.
27922800
error<EvalError>("eqValues: cannot compare %1% with %2%", showType(v1), showType(v2))
27932801
.withTrace(pos, errorCtx)

src/libexpr/include/nix/expr/eval-inline.hh

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,19 @@ void EvalState::forceValue(Value & v, const PosIdx pos)
9797
else
9898
ExprBlackHole::throwInfiniteRecursionError(*this, v);
9999
} catch (...) {
100-
v.mkThunk(env, expr);
101100
tryFixupBlackHolePos(v, pos);
101+
v.mkFailed();
102102
throw;
103103
}
104-
} else if (v.isApp())
105-
callFunction(*v.app().left, *v.app().right, v, pos);
104+
} else if (v.isApp()) {
105+
try {
106+
callFunction(*v.app().left, *v.app().right, v, pos);
107+
} catch (...) {
108+
v.mkFailed();
109+
throw;
110+
}
111+
} else if (v.isFailed())
112+
std::rethrow_exception(v.failed()->ex);
106113
}
107114

108115
[[gnu::always_inline]]

src/libexpr/include/nix/expr/value.hh

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ typedef enum {
3535
tBool,
3636
tNull,
3737
tFloat,
38+
tFailed,
3839
tExternal,
3940
tPrimOp,
4041
tAttrs,
@@ -57,6 +58,7 @@ typedef enum {
5758
*/
5859
typedef enum {
5960
nThunk,
61+
nFailed,
6062
nInt,
6163
nFloat,
6264
nBool,
@@ -265,6 +267,11 @@ struct ValueBase
265267
size_t size;
266268
Value * const * elems;
267269
};
270+
271+
struct Failed : gc
272+
{
273+
std::exception_ptr ex;
274+
};
268275
};
269276

270277
template<typename T>
@@ -291,6 +298,7 @@ struct PayloadTypeToInternalType
291298
MACRO(PrimOp *, primOp, tPrimOp) \
292299
MACRO(ValueBase::PrimOpApplicationThunk, primOpApp, tPrimOpApp) \
293300
MACRO(ExternalValueBase *, external, tExternal) \
301+
MACRO(ValueBase::Failed *, failed, tFailed) \
294302
MACRO(NixFloat, fpoint, tFloat)
295303

296304
#define NIX_VALUE_PAYLOAD_TYPE(T, FIELD_NAME, DISCRIMINATOR) \
@@ -594,6 +602,11 @@ protected:
594602
path.path = std::bit_cast<const char *>(payload[1]);
595603
}
596604

605+
void getStorage(Failed *& failed) const noexcept
606+
{
607+
failed = std::bit_cast<Failed *>(payload[1]);
608+
}
609+
597610
void setStorage(NixInt integer) noexcept
598611
{
599612
setSingleDWordPayload<tInt>(integer.value);
@@ -643,6 +656,11 @@ protected:
643656
{
644657
setUntaggablePayload<pdPath>(path.accessor, path.path);
645658
}
659+
660+
void setStorage(Failed * failed) noexcept
661+
{
662+
setSingleDWordPayload<tFailed>(std::bit_cast<PackedPointer>(failed));
663+
}
646664
};
647665

648666
/**
@@ -865,30 +883,35 @@ public:
865883
inline bool isThunk() const
866884
{
867885
return isa<tThunk>();
868-
};
886+
}
869887

870888
inline bool isApp() const
871889
{
872890
return isa<tApp>();
873-
};
891+
}
874892

875893
inline bool isBlackhole() const;
876894

877895
// type() == nFunction
878896
inline bool isLambda() const
879897
{
880898
return isa<tLambda>();
881-
};
899+
}
882900

883901
inline bool isPrimOp() const
884902
{
885903
return isa<tPrimOp>();
886-
};
904+
}
887905

888906
inline bool isPrimOpApp() const
889907
{
890908
return isa<tPrimOpApp>();
891-
};
909+
}
910+
911+
inline bool isFailed() const
912+
{
913+
return isa<tFailed>();
914+
}
892915

893916
/**
894917
* Returns the normal type of a Value. This only returns nThunk if
@@ -925,6 +948,8 @@ public:
925948
return nExternal;
926949
case tFloat:
927950
return nFloat;
951+
case tFailed:
952+
return nFailed;
928953
case tThunk:
929954
case tApp:
930955
return nThunk;
@@ -1039,6 +1064,11 @@ public:
10391064
setStorage(n);
10401065
}
10411066

1067+
inline void mkFailed() noexcept
1068+
{
1069+
setStorage(new Value::Failed{.ex = std::current_exception()});
1070+
}
1071+
10421072
bool isList() const noexcept
10431073
{
10441074
return isa<tListSmall, tListN>();
@@ -1142,6 +1172,11 @@ public:
11421172
{
11431173
return getStorage<Path>().accessor;
11441174
}
1175+
1176+
Failed * failed() const noexcept
1177+
{
1178+
return getStorage<Failed *>();
1179+
}
11451180
};
11461181

11471182
extern ExprBlackHole eBlackHole;

src/libexpr/primops.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,7 @@ static void prim_typeOf(EvalState & state, const PosIdx pos, Value ** args, Valu
515515
v.mkStringNoCopy("float");
516516
break;
517517
case nThunk:
518+
case nFailed:
518519
unreachable();
519520
}
520521
}

src/libexpr/print-ambiguous.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ void printAmbiguous(
7575
str << "«potential infinite recursion»";
7676
}
7777
break;
78+
case nFailed:
79+
str << "«failed»";
80+
break;
7881
case nFunction:
7982
if (v.isLambda()) {
8083
str << "<LAMBDA>";

src/libexpr/print.cc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,11 @@ class Printer
508508
}
509509
}
510510

511+
void printFailed(Value & v)
512+
{
513+
output << "«failed»";
514+
}
515+
511516
void printExternal(Value & v)
512517
{
513518
v.external()->print(output);
@@ -583,6 +588,10 @@ class Printer
583588
printThunk(v);
584589
break;
585590

591+
case nFailed:
592+
printFailed(v);
593+
break;
594+
586595
case nExternal:
587596
printExternal(v);
588597
break;

src/libexpr/value-to-json.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ json printValueAsJSON(
9696
break;
9797

9898
case nThunk:
99+
case nFailed:
99100
case nFunction:
100101
state.error<TypeError>("cannot convert %1% to JSON", showType(v)).atPos(v.determinePos(pos)).debugThrow();
101102
}

src/libexpr/value-to-xml.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,11 @@ static void printValueAsXML(
170170

171171
case nThunk:
172172
doc.writeEmptyElement("unevaluated");
173+
break;
174+
175+
case nFailed:
176+
doc.writeEmptyElement("failed");
177+
break;
173178
}
174179
}
175180

0 commit comments

Comments
 (0)