Skip to content

Commit 9c8ee9e

Browse files
authored
Memory leak fixes (#4)
* dagger fix: avoid leaking whole state/job with session.Group Passing solver.state around as a session.Group results in the solver.state being stored in cache refs, which makes cache ref memory leaks significantly worse. Signed-off-by: Erik Sipsma <[email protected]> * rm SetFinalizer usage as it makes memory unfreeable Based on testing, each of the three usages of SetFinalizer in buildkit are broken in that they make the memory unfreeable. Most likely this is somehow related to cyclic data structures, but SetFinalizer is very tricky to use correctly so could be something else. Either way, you can empirically see their usage results in unfreeable memory by looking at the heap.alloc.garbage metric when running `viewcore breakdown`. That metric indicates memory in the heap that's not referenced but has not been released. With the SetFinalizers in place, the garbage accumulates to over a GB when running TestModule for a few mins. It never goes down even with manual GC triggers and GOMEMLIMIT settings that force the GC to free as much memory as it can. Removing any one of the SetFinalizers reduces that garbage metric by a few hundred MBs, but removing all of them reduces it down to negligible levels. Signed-off-by: Erik Sipsma <[email protected]> --------- Signed-off-by: Erik Sipsma <[email protected]>
1 parent acd758f commit 9c8ee9e

File tree

4 files changed

+35
-32
lines changed

4 files changed

+35
-32
lines changed

solver/jobs.go

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ type state struct {
5959
clientVertex client.Vertex
6060
origDigest digest.Digest // original LLB digest. TODO: probably better to use string ID so this isn't needed
6161

62-
mu sync.Mutex
62+
mu *sync.Mutex
6363
op *sharedOp
6464
edges map[Index]*edge
6565
opts SolverOpt
@@ -70,16 +70,36 @@ type state struct {
7070
solver *Solver
7171
}
7272

73-
func (s *state) SessionIterator() session.Iterator {
74-
return s.sessionIterator()
73+
func (s *state) SessionGroup() *stateSessionGroup {
74+
return &stateSessionGroup{
75+
mu: s.mu,
76+
jobs: s.jobs,
77+
stateParents: s.parents,
78+
solver: s.solver,
79+
}
80+
}
81+
82+
type stateSessionGroup struct {
83+
mu *sync.Mutex
84+
jobs map[*Job]struct{}
85+
stateParents map[digest.Digest]struct{}
86+
solver *Solver
7587
}
7688

77-
func (s *state) sessionIterator() *sessionGroup {
78-
return &sessionGroup{state: s, visited: map[string]struct{}{}}
89+
func (g *stateSessionGroup) SessionIterator() session.Iterator {
90+
return g.sessionIterator()
91+
}
92+
93+
func (g *stateSessionGroup) sessionIterator() *sessionGroup {
94+
return &sessionGroup{
95+
stateSessionGroup: g,
96+
visited: map[string]struct{}{},
97+
}
7998
}
8099

81100
type sessionGroup struct {
82-
*state
101+
*stateSessionGroup
102+
83103
visited map[string]struct{}
84104
parents []session.Iterator
85105
mode int
@@ -104,7 +124,7 @@ func (g *sessionGroup) NextSession() string {
104124
if g.mode == 1 {
105125
parents := map[digest.Digest]struct{}{}
106126
g.mu.Lock()
107-
for p := range g.state.parents {
127+
for p := range g.stateParents {
108128
parents[p] = struct{}{}
109129
}
110130
g.mu.Unlock()
@@ -114,7 +134,7 @@ func (g *sessionGroup) NextSession() string {
114134
pst, ok := g.solver.actives[p]
115135
g.solver.mu.Unlock()
116136
if ok {
117-
gg := pst.sessionIterator()
137+
gg := pst.SessionGroup().sessionIterator()
118138
gg.visited = g.visited
119139
g.parents = append(g.parents, gg)
120140
}
@@ -280,7 +300,7 @@ func (sb *subBuilder) InContext(ctx context.Context, f func(context.Context, ses
280300
if sb.mspan.Span != nil {
281301
ctx = trace.ContextWithSpan(ctx, sb.mspan)
282302
}
283-
return f(ctx, sb.state)
303+
return f(ctx, sb.state.SessionGroup())
284304
}
285305

286306
func (sb *subBuilder) EachValue(ctx context.Context, key string, fn func(interface{}) error) error {
@@ -496,6 +516,8 @@ func (jl *Solver) loadUnlocked(ctx context.Context, v, parent Vertex, j *Job, ca
496516

497517
if !ok {
498518
st = &state{
519+
mu: &sync.Mutex{},
520+
499521
opts: jl.opts,
500522
jobs: map[*Job]struct{}{},
501523
parents: map[digest.Digest]struct{}{},
@@ -939,7 +961,7 @@ func (s *sharedOp) CalcSlowCache(ctx context.Context, index Index, p PreprocessF
939961
if st.mspan.Span != nil {
940962
ctx2 = trace.ContextWithSpan(ctx2, st.mspan)
941963
}
942-
err = p(ctx2, res, st)
964+
err = p(ctx2, res, st.SessionGroup())
943965
if err != nil {
944966
f = nil
945967
ctx = ctx2
@@ -952,7 +974,7 @@ func (s *sharedOp) CalcSlowCache(ctx context.Context, index Index, p PreprocessF
952974
if s.st.mspan.Span != nil {
953975
ctx = trace.ContextWithSpan(ctx, s.st.mspan)
954976
}
955-
key, err = f(withAncestorCacheOpts(ctx, s.st), res, s.st)
977+
key, err = f(withAncestorCacheOpts(ctx, s.st), res, s.st.SessionGroup())
956978
}
957979
if err != nil {
958980
select {
@@ -1018,7 +1040,7 @@ func (s *sharedOp) CacheMap(ctx context.Context, index int) (resp *cacheMapResp,
10181040
notifyCompleted(retErr, false)
10191041
}()
10201042
}
1021-
res, done, err := op.CacheMap(ctx, s.st, len(s.cacheRes))
1043+
res, done, err := op.CacheMap(ctx, s.st.SessionGroup(), len(s.cacheRes))
10221044
complete := true
10231045
if err != nil {
10241046
select {
@@ -1097,7 +1119,7 @@ func (s *sharedOp) Exec(ctx context.Context, inputs []Result) (outputs []Result,
10971119
notifyCompleted(retErr, false)
10981120
}()
10991121

1100-
res, err := op.Exec(ctx, s.st, inputs)
1122+
res, err := op.Exec(ctx, s.st.SessionGroup(), inputs)
11011123
complete := true
11021124
if err != nil {
11031125
select {

solver/llbsolver/errdefs/exec.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ package errdefs
22

33
import (
44
"context"
5-
"runtime"
65

76
"github.com/moby/buildkit/solver"
8-
"github.com/moby/buildkit/util/bklog"
97
)
108

119
// ExecError will be returned when an error is encountered when evaluating an op.
@@ -74,14 +72,5 @@ func WithExecErrorWithContext(ctx context.Context, err error, inputs, mounts []s
7472
Inputs: inputs,
7573
Mounts: mounts,
7674
}
77-
runtime.SetFinalizer(ee, func(e *ExecError) {
78-
if !e.OwnerBorrowed {
79-
e.EachRef(func(r solver.Result) error {
80-
bklog.G(ctx).Warn("leaked execError detected and released")
81-
r.Release(context.TODO())
82-
return nil
83-
})
84-
}
85-
})
8675
return ee
8776
}

util/contentutil/pusher.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package contentutil
22

33
import (
44
"context"
5-
"runtime"
65
"sync"
76
"time"
87

@@ -78,9 +77,6 @@ func (i *pushingIngester) Writer(ctx context.Context, opts ...content.WriterOpt)
7877
release()
7978
return nil, err
8079
}
81-
runtime.SetFinalizer(contentWriter, func(_ content.Writer) {
82-
release()
83-
})
8480
return &writer{
8581
Writer: contentWriter,
8682
contentWriterRef: wOpts.Ref,

util/resolver/limited/group.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package limited
33
import (
44
"context"
55
"io"
6-
"runtime"
76
"strings"
87
"sync"
98

@@ -124,9 +123,6 @@ func (f *fetcher) Fetch(ctx context.Context, desc ocispecs.Descriptor) (io.ReadC
124123
release()
125124
}
126125
rcw.release = closer
127-
runtime.SetFinalizer(rcw, func(rc *readCloser) {
128-
rc.close()
129-
})
130126

131127
if s, ok := rc.(io.Seeker); ok {
132128
return &readCloserSeeker{rcw, s}, nil

0 commit comments

Comments
 (0)