From 025866b37482162a2ebf1c797cdb92d45f75cad8 Mon Sep 17 00:00:00 2001 From: Raphael Vigee Date: Sat, 11 May 2024 22:33:17 +0100 Subject: [PATCH] Fix yield and ctrlc --- BUILD | 26 ++++++++++++++----- cmd/heph/entrypoint.go | 8 +++++- platform/provider_local.go | 2 +- utils/xcontext/context.go | 52 ++++++++++++++++++++------------------ worker2/dag.go | 8 ++++-- worker2/engine.go | 23 ++++------------- 6 files changed, 65 insertions(+), 54 deletions(-) diff --git a/BUILD b/BUILD index 964b1a41..a70b81d9 100644 --- a/BUILD +++ b/BUILD @@ -79,9 +79,6 @@ deps = ["go.mod", "go.sum"] + glob( release = "release" in CONFIG["profiles"] -def _gobuild(pkg, args): - return "go build -trimpath {} -ldflags='-s -w' -o $OUT {}".format(args, pkg) - build_flags = "" if release: version = target( @@ -101,10 +98,7 @@ for os in ["linux", "darwin"]: name = "build_{}_{}".format(os, arch), run = [ "go version", - _gobuild( - "github.com/hephbuild/heph/cmd/heph", - build_flags, - ), + "go build -o $OUT -trimpath -ldflags='-s -w' github.com/hephbuild/heph/cmd/heph", ], out = "heph_{}_{}".format(os, arch), deps = deps, @@ -119,6 +113,24 @@ for os in ["linux", "darwin"]: ) builds.append(t) + target( + name = "build_debug_{}_{}".format(os, arch), + run = [ + "go version", + "go build -o $OUT -trimpath -gcflags='all=-N -l' github.com/hephbuild/heph/cmd/heph", + ], + out = "heph_debug_{}_{}".format(os, arch), + deps = deps, + env = { + "GOOS": os, + "GOARCH": arch, + "CGO_ENABLED": "0", + }, + tools = ["go"], + labels = ["build-debug"], + pass_env = go_env_vars, + ) + target( name = "cp_builds", run = "cp * $1", diff --git a/cmd/heph/entrypoint.go b/cmd/heph/entrypoint.go index 369a6bdb..bd146763 100644 --- a/cmd/heph/entrypoint.go +++ b/cmd/heph/entrypoint.go @@ -1,6 +1,7 @@ package main import ( + "context" "errors" "github.com/hephbuild/heph/bootstrap" "github.com/hephbuild/heph/utils/xcontext" @@ -11,13 +12,18 @@ import ( func execute() error { ctx, cancel := xcontext.BootstrapSoftCancel() - defer cancel() + defer cancel(nil) vfssimple.WithContext(ctx) err := rootCmd.ExecuteContext(ctx) postRun(err) if err != nil { + // Handle ctrlc gracefuly + if ctx.Err() != nil { + return context.Cause(ctx) + } + return err } diff --git a/platform/provider_local.go b/platform/provider_local.go index dbfa2a68..6dfbd771 100644 --- a/platform/provider_local.go +++ b/platform/provider_local.go @@ -45,7 +45,7 @@ func (p *localExecutor) Exec(ctx context.Context, o ExecOptions, execArgs []stri } sctx, hctx, cancel := xcontext.NewSoftCancel(ctx) - defer cancel() + defer cancel(nil) cmd := sandbox.Exec(sandbox.ExecConfig{ Context: hctx, diff --git a/utils/xcontext/context.go b/utils/xcontext/context.go index ffde2b94..3fddc138 100644 --- a/utils/xcontext/context.go +++ b/utils/xcontext/context.go @@ -2,6 +2,7 @@ package xcontext import ( "context" + "fmt" "github.com/hephbuild/heph/log/log" "github.com/hephbuild/heph/utils/ads" "github.com/hephbuild/heph/utils/xsync" @@ -22,9 +23,11 @@ func IsDone(ctx context.Context) bool { } } +type CancelFunc = context.CancelCauseFunc + type entry struct { - softCancel context.CancelFunc - hardCancel context.CancelFunc + softCancel CancelFunc + hardCancel CancelFunc } type state struct { @@ -42,9 +45,9 @@ func newSoftCancelState() *state { // New returns one context that will be canceled by soft cancel first, the second one will act as a force cancel // both inherit values from their parents -func (a *state) New(parent context.Context) (context.Context, context.Context, func()) { - scctx, scancel := context.WithCancel(parent) - hcctx, hcancel := context.WithCancel(context.Background()) +func (a *state) New(parent context.Context) (context.Context, context.Context, CancelFunc) { + scctx, scancel := context.WithCancelCause(parent) + hcctx, hcancel := context.WithCancelCause(context.Background()) hctx := CancellableContext{ Parent: parent, @@ -58,9 +61,9 @@ func (a *state) New(parent context.Context) (context.Context, context.Context, f a.add(e) - return scctx, hctx, func() { - e.softCancel() - e.hardCancel() + return scctx, hctx, func(cause error) { + e.softCancel(cause) + e.hardCancel(cause) a.remove(e) } @@ -95,7 +98,7 @@ func (a *state) has() bool { return len(a.ctxs) > 0 } -func (a *state) hardCancel() bool { +func (a *state) hardCancel(cause error) bool { a.m.Lock() defer a.m.Unlock() @@ -104,7 +107,7 @@ func (a *state) hardCancel() bool { } for _, e := range a.ctxs { - e.hardCancel() + e.hardCancel(cause) } return true @@ -113,7 +116,7 @@ func (a *state) hardCancel() bool { type keySoftCancelState struct{} // NewSoftCancel See softCancel.New -func NewSoftCancel(parent context.Context) (context.Context, context.Context, context.CancelFunc) { +func NewSoftCancel(parent context.Context) (context.Context, context.Context, CancelFunc) { sc := parent.Value(keySoftCancelState{}).(*state) return sc.New(parent) @@ -140,9 +143,10 @@ func Cancel(ctx context.Context) { } const stuckTimeout = 5 * time.Second +const forceTimeout = 1 * time.Second -func BootstrapSoftCancel() (context.Context, context.CancelFunc) { - ctx, cancel := context.WithCancel(context.Background()) +func BootstrapSoftCancel() (context.Context, CancelFunc) { + ctx, cancel := context.WithCancelCause(context.Background()) sigCh := make(chan os.Signal) signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) @@ -150,23 +154,21 @@ func BootstrapSoftCancel() (context.Context, context.CancelFunc) { sc := newSoftCancelState() go func() { - <-sigCh - cancel() + sig := <-sigCh + cancel(fmt.Errorf(sig.String())) if sc.has() { hardCanceled := false go func() { - <-time.After(200 * time.Millisecond) + <-time.After(forceTimeout) if hardCanceled { return } log.Warnf("Attempting to cancel... ctrl+c one more time to force") }() - select { - case <-sigCh: - } - log.Warnf("Forcing cancellation...") + sig := <-sigCh hardCanceled = true - sc.hardCancel() + log.Warnf("Forcing cancellation...") + sc.hardCancel(fmt.Errorf(sig.String())) select { // Wait for soft cancel to all be unregistered, should be fast, unless something is stuck case <-sc.wait(): @@ -180,7 +182,7 @@ func BootstrapSoftCancel() (context.Context, context.CancelFunc) { } log.Error("Something seems to be stuck, ctrl+c one more time to forcefully exit") - sig := <-sigCh + sig = <-sigCh sigN := 0 if sig, ok := sig.(syscall.Signal); ok { sigN = int(sig) @@ -194,8 +196,8 @@ func BootstrapSoftCancel() (context.Context, context.CancelFunc) { sigCh <- os.Interrupt })) - return ctx, func() { - cancel() - sc.hardCancel() + return ctx, func(cause error) { + cancel(cause) + sc.hardCancel(cause) } } diff --git a/worker2/dag.go b/worker2/dag.go index 2eef9dd4..3bf98451 100644 --- a/worker2/dag.go +++ b/worker2/dag.go @@ -83,7 +83,9 @@ func (d *nodesTransitive[T]) TransitiveSet() *sets.Set[*Node[T], *Node[T]] { func (d *nodesTransitive[T]) TransitiveValues() iter.Seq2[int, T] { return func(yield func(int, T) bool) { for i, node := range d.TransitiveSet().Slice() { - yield(i, node.V) + if !yield(i, node.V) { + break + } } } } @@ -91,7 +93,9 @@ func (d *nodesTransitive[T]) TransitiveValues() iter.Seq2[int, T] { func (d *nodesTransitive[T]) Values() iter.Seq2[int, T] { return func(yield func(int, T) bool) { for i, node := range d.Set().Slice() { - yield(i, node.V) + if !yield(i, node.V) { + break + } } } } diff --git a/worker2/engine.go b/worker2/engine.go index 20b4c8b6..90813b70 100644 --- a/worker2/engine.go +++ b/worker2/engine.go @@ -4,7 +4,6 @@ import ( "github.com/bep/debounce" "github.com/dlsniper/debugger" "github.com/hephbuild/heph/utils/ads" - "go.uber.org/multierr" "runtime" "sync" "sync/atomic" @@ -111,13 +110,10 @@ func (e *Engine) waitForDeps(exec *Execution) error { exec.c.L.Lock() defer exec.c.L.Unlock() - var errs []error for { depObj := exec.Dep.GetNode() - allDepsSucceeded := true allDepsDone := true - errs = errs[:0] for _, dep := range depObj.Dependencies.Values() { depExec := e.scheduleOne(dep) @@ -129,30 +125,21 @@ func (e *Engine) waitForDeps(exec *Execution) error { allDepsDone = false } - if state != ExecStateSucceeded { - allDepsSucceeded = false - } - switch state { case ExecStateSkipped, ExecStateFailed: - errs = append(errs, Error{ + // Prevent accumulating a million errors, fail early + return Error{ ID: depExec.ID, State: depExec.State, Name: depExec.Dep.GetName(), Err: depExec.Err, - }) + } } } if allDepsDone { - if len(errs) > 0 { - return multierr.Combine(errs...) - } - - if allDepsSucceeded { - if e.tryFreeze(depObj) { - return nil - } + if e.tryFreeze(depObj) { + return nil } }