Skip to content

Commit a83cac3

Browse files
authored
v2 (#2)
* add race and fix any * rename promise to race * finish rename to race * finish implementing any * create v2 * remove dead code
1 parent 9b95d2a commit a83cac3

File tree

8 files changed

+231
-26
lines changed

8 files changed

+231
-26
lines changed

benchmark/bench_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package main
22

33
import (
4-
"github.com/garlicnation/promises"
54
"testing"
5+
6+
promise "github.com/garlicnation/promises/v2"
67
)
78

89
var values []int

blog_example/promises_checksum/promises_checksum.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import (
44
"crypto/sha512"
55
"encoding/hex"
66
"fmt"
7-
"github.com/garlicnation/promises"
87
"io"
98
"io/ioutil"
109
"net/http"
1110
"time"
11+
12+
promise "github.com/garlicnation/promises/v2"
1213
)
1314

1415
var listOfWebsites = []string{

docs.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
2-
Promises is a library that builds something similar to JS style promises, or Futures(as seen in Java and other languages) for golang.
2+
Package promise builds something similar to JS style promises, or Futures(as seen in Java and other languages) for golang.
33
4-
Promises is type-safe at runtime, and within an order of magnitude of performance of a solution built with pure channels and goroutines.
4+
promise is type-safe at runtime, and within an order of magnitude of performance of a solution built with pure channels and goroutines.
55
66
For a more thorough introduction to the library, please check out https://github.com/garlicnation/promises/blob/master/blog_example/WHY.md
77

go.mod

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
module github.com/garlicnation/promises
1+
module github.com/garlicnation/promises/v2
22

33
go 1.12
44

55
require (
6-
github.com/campoy/embedmd v1.0.0 // indirect
76
github.com/pkg/errors v0.8.1
87
github.com/stretchr/testify v1.4.0
98
)

go.sum

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY=
2-
github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8=
31
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
42
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
53
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=

promises.go

+102-16
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
package promise
22

3-
import "reflect"
4-
import "sync"
5-
import "sync/atomic"
6-
import "github.com/pkg/errors"
3+
import (
4+
"fmt"
5+
"reflect"
6+
"sync"
7+
"sync/atomic"
8+
9+
"github.com/pkg/errors"
10+
)
711

812
type promiseType int
913

1014
const (
11-
legacyCall promiseType = iota
12-
simpleCall
15+
simpleCall promiseType = iota
1316
thenCall
1417
allCall
18+
raceCall
1519
anyCall
1620
)
1721

@@ -23,10 +27,12 @@ type Promise struct {
2327
functionRv reflect.Value
2428
results []reflect.Value
2529
resultType []reflect.Type
30+
anyErrs []error
2631
// returnsError is true if the last value returns an error
2732
returnsError bool
2833
cond sync.Cond
2934
counter int64
35+
errCounter int64
3036
noCopy
3137
}
3238

@@ -36,7 +42,7 @@ type noCopy struct{}
3642
func (*noCopy) Lock() {}
3743
func (*noCopy) Unlock() {}
3844

39-
func (p *Promise) anyCall(priors []*Promise, index int) (results []reflect.Value) {
45+
func (p *Promise) raceCall(priors []*Promise, index int) (results []reflect.Value) {
4046
prior := priors[index]
4147
prior.cond.L.Lock()
4248
for !prior.complete {
@@ -78,6 +84,40 @@ func (p *Promise) allCall(priors []*Promise, index int) (results []reflect.Value
7884
return nil
7985
}
8086

87+
// AnyErr returns when all promises passed to Any fail
88+
type AnyErr struct {
89+
// Errs contains the error of all passed promises
90+
Errs []error
91+
// LastErr contains the error of the last promise to fail.
92+
LastErr error
93+
}
94+
95+
func (err *AnyErr) Error() string {
96+
return fmt.Sprintf("all %d promises failed. last err=%v", len(err.Errs), err.LastErr)
97+
}
98+
99+
func (p *Promise) anyCall(priors []*Promise, index int) (results []reflect.Value) {
100+
prior := priors[index]
101+
prior.cond.L.Lock()
102+
for !prior.complete {
103+
prior.cond.Wait()
104+
}
105+
prior.cond.L.Unlock()
106+
if prior.err != nil {
107+
remaining := atomic.AddInt64(&p.errCounter, -1)
108+
p.anyErrs[index] = prior.err
109+
if remaining != 0 {
110+
return nil
111+
}
112+
panic(AnyErr{Errs: p.anyErrs[:], LastErr: prior.err})
113+
}
114+
remaining := atomic.AddInt64(&p.counter, -1)
115+
if remaining == 0 {
116+
return prior.results[:]
117+
}
118+
return nil
119+
}
120+
81121
func empty() {}
82122

83123
// All returns a promise that resolves if all of the passed promises
@@ -107,10 +147,10 @@ func All(promises ...*Promise) *Promise {
107147

108148
const anyErrorFormat = "promise %d has an unexpected return type, expected all promises passed to Any to return the same type"
109149

110-
// Any returns a promise that resolves if any of the passed promises
150+
// Race returns a promise that resolves if any of the passed promises
111151
// succeed or fails if any of the passed promises panics.
112152
// All of the supplied promises must be of the same type.
113-
func Any(promises ...*Promise) *Promise {
153+
func Race(promises ...*Promise) *Promise {
114154
if len(promises) == 0 {
115155
return New(empty)
116156
}
@@ -135,16 +175,57 @@ func Any(promises ...*Promise) *Promise {
135175

136176
p := &Promise{
137177
cond: sync.Cond{L: &sync.Mutex{}},
138-
t: anyCall,
178+
t: raceCall,
139179
}
140180

141181
// Extract the type
142-
p.resultType = []reflect.Type{}
143-
for _, prior := range promises {
144-
p.resultType = append(p.resultType, prior.resultType...)
182+
p.resultType = firstResultType[:]
183+
184+
p.counter = int64(1)
185+
186+
for i := range promises {
187+
go p.run(reflect.Value{}, nil, promises, i, nil)
188+
}
189+
return p
190+
}
191+
192+
// Any returns a promise that resolves if any of the passed promises
193+
// succeed or fails if all of the passed promises panics.
194+
// All of the supplied promises must be of the same type.
195+
func Any(promises ...*Promise) *Promise {
196+
if len(promises) == 0 {
197+
return New(empty)
198+
}
199+
200+
if len(promises) == 1 {
201+
return promises[0]
202+
}
203+
204+
// Check that all the promises have the same return type
205+
firstResultType := promises[0].resultType
206+
for promiseIdx, promise := range promises[1:] {
207+
newResultType := promise.resultType
208+
if len(firstResultType) != len(newResultType) {
209+
panic(errors.Errorf(anyErrorFormat, promiseIdx))
210+
}
211+
for index := range firstResultType {
212+
if firstResultType[index] != newResultType[index] {
213+
panic(errors.Errorf(anyErrorFormat, promiseIdx))
214+
}
215+
}
216+
}
217+
218+
p := &Promise{
219+
cond: sync.Cond{L: &sync.Mutex{}},
220+
t: anyCall,
221+
anyErrs: make([]error, len(promises)),
145222
}
146223

224+
// Extract the type
225+
p.resultType = firstResultType[:]
226+
147227
p.counter = int64(1)
228+
p.errCounter = int64(len(promises))
148229

149230
for i := range promises {
150231
go p.run(reflect.Value{}, nil, promises, i, nil)
@@ -225,10 +306,10 @@ func (p *Promise) thenCall(prior *Promise, functionRv reflect.Value) []reflect.V
225306
if p.err != nil {
226307
panic(errors.Wrap(p.err, "error in previous promise"))
227308
}
228-
results := functionRv.Call(prior.results)
229-
if prior.returnsError && prior.err != nil {
309+
if prior.err != nil {
230310
panic(prior.err)
231311
}
312+
results := functionRv.Call(prior.results)
232313
return results
233314
}
234315

@@ -322,6 +403,11 @@ func (p *Promise) run(functionRv reflect.Value, prior *Promise, priors []*Promis
322403
}
323404
case anyCall:
324405
results = p.anyCall(priors, index)
406+
if results == nil {
407+
return
408+
}
409+
case raceCall:
410+
results = p.raceCall(priors, index)
325411
default:
326412
panic("unexpected call type")
327413
}
@@ -417,7 +503,7 @@ func (p *Promise) Wait(out ...interface{}) error {
417503
p.cond.L.Unlock()
418504

419505
if p.err != nil {
420-
return errors.Wrap(p.err, "panic() during promise execution")
506+
return errors.Wrap(p.err, "error during promise execution")
421507
}
422508

423509
var outRvs []reflect.Value

promises_test.go

+121-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package promise
22

33
import (
44
"errors"
5-
"github.com/stretchr/testify/require"
5+
"fmt"
66
"testing"
77
"time"
8+
9+
"github.com/stretchr/testify/require"
810
)
911

1012
func TestPromiseResolution(t *testing.T) {
@@ -277,3 +279,121 @@ func TestErrorReturnExitsEarly(t *testing.T) {
277279
close(blocker)
278280
require.Error(t, err)
279281
}
282+
283+
func TestPromiseRaceSucceedsIfOneSucceeds(t *testing.T) {
284+
sleepThenPanic := func() string {
285+
time.Sleep(100 * time.Millisecond)
286+
panic("failed")
287+
return ""
288+
}
289+
290+
sleepThenErr := func() (string, error) {
291+
time.Sleep(100 * time.Millisecond)
292+
return "", fmt.Errorf("err")
293+
}
294+
295+
success := func() string {
296+
return "success"
297+
}
298+
299+
result := Race(New(sleepThenErr), New(sleepThenPanic), New(success))
300+
var retval string
301+
err := result.Wait(&retval)
302+
require.NoError(t, err)
303+
require.Equal(t, "success", retval)
304+
}
305+
306+
func TestPromiseRaceFailsIfOneErrors(t *testing.T) {
307+
sleepThenPanic := func() string {
308+
time.Sleep(100 * time.Millisecond)
309+
panic("failed")
310+
return ""
311+
}
312+
313+
returnError := func() (string, error) {
314+
return "", fmt.Errorf("err")
315+
}
316+
317+
sleepThenSuccess := func() string {
318+
time.Sleep(100 * time.Millisecond)
319+
return "success"
320+
}
321+
322+
result := Race(New(returnError), New(sleepThenPanic), New(sleepThenSuccess))
323+
var retval string
324+
err := result.Wait(&retval)
325+
require.Error(t, err)
326+
require.Contains(t, err.Error(), "err")
327+
require.Equal(t, "", retval)
328+
}
329+
330+
func TestPromiseRaceFailsIfOnePanics(t *testing.T) {
331+
justPanic := func() string {
332+
panic("failed")
333+
return ""
334+
}
335+
336+
sleepThenError := func() (string, error) {
337+
time.Sleep(100 * time.Millisecond)
338+
return "", fmt.Errorf("err")
339+
}
340+
341+
sleepThenSuccess := func() string {
342+
time.Sleep(100 * time.Millisecond)
343+
return "success"
344+
}
345+
346+
result := Race(New(sleepThenError), New(justPanic), New(sleepThenSuccess))
347+
var retval string
348+
err := result.Wait(&retval)
349+
require.Error(t, err)
350+
require.Contains(t, err.Error(), "failed")
351+
require.Equal(t, "", retval)
352+
}
353+
354+
func TestPromiseAnySucceedsIfOneSucceeds(t *testing.T) {
355+
sleepThenPanic := func() string {
356+
time.Sleep(100 * time.Millisecond)
357+
panic("failed")
358+
return ""
359+
}
360+
361+
sleepThenErr := func() (string, error) {
362+
time.Sleep(100 * time.Millisecond)
363+
return "", fmt.Errorf("err")
364+
}
365+
366+
success := func() string {
367+
return "success"
368+
}
369+
370+
result := Race(New(sleepThenErr), New(sleepThenPanic), New(success))
371+
var retval string
372+
err := result.Wait(&retval)
373+
require.NoError(t, err)
374+
require.Equal(t, "success", retval)
375+
}
376+
377+
func TestPromiseAnyFailsIfAllFail(t *testing.T) {
378+
sleepThenPanic := func() string {
379+
time.Sleep(100 * time.Millisecond)
380+
panic("failed")
381+
return ""
382+
}
383+
384+
returnError := func() (string, error) {
385+
return "", fmt.Errorf("err")
386+
}
387+
388+
sleepThenSuccess := func() string {
389+
time.Sleep(100 * time.Millisecond)
390+
return "success"
391+
}
392+
393+
result := Race(New(returnError), New(sleepThenPanic), New(sleepThenSuccess))
394+
var retval string
395+
err := result.Wait(&retval)
396+
require.Error(t, err)
397+
require.Contains(t, err.Error(), "err")
398+
require.Equal(t, "", retval)
399+
}

vendor/modules.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ github.com/pkg/errors
55
# github.com/pmezard/go-difflib v1.0.0
66
github.com/pmezard/go-difflib/difflib
77
# github.com/stretchr/testify v1.4.0
8-
github.com/stretchr/testify/require
98
github.com/stretchr/testify/assert
9+
github.com/stretchr/testify/require
1010
# gopkg.in/yaml.v2 v2.2.2
1111
gopkg.in/yaml.v2

0 commit comments

Comments
 (0)