Skip to content

Commit d0182fc

Browse files
committed
internal/core/adt: keep lookups of settled arcs from forcing unrelated tasks
Vertex.lookup drives the target scheduler whenever not all of its tasks have started, demanding allTasksCompleted. That also runs pushed-down comprehensions that cannot affect the arc being looked up: a comprehension whose body consists of literal fields only contributes conjuncts to the arcs it pre-created at scheduling time, none of which is the requested one. Running such a comprehension is not just unnecessary work. Its guard may resolve a disjunction, which evaluates its disjuncts in finalize mode; that mode force-freezes blocked tasks, permanently recording incomplete errors on vertices whose dependencies are still being computed further up the evaluation stack. In the reproducer, a reference to a settled member arc _port forced the comprehension guard on its parent struct. Resolving the sibling spec disjunction finalized a "\(port)" interpolation elsewhere in the tree while the port vertex was mid-lookup on the stack, freezing it as an "invalid interpolation: non-concrete value" error even though port resolved to a concrete value immediately afterwards. When the requested arc is already a member and provably cannot gain conjuncts from a pushed-down comprehension, drop allTasksCompleted from the needs mask when driving the scheduler: the task selection in process then leaves such comprehensions unforced while other pending tasks still run. Fixes #4448 Signed-off-by: Daniel Martí <mvdan@mvdan.cc> Change-Id: I7513822cb7a536efe84ef975db5d79a25fca8093 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1242735 TryBot-Result: CUEcueckoo <cueckoo@cuelang.org> Unity-Result: CUE porcuepine <cue.porcuepine@gmail.com> Reviewed-by: Marcel van Lohuizen <mpvl@gmail.com>
1 parent 910bb32 commit d0182fc

5 files changed

Lines changed: 61 additions & 21 deletions

File tree

cue/testdata/comprehensions/issue4448.txtar

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,7 @@ app: {
4545
_port: 8080 @test(eq, 8080)
4646
spec: {
4747
src: "x" @test(eq, "x")
48-
tags: [#cfg._all] @test(err, at=0.0, code=incomplete,
49-
contains="invalid interpolation", contains="non-concrete value", pos=[-19:13]) @test(eq:todo, [["8080"]])
48+
tags: [#cfg._all] @test(eq, [["8080"]])
5049
}
5150
}
5251
}
@@ -55,15 +54,11 @@ app: {
5554
./in.cue:19:13
5655
[incomplete] #Mixin.#cfg.entries.w._lines.0: invalid interpolation: non-concrete value _ (type _):
5756
./in.cue:19:13
58-
[incomplete] app.#cfg.entries.w._lines.0: invalid interpolation: non-concrete value _ (type _):
59-
./in.cue:19:13
60-
[incomplete] app.#cfg.entries.w._lines.0: invalid interpolation: non-concrete value _ (type _):
61-
./in.cue:19:13
6257
-- out/eval/stats --
6358
Leaks: 4
6459
Freed: 45
65-
Reused: 23
66-
Allocs: 26
60+
Reused: 28
61+
Allocs: 21
6762
Retain: 0
6863

6964
Unifications: 41

internal/core/adt/comprehension.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,3 +334,12 @@ func pushDownDeps(n *nodeContext, t *task, x Node) condition {
334334

335335
return completes | allTasksCompleted
336336
}
337+
338+
// isPushedDownComp reports whether t is a comprehension task whose body
339+
// was fully pushed down: [pushDownDeps] returns bare allTasksCompleted
340+
// exactly when the body consists of literal fields only. Such a task
341+
// contributes conjuncts solely to the arcs it pre-created at scheduling
342+
// time, registering itself in their parentTasks.
343+
func (t *task) isPushedDownComp() bool {
344+
return t.run == handleComprehension && t.completes == allTasksCompleted
345+
}

internal/core/adt/disjunct2.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,7 @@ func hasDeferredCompAncestor(v *Vertex) bool {
249249
return false
250250
}
251251
for _, t := range p.state.tasks {
252-
if t.run == handleComprehension &&
253-
t.completes == allTasksCompleted {
252+
if t.isPushedDownComp() {
254253
return true
255254
}
256255
}

internal/core/adt/sched.go

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -534,16 +534,13 @@ processNextTask:
534534
// A comprehension still evaluating its clauses does not defer
535535
// resolvers: its guards may depend on the resolver's conjuncts.
536536
continue
537-
case hasPendingDisjunction && t.run == handleComprehension &&
538-
t.completes == allTasksCompleted:
537+
case hasPendingDisjunction && t.isPushedDownComp():
539538
// Defer pushed-down comprehensions until pending
540-
// disjunctions have expanded the parent into disjuncts.
541-
// Only comps whose body was fully pushed down (after
542-
// pushDownDeps, completes == allTasksCompleted) are
543-
// delayed: such comps need to run inside each disjunct
544-
// so the pushed-down arcs see that disjunct's view.
545-
// Comps with broader completes (e.g. for-comps over a
546-
// list) are not delayed.
539+
// disjunctions have expanded the parent into disjuncts:
540+
// such comps need to run inside each disjunct so the
541+
// pushed-down arcs see that disjunct's view. Comps with
542+
// broader completes (e.g. for-comps over a list) are not
543+
// delayed.
547544
continue
548545
default:
549546
runTask(t, mode)

internal/core/adt/unify.go

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,38 @@ func root(v *Vertex) *Vertex {
812812
return v
813813
}
814814

815+
// arcSettledForLookup reports whether the arc for f is already a member
816+
// whose conjunct set cannot be affected by pushed-down comprehension
817+
// tasks, so that a lookup-driven process call may leave those tasks
818+
// unforced by dropping allTasksCompleted from its needs mask.
819+
//
820+
// This preserves the fine-grained dependency tracking of comprehension
821+
// pushdown: a pushed-down comprehension task (see [task.isPushedDownComp])
822+
// contributes conjuncts solely to the arcs it pre-created at scheduling
823+
// time, so one that may still contribute to f is found in the parentTasks
824+
// of this node or of the arc itself.
825+
//
826+
// TODO(cycle): this guard removes one trigger of a deeper problem. When a
827+
// forced task resolves a disjunction, doDisjunct leaks finalize mode into
828+
// vertices outside its overlay, and the freeze cycle-breaker then commits
829+
// permanent incomplete errors for tasks blocked on a vertex that is merely
830+
// in-flight on the evaluation stack, not semantically cyclic. A more
831+
// principled fix would distinguish stack re-entrancy from value cycles in
832+
// the freeze path, or keep finalize scoped to the disjunct overlay.
833+
func (n *nodeContext) arcSettledForLookup(f Feature) bool {
834+
// An ancestor comprehension may still add conjuncts to this node.
835+
if n.hasActiveParentTask() {
836+
return false
837+
}
838+
arc := n.node.LookupRaw(f)
839+
if arc == nil || arc.ArcType != ArcMember {
840+
return false
841+
}
842+
// Pushed-down comprehensions that may contribute to f, whether from
843+
// this node or an ancestor, are registered on the arc itself.
844+
return arc.state == nil || !arc.state.hasActiveParentTask()
845+
}
846+
815847
func (v *Vertex) lookup(c *OpContext, pos token.Pos, f Feature, flags Flags) *Vertex {
816848
needs := flags.condition
817849
runMode := flags.mode
@@ -851,9 +883,17 @@ func (v *Vertex) lookup(c *OpContext, pos token.Pos, f Feature, flags Flags) *Ve
851883

852884
// Drive the lookup target forward when its scheduler has not yet
853885
// started everything; the !allTasksStarted guard keeps us out of
854-
// nodes that are already mid-execution.
886+
// nodes that are already mid-execution. When the requested arc is
887+
// already settled (see [nodeContext.arcSettledForLookup]), drop
888+
// allTasksCompleted from the needs mask: the selectTasks filter in
889+
// process then skips pushed-down comprehensions, which cannot
890+
// affect the arc, instead of forcing them.
855891
if !allTasksStarted(state) {
856-
state.process(valueKnown|fieldConjunctsKnown|allTasksCompleted, attemptOnly)
892+
needs := valueKnown | fieldConjunctsKnown | allTasksCompleted
893+
if state.arcSettledForLookup(f) {
894+
needs = valueKnown | fieldConjunctsKnown
895+
}
896+
state.process(needs, attemptOnly)
857897
state.updateScalar()
858898
}
859899
}

0 commit comments

Comments
 (0)