Skip to content

Commit 2d4e806

Browse files
authored
Merge pull request #6 from reugn/develop
v0.5.0
2 parents bb6e6f3 + 22ee430 commit 2d4e806

File tree

14 files changed

+272
-59
lines changed

14 files changed

+272
-59
lines changed

.github/workflows/build.yml

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,30 @@
11
name: Build
22

3-
on: [push, pull_request]
3+
on:
4+
push:
5+
branches:
6+
- '**'
7+
pull_request:
8+
branches:
9+
- master
410

511
jobs:
612
build:
713
runs-on: ubuntu-latest
814
strategy:
915
matrix:
10-
go-version: [1.18.x]
16+
go-version: [1.19.x]
1117
steps:
1218
- name: Setup Go
13-
uses: actions/setup-go@v2
19+
uses: actions/setup-go@v3
1420
with:
1521
go-version: ${{ matrix.go-version }}
22+
1623
- name: Checkout code
17-
uses: actions/checkout@v2
24+
uses: actions/checkout@v3
25+
1826
- name: Run coverage
1927
run: go test . -coverprofile=coverage.out -covermode=atomic
28+
2029
- name: Upload coverage to Codecov
2130
run: bash <(curl -s https://codecov.io/bash)

.github/workflows/golangci-lint.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ jobs:
1010
name: lint
1111
runs-on: ubuntu-latest
1212
steps:
13-
- uses: actions/checkout@v2
13+
- name: Checkout code
14+
uses: actions/checkout@v3
15+
1416
- name: golangci-lint
15-
uses: golangci/golangci-lint-action@v2
17+
uses: golangci/golangci-lint-action@v3

.golangci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ run:
55
linters:
66
disable-all: true
77
enable:
8-
- deadcode
98
- dupl
109
- errcheck
1110
- exportloopref
@@ -15,15 +14,16 @@ linters:
1514
- gocyclo
1615
- gofmt
1716
- goimports
17+
- gosimple
1818
- govet
1919
- ineffassign
2020
- lll
2121
- misspell
2222
- prealloc
2323
- revive
24+
- staticcheck
2425
- typecheck
2526
- unconvert
26-
- varcheck
2727

2828
issues:
2929
exclude-rules:

README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,18 @@
66
<a href="https://codecov.io/gh/reugn/async"><img src="https://codecov.io/gh/reugn/async/branch/master/graph/badge.svg"></a>
77
</div>
88
<br/>
9-
Async provides synchronization and asynchronous computation utilities for Go.
10-
11-
The implemented patterns were taken from Scala and Java.
9+
Async is a synchronization and asynchronous computation package for Go.
1210

1311
## Overview
1412
* **Future** - A placeholder object for a value that may not yet exist.
1513
* **Promise** - While futures are defined as a type of read-only placeholder object created for a result which doesn’t yet exist, a promise can be thought of as a writable, single-assignment container, which completes a future.
1614
* **Task** - A data type for controlling possibly lazy and asynchronous computations.
15+
* **WaitGroupContext** - A WaitGroup with the `context.Context` support for graceful unblocking.
1716
* **Reentrant Lock** - Mutex that allows goroutines to enter into the lock on a resource more than once.
1817
* **Optimistic Lock** - Mutex that allows optimistic reading. Could be retried or switched to RLock in case of failure. Significantly improves performance in case of frequent reads and short writes. See [benchmarks](./benchmarks/README.md).
1918

2019
## Examples
2120
Can be found in the examples directory/tests.
2221

2322
## License
24-
Licensed under the MIT License.
23+
Licensed under the MIT License.

examples/future/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99

1010
func main() {
1111
future := asyncAction()
12-
result, err := future.Get()
12+
result, err := future.Join()
1313
if err != nil {
1414
log.Fatal(err)
1515
}

future.go

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package async
22

3-
import "sync"
3+
import (
4+
"fmt"
5+
"sync"
6+
"time"
7+
)
48

59
// Future represents a value which may or may not currently be available,
610
// but will be available at some point, or an error if that value could not be made available.
@@ -13,8 +17,12 @@ type Future[T any] interface {
1317
// this Future.
1418
FlatMap(func(T) (Future[T], error)) Future[T]
1519

16-
// Get blocks until the Future is completed and returns either a result or an error.
17-
Get() (T, error)
20+
// Join blocks until the Future is completed and returns either a result or an error.
21+
Join() (T, error)
22+
23+
// Get blocks for at most the given time duration for this Future to complete
24+
// and returns either a result or an error.
25+
Get(time.Duration) (T, error)
1826

1927
// Recover handles any error that this Future might contain using a resolver function.
2028
Recover(func() (T, error)) Future[T]
@@ -49,16 +57,35 @@ func NewFuture[T any]() Future[T] {
4957
// accept blocks once, until the Future result is available.
5058
func (fut *FutureImpl[T]) accept() {
5159
fut.acceptOnce.Do(func() {
52-
sig := <-fut.done
53-
switch v := sig.(type) {
54-
case error:
55-
fut.err = v
56-
default:
57-
fut.value = v.(T)
60+
result := <-fut.done
61+
fut.setResult(result)
62+
})
63+
}
64+
65+
// acceptTimeout blocks once, until the Future result is available or until the timeout expires.
66+
func (fut *FutureImpl[T]) acceptTimeout(timeout time.Duration) {
67+
fut.acceptOnce.Do(func() {
68+
timer := time.NewTimer(timeout)
69+
defer timer.Stop()
70+
select {
71+
case result := <-fut.done:
72+
fut.setResult(result)
73+
case <-timer.C:
74+
fut.err = fmt.Errorf("Future timeout after %s", timeout)
5875
}
5976
})
6077
}
6178

79+
// setResult assigns a value to the Future instance.
80+
func (fut *FutureImpl[T]) setResult(result interface{}) {
81+
switch value := result.(type) {
82+
case error:
83+
fut.err = value
84+
default:
85+
fut.value = value.(T)
86+
}
87+
}
88+
6289
// Map creates a new Future by applying a function to the successful result of this Future
6390
// and returns the result of the function as a new Future.
6491
func (fut *FutureImpl[T]) Map(f func(T) (T, error)) Future[T] {
@@ -90,19 +117,26 @@ func (fut *FutureImpl[T]) FlatMap(f func(T) (Future[T], error)) Future[T] {
90117
var nilT T
91118
next.complete(nilT, terr)
92119
} else {
93-
next.complete(tfut.Get())
120+
next.complete(tfut.Join())
94121
}
95122
}
96123
}()
97124
return next
98125
}
99126

100-
// Get blocks until the Future is completed and returns either a result or an error.
101-
func (fut *FutureImpl[T]) Get() (T, error) {
127+
// Join blocks until the Future is completed and returns either a result or an error.
128+
func (fut *FutureImpl[T]) Join() (T, error) {
102129
fut.accept()
103130
return fut.value, fut.err
104131
}
105132

133+
// Get blocks for at most the given time duration for this Future to complete
134+
// and returns either a result or an error.
135+
func (fut *FutureImpl[T]) Get(timeout time.Duration) (T, error) {
136+
fut.acceptTimeout(timeout)
137+
return fut.value, fut.err
138+
}
139+
106140
// Recover handles any error that this Future might contain using a given resolver function.
107141
// Returns the result as a new Future.
108142
func (fut *FutureImpl[T]) Recover(f func() (T, error)) Future[T] {
@@ -125,7 +159,7 @@ func (fut *FutureImpl[T]) RecoverWith(rf Future[T]) Future[T] {
125159
go func() {
126160
fut.accept()
127161
if fut.err != nil {
128-
next.complete(rf.Get())
162+
next.complete(rf.Join())
129163
} else {
130164
next.complete(fut.value, nil)
131165
}

future_test.go

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ func TestFuture(t *testing.T) {
1414
time.Sleep(time.Millisecond * 100)
1515
p.Success(true)
1616
}()
17-
v, e := p.Future().Get()
17+
res, err := p.Future().Join()
1818

19-
internal.AssertEqual(t, v, true)
20-
internal.AssertEqual(t, e, nil)
19+
internal.AssertEqual(t, res, true)
20+
internal.AssertEqual(t, err, nil)
2121
}
2222

2323
func TestFutureUtils(t *testing.T) {
@@ -34,7 +34,7 @@ func TestFutureUtils(t *testing.T) {
3434
}()
3535
arr := []Future[int]{p1.Future(), p2.Future(), p3.Future()}
3636
res := []interface{}{1, 2, 3}
37-
futRes, _ := FutureSeq(arr).Get()
37+
futRes, _ := FutureSeq(arr).Join()
3838

3939
internal.AssertEqual(t, res, futRes)
4040
}
@@ -46,7 +46,7 @@ func TestFutureFirstCompleted(t *testing.T) {
4646
p.Success(true)
4747
}()
4848
timeout := FutureTimer[bool](time.Millisecond * 100)
49-
futRes, futErr := FutureFirstCompletedOf(p.Future(), timeout).Get()
49+
futRes, futErr := FutureFirstCompletedOf(p.Future(), timeout).Join()
5050

5151
internal.AssertEqual(t, false, futRes)
5252
if futErr == nil {
@@ -60,7 +60,7 @@ func TestFutureTransform(t *testing.T) {
6060
time.Sleep(time.Millisecond * 100)
6161
p1.Success(1)
6262
}()
63-
res, _ := p1.Future().Map(func(v int) (int, error) {
63+
future := p1.Future().Map(func(v int) (int, error) {
6464
return v + 1, nil
6565
}).FlatMap(func(v int) (Future[int], error) {
6666
nv := v + 1
@@ -69,8 +69,12 @@ func TestFutureTransform(t *testing.T) {
6969
return p2.Future(), nil
7070
}).Recover(func() (int, error) {
7171
return 5, nil
72-
}).Get()
72+
})
7373

74+
res, _ := future.Get(time.Second * 5)
75+
internal.AssertEqual(t, 3, res)
76+
77+
res, _ = future.Join()
7478
internal.AssertEqual(t, 3, res)
7579
}
7680

@@ -83,7 +87,22 @@ func TestFutureFailure(t *testing.T) {
8387
time.Sleep(time.Millisecond * 200)
8488
p2.Success(2)
8589
}()
86-
res, _ := p1.Future().RecoverWith(p2.Future()).Get()
90+
res, _ := p1.Future().RecoverWith(p2.Future()).Join()
8791

8892
internal.AssertEqual(t, 2, res)
8993
}
94+
95+
func TestFutureTimeout(t *testing.T) {
96+
p := NewPromise[bool]()
97+
go func() {
98+
time.Sleep(time.Millisecond * 200)
99+
p.Success(true)
100+
}()
101+
future := p.Future()
102+
103+
_, err := future.Get(time.Millisecond * 50)
104+
internal.AssertErrorContains(t, err, "timeout")
105+
106+
_, err = future.Join()
107+
internal.AssertErrorContains(t, err, "timeout")
108+
}

future_utils.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ func FutureSeq[T any](futures []Future[T]) Future[[]interface{}] {
1010
next := NewFuture[[]interface{}]()
1111
go func() {
1212
seq := make([]interface{}, len(futures))
13-
for i, f := range futures {
14-
v, e := f.Get()
15-
if e != nil {
16-
seq[i] = e
13+
for i, future := range futures {
14+
res, err := future.Join()
15+
if err != nil {
16+
seq[i] = err
1717
} else {
18-
seq[i] = v
18+
seq[i] = res
1919
}
2020
}
2121
next.complete(seq, nil)
@@ -30,7 +30,7 @@ func FutureFirstCompletedOf[T any](futures ...Future[T]) Future[T] {
3030
go func() {
3131
for _, f := range futures {
3232
go func(future Future[T]) {
33-
next.complete(future.Get())
33+
next.complete(future.Join())
3434
}(f)
3535
}
3636
}()
@@ -45,7 +45,7 @@ func FutureTimer[T any](d time.Duration) Future[T] {
4545
timer := time.NewTimer(d)
4646
<-timer.C
4747
var nilT T
48-
next.complete(nilT, fmt.Errorf("FutureTimer %v timeout", d))
48+
next.complete(nilT, fmt.Errorf("FutureTimer %s timeout", d))
4949
}()
5050
return next
5151
}

internal/test_utils.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,32 @@ package internal
22

33
import (
44
"reflect"
5+
"strings"
56
"testing"
67
)
78

89
// AssertEqual verifies equality of two objects.
9-
func AssertEqual[T any](t *testing.T, a T, b T) {
10+
func AssertEqual[T any](t *testing.T, a, b T) {
1011
if !reflect.DeepEqual(a, b) {
1112
t.Fatalf("%v != %v", a, b)
1213
}
1314
}
15+
16+
// AssertErrorContains checks whether the given error contains the specified string.
17+
func AssertErrorContains(t *testing.T, err error, str string) {
18+
if err == nil {
19+
t.Fatalf("Error is nil")
20+
} else if !strings.Contains(err.Error(), str) {
21+
t.Fatalf("Error doen't contain string: %s", str)
22+
}
23+
}
24+
25+
// AssertPanic checks whether the given function panics.
26+
func AssertPanic(t *testing.T, f func()) {
27+
defer func() {
28+
if r := recover(); r == nil {
29+
t.Errorf("The function did not panic")
30+
}
31+
}()
32+
f()
33+
}

0 commit comments

Comments
 (0)