Skip to content

Commit 2785aa4

Browse files
committed
internal/core/adt: fix lifetime tracking of shared closeContext
Upon sharing of a node the processing of this node terminates. This, in turn, leads to the closeContext to be finalized. However, if at a later point another conjunct invalidates the sharing, processing will continue. For this reason, we should hold finalizing a closeContext until all other conjuncts are processed or up to the point sharing is undone. Delaying closing closeContext can have an impact on performance. In this case it should not matter, as there is nothing to process about shared nodes. Fixes #3062 Signed-off-by: Marcel van Lohuizen <[email protected]> Change-Id: Id654881563eba54123c8afbd837d47bba7f53614 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1195897 Reviewed-by: Daniel Martí <[email protected]> TryBot-Result: CUEcueckoo <[email protected]> Unity-Result: CUE porcuepine <[email protected]>
1 parent 8fdc65e commit 2785aa4

File tree

4 files changed

+88
-1
lines changed

4 files changed

+88
-1
lines changed

cue/testdata/eval/sharing.txtar

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
2+
3+
-- in.cue --
4+
issue3062: ok1: {
5+
#S: "a"
6+
#o: x: #S
7+
o: #o
8+
o: X
9+
X: x: A
10+
A: "a"
11+
}
12+
-- out/eval/stats --
13+
Leaks: 0
14+
Freed: 11
15+
Reused: 4
16+
Allocs: 7
17+
Retain: 3
18+
19+
Unifications: 11
20+
Conjuncts: 18
21+
Disjuncts: 12
22+
-- out/eval --
23+
(struct){
24+
issue3062: (struct){
25+
ok1: (struct){
26+
#S: (string){ "a" }
27+
#o: (#struct){
28+
x: (string){ "a" }
29+
}
30+
o: (#struct){
31+
x: (string){ "a" }
32+
}
33+
X: (struct){
34+
x: (string){ "a" }
35+
}
36+
A: (string){ "a" }
37+
}
38+
}
39+
}
40+
-- out/compile --
41+
--- in.cue
42+
{
43+
issue3062: {
44+
ok1: {
45+
#S: "a"
46+
#o: {
47+
x: 〈1;#S〉
48+
}
49+
o: 〈0;#o〉
50+
o: 〈0;X〉
51+
X: {
52+
x: 〈1;A〉
53+
}
54+
A: "a"
55+
}
56+
}
57+
}

internal/core/adt/debug.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,10 @@ const (
192192
// DEFER is used to track recursive processing of a node.
193193
DEFER // Always refers to self.
194194

195+
// SHARED is used to track shared nodes. The processing of shared nodes may
196+
// change until all other conjuncts have been processed.
197+
SHARED
198+
195199
// TEST is used for testing notifications.
196200
TEST // Always refers to self.
197201
)
@@ -219,6 +223,8 @@ func (k depKind) String() string {
219223
return "INIT"
220224
case DEFER:
221225
return "DEFER"
226+
case SHARED:
227+
return "SHARED"
222228
case TEST:
223229
return "TEST"
224230
}
@@ -565,7 +571,7 @@ func (m *mermaidContext) cc(cc *closeContext) {
565571
taskID = m.task(d)
566572
}
567573
name = fmt.Sprintf("%s((%d))", taskID, d.taskID)
568-
case ROOT, INIT:
574+
case ROOT, INIT, SHARED:
569575
w = node
570576
src := cc.src
571577
if v.f != src.Label {

internal/core/adt/share.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,18 @@ func (n *nodeContext) unshare() {
4646
n.node.BaseValue = n.origBaseValue
4747

4848
n.scheduleVertexConjuncts(n.shared, v, n.sharedID)
49+
50+
n.sharedID.cc.decDependent(n.ctx, SHARED, n.node.cc)
51+
n.sharedID.cc = nil
52+
}
53+
54+
// finalizeSharing should be called when it is known for sure a node can be
55+
// shared.
56+
func (n *nodeContext) finalizeSharing() {
57+
if n.sharedID.cc != nil {
58+
n.sharedID.cc.decDependent(n.ctx, SHARED, n.node.cc)
59+
n.sharedID.cc = nil
60+
}
4961
}
5062

5163
func (n *nodeContext) share(c Conjunct, arc *Vertex, id CloseInfo) {
@@ -58,6 +70,13 @@ func (n *nodeContext) share(c Conjunct, arc *Vertex, id CloseInfo) {
5870
n.isShared = true
5971
n.shared = c
6072
n.sharedID = id
73+
74+
// At this point, the node may still be unshared at a later point. For this
75+
// purpose we need to keep the retain count above zero until all conjuncts
76+
// have been processed and it is clear that sharing is possible. Delaying
77+
// such a count should not hurt performance, as a shared node is completed
78+
// anyway.
79+
id.cc.incDependent(n.ctx, SHARED, n.node.cc)
6180
}
6281

6382
func (n *nodeContext) shareIfPossible(c Conjunct, arc *Vertex, id CloseInfo) bool {

internal/core/adt/unify.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,11 @@ func (n *nodeContext) completeAllArcs(needs condition, mode runMode) bool {
562562
}
563563
n.node.Arcs = n.node.Arcs[:k]
564564

565+
// This should be called after all arcs have been processed, because
566+
// whether sharing is possible or not may depend on how arcs with type
567+
// ArcPending will resolve.
568+
n.finalizeSharing()
569+
565570
// Strip struct literals that were not initialized and are not part
566571
// of the output.
567572
//

0 commit comments

Comments
 (0)