Skip to content

WIP fix(gnovm): patch edge condition in garbage collector #4302

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions gnovm/pkg/gnolang/garbage_collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,11 @@ func GCVisitorFn(gcCycle int64, alloc *Allocator, visitCount int64) Visitor {
}

if oo, isObject := v.(Object); isObject {
defer func() {
// Finally bump cycle for object.
oo.SetLastGCCycle(gcCycle)
}()

// Return if already measured.
if debug {
debug.Printf("oo.GetLastGCCycle: %d, gcCycle: %d\n", oo.GetLastGCCycle(), gcCycle)
}

if oo.GetLastGCCycle() == gcCycle {
return false // but don't stop
}
Expand All @@ -140,17 +136,24 @@ func GCVisitorFn(gcCycle int64, alloc *Allocator, visitCount int64) Visitor {

// Add object size to alloc.
size := v.GetShallowSize()
alloc.Allocate(size)

// Stop if alloc max exceeded.
// Stop if alloc max exceeded during GC.
// NOTE: Unlikely to occur, but keep it here for
// now to handle potential edge cases.
// Consider removing it later if no issues arise.
maxBytes, curBytes := alloc.Status()
if maxBytes < curBytes {
if maxBytes < curBytes+size {
return true
}

alloc.Allocate(size)

// bump before visiting associated,
// this avoids infinite recurse.
if oo, isObject := v.(Object); isObject {
oo.SetLastGCCycle(gcCycle)
}

// Invoke the traverser on v.
stop := v.VisitAssociated(vis)

Expand Down Expand Up @@ -198,8 +201,18 @@ func (fv *FuncValue) VisitAssociated(vis Visitor) (stop bool) {
}
}

// Skip visiting the parent to avoid redundancy
// and prevent a potential cycle.
// Visit parent.
switch v := fv.Parent.(type) {
case nil:
return
case *Block:
if v != nil {
stop = vis(v)
}
case RefValue:
stop = vis(v)
}

return
}

Expand Down
2 changes: 1 addition & 1 deletion gnovm/tests/files/alloc_0.gno
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func main() {
}

// Output:
// MemStats: Allocator{maxBytes:100000000, bytes:6358}
// MemStats: Allocator{maxBytes:100000000, bytes:6454}

// TypeCheckError:
// main/files/alloc_0.gno:13:2: declared and not used: f1
2 changes: 1 addition & 1 deletion gnovm/tests/files/alloc_1.gno
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func main() {
}

// Output:
// MemStats: Allocator{maxBytes:100000000, bytes:7608}
// MemStats: Allocator{maxBytes:100000000, bytes:7704}

// TypeCheckError:
// main/files/alloc_1.gno:18:2: declared and not used: S1
2 changes: 1 addition & 1 deletion gnovm/tests/files/alloc_3.gno
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ func main() {
}

// Output:
// MemStats after GC: Allocator{maxBytes:110000000, bytes:5704}
// MemStats after GC: Allocator{maxBytes:110000000, bytes:5800}

// TypeCheckError:
// main/files/alloc_3.gno:7:2: declared and not used: data
2 changes: 1 addition & 1 deletion gnovm/tests/files/alloc_4.gno
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ func main() {
}

// Output:
// memstats in main after first GC: Allocator{maxBytes:50000, bytes:11438}
// memstats in main after first GC: Allocator{maxBytes:50000, bytes:11630}
// memstats in main after second GC: Allocator{maxBytes:50000, bytes:7069}
2 changes: 1 addition & 1 deletion gnovm/tests/files/alloc_5.gno
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func main() {
}

// Output:
// memstats in main after GC: Allocator{maxBytes:100000000, bytes:6048}
// memstats in main after GC: Allocator{maxBytes:100000000, bytes:6144}

// TypeCheckError:
// main/files/alloc_5.gno:7:2: declared and not used: data
2 changes: 1 addition & 1 deletion gnovm/tests/files/alloc_6.gno
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func main() {
}

// Output:
// memstats in main after GC: Allocator{maxBytes:100000000, bytes:6048}
// memstats in main after GC: Allocator{maxBytes:100000000, bytes:6144}

// TypeCheckError:
// main/files/alloc_6.gno:7:6: declared and not used: a
2 changes: 1 addition & 1 deletion gnovm/tests/files/alloc_6a.gno
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func main() {
}

// Output:
// memstats in main after GC: Allocator{maxBytes:100000000, bytes:6554}
// memstats in main after GC: Allocator{maxBytes:100000000, bytes:6650}

// TypeCheckError:
// main/files/alloc_6a.gno:8:7: declared and not used: a
2 changes: 1 addition & 1 deletion gnovm/tests/files/alloc_7.gno
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func main() {
}

// Output:
// MemStats: Allocator{maxBytes:100000000, bytes:5888}
// MemStats: Allocator{maxBytes:100000000, bytes:5984}

// TypeCheckError:
// main/files/alloc_7.gno:10:2: declared and not used: s1
12 changes: 12 additions & 0 deletions gnovm/tests/files/alloc_8.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// MAXALLOC: 50000000
package main

func recurse(n int) {
recurse(n + 1)
}
func main() {
recurse(0)
}

// Error:
// should not happen, allocation limit exceeded while gc.
Loading