make sure execerror is released on cancellation

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
v0.8
Tonis Tiigi 2020-11-25 15:13:59 -08:00
parent 372df78cc8
commit df3a9cad23
3 changed files with 47 additions and 3 deletions

View File

@ -659,6 +659,7 @@ func (s *sharedOp) CalcSlowCache(ctx context.Context, index Index, p PreprocessF
case <-ctx.Done(): case <-ctx.Done():
if strings.Contains(err.Error(), context.Canceled.Error()) { if strings.Contains(err.Error(), context.Canceled.Error()) {
complete = false complete = false
releaseError(err)
err = errors.Wrap(ctx.Err(), err.Error()) err = errors.Wrap(ctx.Err(), err.Error())
} }
default: default:
@ -717,6 +718,7 @@ func (s *sharedOp) CacheMap(ctx context.Context, index int) (resp *cacheMapResp,
case <-ctx.Done(): case <-ctx.Done():
if strings.Contains(err.Error(), context.Canceled.Error()) { if strings.Contains(err.Error(), context.Canceled.Error()) {
complete = false complete = false
releaseError(err)
err = errors.Wrap(ctx.Err(), err.Error()) err = errors.Wrap(ctx.Err(), err.Error())
} }
default: default:
@ -774,6 +776,7 @@ func (s *sharedOp) Exec(ctx context.Context, inputs []Result) (outputs []Result,
case <-ctx.Done(): case <-ctx.Done():
if strings.Contains(err.Error(), context.Canceled.Error()) { if strings.Contains(err.Error(), context.Canceled.Error()) {
complete = false complete = false
releaseError(err)
err = errors.Wrap(ctx.Err(), err.Error()) err = errors.Wrap(ctx.Err(), err.Error())
} }
default: default:
@ -911,3 +914,15 @@ func WrapSlowCache(err error, index Index, res Result) error {
} }
return &SlowCacheError{Index: index, Result: res, error: err} return &SlowCacheError{Index: index, Result: res, error: err}
} }
func releaseError(err error) {
if err == nil {
return
}
if re, ok := err.(interface {
Release() error
}); ok {
re.Release()
}
releaseError(errors.Unwrap(err))
}

View File

@ -167,6 +167,8 @@ func newResultProxy(b *llbBridge, req frontend.SolveRequest) *resultProxy {
rp.errResults = append(rp.errResults, res) rp.errResults = append(rp.errResults, res)
return nil return nil
}) })
// acquire ownership so ExecError finalizer doesn't attempt to release as well
ee.OwnerBorrowed = true
} }
return res, err return res, err
} }

View File

@ -1,7 +1,11 @@
package errdefs package errdefs
import ( import (
"context"
"runtime"
"github.com/moby/buildkit/solver" "github.com/moby/buildkit/solver"
"github.com/sirupsen/logrus"
) )
// ExecError will be returned when an error is encountered when evaluating an op. // ExecError will be returned when an error is encountered when evaluating an op.
@ -9,6 +13,7 @@ type ExecError struct {
error error
Inputs []solver.Result Inputs []solver.Result
Mounts []solver.Result Mounts []solver.Result
OwnerBorrowed bool
} }
func (e *ExecError) Unwrap() error { func (e *ExecError) Unwrap() error {
@ -35,13 +40,35 @@ func (e *ExecError) EachRef(fn func(solver.Result) error) (err error) {
return err return err
} }
func (e *ExecError) Release() error {
if e.OwnerBorrowed {
return nil
}
err := e.EachRef(func(r solver.Result) error {
r.Release(context.TODO())
return nil
})
e.OwnerBorrowed = true
return err
}
func WithExecError(err error, inputs, mounts []solver.Result) error { func WithExecError(err error, inputs, mounts []solver.Result) error {
if err == nil { if err == nil {
return nil return nil
} }
return &ExecError{ ee := &ExecError{
error: err, error: err,
Inputs: inputs, Inputs: inputs,
Mounts: mounts, Mounts: mounts,
} }
runtime.SetFinalizer(ee, func(e *ExecError) {
if !e.OwnerBorrowed {
e.EachRef(func(r solver.Result) error {
logrus.Warn("leaked execError detected and released")
r.Release(context.TODO())
return nil
})
}
})
return ee
} }