├── cmd
├── main_test.go
└── main.go
├── .gitignore
├── .vscode
├── settings.json
└── tasks.json
├── Makefile
├── go.mod
├── .github
├── dependabot.yml
└── workflows
│ └── go.yaml
├── errgroup_test.go
├── waitgroup_test.go
├── waitgroup.go
├── errgroup.go
├── go.sum
├── techstack.yml
├── techstack.md
├── README.md
└── LICENSE
/cmd/main_test.go:
--------------------------------------------------------------------------------
1 | package main_test
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /go-waitgroup
2 | .DS_Store
3 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.exclude": {
3 | "go-waitgroup": true
4 | }
5 | }
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | go build -o go-waitgroup github.com/pieterclaerhout/go-waitgroup/cmd
3 |
4 | run: build
5 | ./go-waitgroup
6 |
7 | test:
8 | go test -cover ./...
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/pieterclaerhout/go-waitgroup
2 |
3 | go 1.13
4 |
5 | require (
6 | github.com/pkg/errors v0.9.1
7 | github.com/stretchr/testify v1.11.1
8 | )
9 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: gomod
4 | directory: /
5 | schedule:
6 | interval: weekly
7 | assignees:
8 | - "pieterclaerhout"
9 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "go-waitgroup | build",
6 | "type": "shell",
7 | "command": "make build",
8 | "group": "build",
9 | "problemMatcher": ["$go"]
10 | },
11 | {
12 | "label": "go-waitgroup | run",
13 | "type": "shell",
14 | "command": "make run",
15 | "group": "build",
16 | "problemMatcher": ["$go"]
17 | },
18 | {
19 | "label": "go-waitgroup | test",
20 | "type": "shell",
21 | "command": "make test",
22 | "group": "build",
23 | "problemMatcher": ["$go"]
24 | }
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/.github/workflows/go.yaml:
--------------------------------------------------------------------------------
1 | name: Go
2 | on: [push]
3 | jobs:
4 |
5 | build:
6 | name: Test Build
7 | runs-on: ubuntu-latest
8 | steps:
9 |
10 | - name: Set up Go 1.19
11 | uses: actions/setup-go@v3
12 | with:
13 | go-version: 1.19
14 | id: go
15 |
16 | - name: Check out code into the Go module directory
17 | uses: actions/checkout@v3
18 |
19 | - name: Restore Cache
20 | uses: actions/cache@v3
21 | id: cache
22 | with:
23 | path: ~/go/pkg
24 | key: ${{ runner.os }}-${{ hashFiles('**/go.sum') }}
25 |
26 | - name: Get dependencies
27 | run: |
28 | go get -v -t -d ./...
29 |
30 | - name: Test
31 | run: |
32 | make test
33 |
34 | - name: Build
35 | run: |
36 | make build
37 |
--------------------------------------------------------------------------------
/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 |
7 | "github.com/pieterclaerhout/go-waitgroup"
8 | )
9 |
10 | func main() {
11 |
12 | urls := []string{
13 | "https://www.easyjet.com/",
14 | "https://www.skyscanner.de/",
15 | "https://www.ryanair.com",
16 | "https://wizzair.com/",
17 | "https://www.swiss.com/",
18 | }
19 |
20 | wg := waitgroup.NewWaitGroup(3)
21 |
22 | for _, url := range urls {
23 |
24 | urlToCheck := url
25 | wg.Add(func() {
26 | fmt.Printf("%s: checking\n", urlToCheck)
27 | res, err := http.Get(urlToCheck)
28 | if err != nil {
29 | fmt.Printf("Error: %v\n", err)
30 | } else {
31 | defer res.Body.Close()
32 | fmt.Printf("%s: result: %v\n", urlToCheck, err)
33 | }
34 | })
35 |
36 | }
37 |
38 | wg.Wait()
39 | fmt.Println("Finished")
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/errgroup_test.go:
--------------------------------------------------------------------------------
1 | package waitgroup_test
2 |
3 | import (
4 | "context"
5 | "testing"
6 |
7 | "github.com/pkg/errors"
8 | "github.com/stretchr/testify/assert"
9 |
10 | "github.com/pieterclaerhout/go-waitgroup"
11 | )
12 |
13 | func TestErrorGroupAdd(t *testing.T) {
14 |
15 | type test struct {
16 | name string
17 | size int
18 | }
19 |
20 | var tests = []test{
21 | {"single", 1},
22 | }
23 |
24 | for _, tc := range tests {
25 | t.Run(tc.name, func(t *testing.T) {
26 |
27 | ctx := context.Background()
28 |
29 | wg, _ := waitgroup.NewErrorGroup(ctx, tc.size)
30 | assert.NotNil(t, wg)
31 |
32 | wg.Add(func() error {
33 | return nil
34 | })
35 |
36 | wg.Add(func() error {
37 | return errors.New("An error occurred")
38 | })
39 |
40 | err := wg.Wait()
41 | assert.Error(t, err)
42 |
43 | })
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/waitgroup_test.go:
--------------------------------------------------------------------------------
1 | package waitgroup_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 |
8 | "github.com/pieterclaerhout/go-waitgroup"
9 | )
10 |
11 | func TestWaitGroup(t *testing.T) {
12 |
13 | type test struct {
14 | name string
15 | size int
16 | }
17 |
18 | var tests = []test{
19 | {"single", 1},
20 | }
21 |
22 | for _, tc := range tests {
23 | t.Run(tc.name, func(t *testing.T) {
24 |
25 | wg := waitgroup.NewWaitGroup(tc.size)
26 | assert.NotNil(t, wg)
27 |
28 | assert.Zero(t, wg.PendingCount(), "pending-before")
29 |
30 | wg.BlockAdd()
31 | go func() {
32 | defer wg.Done()
33 | }()
34 |
35 | assert.EqualValues(t, 1, wg.PendingCount(), "pending-during")
36 |
37 | wg.Wait()
38 |
39 | assert.Zero(t, wg.PendingCount(), "pending-after")
40 |
41 | })
42 | }
43 |
44 | }
45 |
46 | func TestWaitGroupAdd(t *testing.T) {
47 |
48 | type test struct {
49 | name string
50 | size int
51 | }
52 |
53 | var tests = []test{
54 | {"single", 1},
55 | }
56 |
57 | for _, tc := range tests {
58 | t.Run(tc.name, func(t *testing.T) {
59 |
60 | wg := waitgroup.NewWaitGroup(tc.size)
61 | assert.NotNil(t, wg)
62 |
63 | wg.Add(func() {
64 | // Doing nothing here
65 | })
66 |
67 | wg.Wait()
68 |
69 | })
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/waitgroup.go:
--------------------------------------------------------------------------------
1 | package waitgroup
2 |
3 | import (
4 | "sync"
5 | )
6 |
7 | // WaitGroup implements a simple goruntine pool.
8 | type WaitGroup struct {
9 | size int
10 | pool chan byte
11 | waitCount int64
12 | waitGroup sync.WaitGroup
13 | }
14 |
15 | // NewWaitGroup creates a waitgroup with a specific size (the maximum number of
16 | // goroutines to run at the same time). If you use -1 as the size, all items
17 | // will run concurrently (just like a normal sync.WaitGroup)
18 | func NewWaitGroup(size int) *WaitGroup {
19 | wg := &WaitGroup{
20 | size: size,
21 | }
22 | if size > 0 {
23 | wg.pool = make(chan byte, size)
24 | }
25 | return wg
26 | }
27 |
28 | // Add add the function close to the waitgroup
29 | func (wg *WaitGroup) Add(closures ...func()) {
30 | for _, c := range closures {
31 | closure := c
32 | wg.BlockAdd()
33 | go func() {
34 | defer wg.Done()
35 | closure()
36 | }()
37 | }
38 | }
39 |
40 | // BlockAdd pushes ‘one’ into the group. Blocks if the group is full.
41 | func (wg *WaitGroup) BlockAdd() {
42 | if wg.size > 0 {
43 | wg.pool <- 1
44 | }
45 | wg.waitGroup.Add(1)
46 | }
47 |
48 | // Done pops ‘one’ out the group.
49 | func (wg *WaitGroup) Done() {
50 | if wg.size > 0 {
51 | <-wg.pool
52 | }
53 | wg.waitGroup.Done()
54 | }
55 |
56 | // Wait waiting the group empty
57 | func (wg *WaitGroup) Wait() {
58 | wg.waitGroup.Wait()
59 | }
60 |
61 | // PendingCount returns the number of pending operations
62 | func (wg *WaitGroup) PendingCount() int64 {
63 | return int64(len(wg.pool))
64 | }
65 |
--------------------------------------------------------------------------------
/errgroup.go:
--------------------------------------------------------------------------------
1 | package waitgroup
2 |
3 | import (
4 | "context"
5 | "sync"
6 | )
7 |
8 | // An ErrorGroup is a collection of goroutines working on subtasks that are part of
9 | // the same overall task.
10 | type ErrorGroup struct {
11 | size int
12 | pool chan byte
13 |
14 | cancel func()
15 |
16 | wg sync.WaitGroup
17 |
18 | errOnce sync.Once
19 | err error
20 | }
21 |
22 | // NewErrorGroup returns a new ErrorGroup instance
23 | func NewErrorGroup(ctx context.Context, size int) (*ErrorGroup, context.Context) {
24 | ctx, cancel := context.WithCancel(ctx)
25 | wg := &ErrorGroup{
26 | size: size,
27 | cancel: cancel,
28 | }
29 | if size > 0 {
30 | wg.pool = make(chan byte, size)
31 | }
32 | return wg, ctx
33 | }
34 |
35 | // Wait blocks until all function calls from the Go method have returned, then
36 | // returns the first non-nil error (if any) from them.
37 | func (g *ErrorGroup) Wait() error {
38 | g.wg.Wait()
39 | if g.cancel != nil {
40 | g.cancel()
41 | }
42 | return g.err
43 | }
44 |
45 | // Add calls the given function in a new goroutine.
46 | //
47 | // The first call to return a non-nil error cancels the group; its error will be
48 | // returned by Wait.
49 | func (g *ErrorGroup) Add(closures ...func() error) {
50 |
51 | for _, c := range closures {
52 |
53 | closure := c
54 |
55 | if g.size > 0 {
56 | g.pool <- 1
57 | }
58 | g.wg.Add(1)
59 |
60 | go func() {
61 | defer func() {
62 | if g.size > 0 {
63 | <-g.pool
64 | }
65 | g.wg.Done()
66 | }()
67 |
68 | if err := closure(); err != nil {
69 | g.errOnce.Do(func() {
70 | g.err = err
71 | if g.cancel != nil {
72 | g.cancel()
73 | }
74 | })
75 | }
76 | }()
77 |
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
5 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
6 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
7 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
8 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
9 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
10 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
11 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
12 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
13 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
14 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
15 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
16 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
17 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
18 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
19 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
20 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
21 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
22 |
--------------------------------------------------------------------------------
/techstack.yml:
--------------------------------------------------------------------------------
1 | repo_name: pieterclaerhout/go-waitgroup
2 | report_id: 315e1142ec1f3e3b468fe2608e3a0b7b
3 | repo_type: Public
4 | timestamp: '2023-11-09T11:13:03+00:00'
5 | requested_by: pieterclaerhout
6 | provider: github
7 | branch: master
8 | detected_tools_count: 5
9 | tools:
10 | - name: Golang
11 | description: An open source programming language that makes it easy to build simple,
12 | reliable, and efficient software
13 | website_url: http://golang.org/
14 | license: BSD-3-Clause
15 | open_source: true
16 | hosted_saas: false
17 | category: Languages & Frameworks
18 | sub_category: Languages
19 | image_url: https://img.stackshare.io/service/1005/O6AczwfV_400x400.png
20 | detection_source: Repo Metadata
21 | - name: Git
22 | description: Fast, scalable, distributed revision control system
23 | website_url: http://git-scm.com/
24 | open_source: true
25 | hosted_saas: false
26 | category: Build, Test, Deploy
27 | sub_category: Version Control System
28 | image_url: https://img.stackshare.io/service/1046/git.png
29 | detection_source: Repo Metadata
30 | - name: GitHub Actions
31 | description: Automate your workflow from idea to production
32 | website_url: https://github.com/features/actions
33 | open_source: false
34 | hosted_saas: true
35 | category: Build, Test, Deploy
36 | sub_category: Continuous Integration
37 | image_url: https://img.stackshare.io/service/11563/actions.png
38 | detection_source: ".github/workflows/go.yaml"
39 | last_updated_by: Pieter Claerhout
40 | last_updated_on: 2022-10-16 13:43:11.000000000 Z
41 | - name: Testify
42 | description: A toolkit with common assertions and mocks that plays nicely with the
43 | standard library
44 | website_url: https://github.com/stretchr/testify
45 | license: MIT
46 | open_source: true
47 | hosted_saas: false
48 | category: Build, Test, Deploy
49 | sub_category: Go Testing
50 | image_url: https://img.stackshare.io/service/8695/stretchr.png
51 | detection_source: go.mod
52 | last_updated_by: dependabot[bot]
53 | last_updated_on: 2023-06-05 22:59:55.000000000 Z
54 | - name: Visual Studio Code
55 | description: Build and debug modern web and cloud applications, by Microsoft
56 | website_url: https://code.visualstudio.com/
57 | license: MIT
58 | open_source: true
59 | hosted_saas: false
60 | category: Build, Test, Deploy
61 | sub_category: Text Editor
62 | image_url: https://img.stackshare.io/service/4202/Visual_Studio_Code_logo.png
63 | detection_source: ".vscode/tasks.json"
64 | last_updated_by: Pieter Claerhout
65 | last_updated_on: 2020-02-21 08:57:58.000000000 Z
66 |
--------------------------------------------------------------------------------
/techstack.md:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 | # Tech Stack File
27 |  [pieterclaerhout/go-waitgroup](https://github.com/pieterclaerhout/go-waitgroup)
28 |
29 | |5
Tools used|11/09/23
Report generated|
30 | |------|------|
31 |
32 |
33 | ##
Languages (1)
34 |
35 |
36 |
37 |
38 | Golang
39 |
40 |
41 | |
42 |
43 |
44 |
45 |
46 | ##
DevOps (4)
47 |
48 |
49 |
50 |
51 | Git
52 |
53 |
54 | |
55 |
56 |
57 |
58 |
59 | GitHub Actions
60 |
61 |
62 | |
63 |
64 |
65 |
66 |
67 | Testify
68 |
69 |
70 | |
71 |
72 |
73 |
74 |
75 | Visual Studio Code
76 |
77 |
78 | |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | Generated via [Stack File](https://github.com/apps/stack-file)
87 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # go-waitgroup
2 |
3 | [](https://github.com/pieterclaerhout/go-waitgroup/actions?query=workflow%3AGo)
4 | [](https://goreportcard.com/report/github.com/pieterclaerhout/go-waitgroup)
5 | [](http://godoc.org/github.com/pieterclaerhout/go-waitgroup)
6 | [](https://github.com/pieterclaerhout/go-waitgroup/raw/master/LICENSE)
7 | [](https://badge.fury.io/gh/pieterclaerhout%2Fgo-waitgroup)
8 | [](https://github.com/pieterclaerhout/go-waitgroup/issues)
9 |
10 | ## How to use
11 |
12 | An package that allows you to use the constructs of a [`sync.WaitGroup`](https://golang.org/pkg/sync/#WaitGroup) to
13 | create a pool of goroutines and control the concurrency.
14 |
15 | Using it is just like a normal [`sync.WaitGroup`](https://golang.org/pkg/sync/#WaitGroup). The only difference is the initialisation. When you use `waitgroup.NewWaitGroup`, you have the option to specify it's size.
16 |
17 | Any `int` which is bigger than `0` will limit the number of concurrent goroutines. If you specify `-1` or `0`, all goroutines will run at once (just like a plain [`sync.WaitGroup`](https://golang.org/pkg/sync/#WaitGroup)).
18 |
19 | ```go
20 | package main
21 |
22 | import (
23 | "fmt"
24 | "net/http"
25 |
26 | "github.com/pieterclaerhout/go-waitgroup"
27 | )
28 |
29 | func main() {
30 |
31 | urls := []string{
32 | "https://www.easyjet.com/",
33 | "https://www.skyscanner.de/",
34 | "https://www.ryanair.com",
35 | "https://wizzair.com/",
36 | "https://www.swiss.com/",
37 | }
38 |
39 | wg := waitgroup.NewWaitGroup(3)
40 |
41 | for _, url := range urls {
42 | wg.BlockAdd()
43 | go func(url string) {
44 | defer wg.Done()
45 | fmt.Printf("%s: checking\n", url)
46 | res, err := http.Get(url)
47 | if err != nil {
48 | fmt.Println("Error: %v")
49 | } else {
50 | defer res.Body.Close()
51 | fmt.Printf("%s: result: %v\n", url, err)
52 | }
53 | }(url)
54 | }
55 |
56 | wg.Wait()
57 | fmt.Println("Finished")
58 |
59 | }
60 | ```
61 |
62 | ## Using closures
63 |
64 | There is also a way to use function closures to make it even more readable:
65 |
66 | ```go
67 | package main
68 |
69 | import (
70 | "fmt"
71 | "net/http"
72 |
73 | "github.com/pieterclaerhout/go-waitgroup"
74 | )
75 |
76 | func main() {
77 |
78 | urls := []string{
79 | "https://www.easyjet.com/",
80 | "https://www.skyscanner.de/",
81 | "https://www.ryanair.com",
82 | "https://wizzair.com/",
83 | "https://www.swiss.com/",
84 | }
85 |
86 | wg := waitgroup.NewWaitGroup(3)
87 |
88 | for _, url := range urls {
89 |
90 | urlToCheck := url
91 | wg.Add(func() {
92 | fmt.Printf("%s: checking\n", urlToCheck)
93 | res, err := http.Get(urlToCheck)
94 | if err != nil {
95 | fmt.Println("Error: %v")
96 | } else {
97 | defer res.Body.Close()
98 | fmt.Printf("%s: result: %v\n", urlToCheck, err)
99 | }
100 | })
101 |
102 | }
103 |
104 | wg.Wait()
105 | fmt.Println("Finished")
106 |
107 | }
108 | ```
109 |
110 | ## Handling errors
111 |
112 | If you want to handle errors, there is also an `ErrorGroup`. This uses the same principles as a normal `WaitGroup` with a small twist.
113 |
114 | First of all, you can only add functions which returns just an `error`.
115 |
116 | Second, as soon as one of the queued items fail, the rest will be cancelled:
117 |
118 | ```go
119 | package main
120 |
121 | import (
122 | "context"
123 | "fmt"
124 | "os"
125 |
126 | "github.com/pieterclaerhout/go-waitgroup"
127 | )
128 |
129 | func main() {
130 |
131 | ctx := context.Background()
132 |
133 | wg, ctx := waitgroup.NewErrorGroup(ctx, tc.size)
134 | if err != nil {
135 | fmt.Println("Error: %v")
136 | os.Exit(1)
137 | }
138 |
139 | wg.Add(func() error {
140 | return nil
141 | })
142 |
143 | wg.Add(func() error {
144 | return errors.New("An error occurred")
145 | })
146 |
147 | if err := wg.Wait(); err != nil {
148 | fmt.Println("Error: %v")
149 | os.Exit(1)
150 | }
151 |
152 | }
153 | ```
154 |
155 | You can also add multiple functions in one step:
156 |
157 | ```go
158 | package main
159 |
160 | import (
161 | "context"
162 | "errors"
163 | "fmt"
164 | "os"
165 |
166 | "github.com/pieterclaerhout/go-waitgroup"
167 | )
168 |
169 | func main() {
170 |
171 | ctx := context.Background()
172 |
173 | wg, ctx := waitgroup.NewErrorGroup(ctx, tc.size)
174 | if err != nil {
175 | fmt.Println("Error: %v")
176 | os.Exit(1)
177 | }
178 |
179 | wg.Add(
180 | func() error {
181 | return nil
182 | },
183 | func() error {
184 | return errors.New("An error occurred")
185 | },
186 | )
187 |
188 | if err := wg.Wait(); err != nil {
189 | fmt.Println("Error: %v")
190 | os.Exit(1)
191 | }
192 |
193 | }
194 | ```
195 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright 2019 Pieter Claerhout
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
--------------------------------------------------------------------------------