Skip to content

Commit 14708d6

Browse files
committed
Persist the fact that a dependency might have failed and replay it each
time (*onceFun).run is invoked. Fixes #371.
1 parent d9e2e41 commit 14708d6

File tree

3 files changed

+99
-20
lines changed

3 files changed

+99
-20
lines changed

mage/main_test.go

+29
Original file line numberDiff line numberDiff line change
@@ -1615,6 +1615,35 @@ func TestWrongDependency(t *testing.T) {
16151615
}
16161616
}
16171617

1618+
// See https://github.com/magefile/mage/issues/371
1619+
func TestRacyDependency(t *testing.T) {
1620+
var stdout, stderr bytes.Buffer
1621+
inv := Invocation{
1622+
Dir: "./testdata/racy_deps",
1623+
Args: []string{"test4"},
1624+
Stderr: &stderr,
1625+
Stdout: &stdout,
1626+
}
1627+
code := Invoke(inv)
1628+
if code != 1 {
1629+
t.Fatalf("expected 1, but got %v", code)
1630+
}
1631+
expectedStderr := `Error: error test1
1632+
error test1
1633+
error test1
1634+
`
1635+
actualStderr := stderr.String()
1636+
if actualStderr != expectedStderr {
1637+
t.Fatalf("expected %q, but got %q", expectedStderr, actualStderr)
1638+
}
1639+
expectedStdout := `execute test1
1640+
`
1641+
actualStdout := stdout.String()
1642+
if actualStdout != expectedStdout {
1643+
t.Fatalf("expected %q, but got %q", expectedStdout, actualStdout)
1644+
}
1645+
}
1646+
16181647
/// This code liberally borrowed from https://github.com/rsc/goversion/blob/master/version/exe.go
16191648

16201649
type (

mage/testdata/racy_deps/magefile.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// +build mage
2+
3+
package main
4+
5+
import (
6+
"fmt"
7+
"time"
8+
9+
"github.com/magefile/mage/mg"
10+
)
11+
12+
func Test1() error {
13+
fmt.Println("execute test1")
14+
return fmt.Errorf("error test1")
15+
}
16+
17+
func Test2() error {
18+
mg.Deps(Test1)
19+
fmt.Println("execute test2")
20+
return nil
21+
}
22+
23+
func Test3() error {
24+
time.Sleep(time.Second)
25+
mg.Deps(Test2)
26+
fmt.Println("execute test3")
27+
return nil
28+
}
29+
func Test4() error {
30+
mg.Deps(Test1, Test2, Test3)
31+
fmt.Println("execute test4")
32+
return nil
33+
}

mg/deps.go

+37-20
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package mg
22

33
import (
44
"context"
5-
"fmt"
65
"log"
76
"os"
87
"reflect"
@@ -36,7 +35,6 @@ func (o *onceMap) LoadOrStore(f Fn) *onceFun {
3635
return existing
3736
}
3837
one := &onceFun{
39-
once: &sync.Once{},
4038
fn: f,
4139
displayName: displayName(f.Name()),
4240
}
@@ -91,37 +89,44 @@ func CtxDeps(ctx context.Context, fns ...interface{}) {
9189

9290
// runDeps assumes you've already called checkFns.
9391
func runDeps(ctx context.Context, fns []Fn) {
94-
mu := &sync.Mutex{}
95-
var errs []string
96-
var exit int
97-
wg := &sync.WaitGroup{}
92+
resultCh := make(chan error, len(fns))
93+
var wg sync.WaitGroup
94+
wg.Add(len(fns))
9895
for _, f := range fns {
9996
fn := onces.LoadOrStore(f)
100-
wg.Add(1)
10197
go func() {
10298
defer func() {
10399
if v := recover(); v != nil {
104-
mu.Lock()
105-
if err, ok := v.(error); ok {
106-
exit = changeExit(exit, ExitStatus(err))
107-
} else {
108-
exit = changeExit(exit, 1)
100+
var err error
101+
var ok bool
102+
if err, ok = v.(error); !ok {
103+
err = Fatal(1)
104+
}
105+
select {
106+
case <-ctx.Done():
107+
case resultCh <- err:
109108
}
110-
errs = append(errs, fmt.Sprint(v))
111-
mu.Unlock()
112109
}
113110
wg.Done()
114111
}()
115-
if err := fn.run(ctx); err != nil {
116-
mu.Lock()
117-
errs = append(errs, fmt.Sprint(err))
118-
exit = changeExit(exit, ExitStatus(err))
119-
mu.Unlock()
112+
err := fn.run(ctx)
113+
select {
114+
case <-ctx.Done():
115+
case resultCh <- err:
120116
}
121117
}()
122118
}
123119

124120
wg.Wait()
121+
close(resultCh)
122+
var errs []string
123+
var exit int
124+
for err := range resultCh {
125+
if err != nil {
126+
errs = append(errs, err.Error())
127+
exit = changeExit(exit, ExitStatus(err))
128+
}
129+
}
125130
if len(errs) > 0 {
126131
panic(Fatal(exit, strings.Join(errs, "\n")))
127132
}
@@ -184,9 +189,12 @@ func displayName(name string) string {
184189
}
185190

186191
type onceFun struct {
187-
once *sync.Once
192+
once sync.Once
188193
fn Fn
189194
err error
195+
// depPanicked references the panic a dependency had previously
196+
// failed with if set
197+
depPanicked interface{}
190198

191199
displayName string
192200
}
@@ -195,10 +203,19 @@ type onceFun struct {
195203
// the same error output.
196204
func (o *onceFun) run(ctx context.Context) error {
197205
o.once.Do(func() {
206+
defer func() {
207+
if v := recover(); v != nil {
208+
o.depPanicked = v
209+
panic(v)
210+
}
211+
}()
198212
if Verbose() {
199213
logger.Println("Running dependency:", displayName(o.fn.Name()))
200214
}
201215
o.err = o.fn.Run(ctx)
202216
})
217+
if o.depPanicked != nil {
218+
panic(o.depPanicked)
219+
}
203220
return o.err
204221
}

0 commit comments

Comments
 (0)