├── .gitignore ├── go.mod ├── option.go ├── util_test.go ├── .travis.yml ├── go.sum ├── exec_test.go ├── actuator_test.go ├── util.go ├── pooled_actuator_test.go ├── LICENCE ├── actuator.go ├── README.md ├── common_test.go ├── pooled_actuator.go └── exec.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendor 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ITcathyh/conexec 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/go-playground/assert/v2 v2.0.1 7 | github.com/panjf2000/ants/v2 v2.2.2 8 | ) 9 | -------------------------------------------------------------------------------- /option.go: -------------------------------------------------------------------------------- 1 | package conexec 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // Options use to init actuator 8 | type Options struct { 9 | TimeOut *time.Duration 10 | } 11 | -------------------------------------------------------------------------------- /util_test.go: -------------------------------------------------------------------------------- 1 | package conexec 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | . "github.com/go-playground/assert/v2" 8 | ) 9 | 10 | func TestDurationPtr(t *testing.T) { 11 | timeout := time.Minute 12 | Equal(t, timeout, *DurationPtr(timeout)) 13 | } 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.13.x 5 | 6 | go_import_path: github.com/ITcathyh/conexec 7 | 8 | before_install: 9 | - go get -t -v ./... 10 | 11 | script: 12 | - go test -race -coverprofile=coverage.txt -covermode=atomic 13 | 14 | after_success: 15 | - bash <(curl -s https://codecov.io/bash) 16 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= 2 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 3 | github.com/panjf2000/ants/v2 v2.2.2 h1:TWzusBjq/IflXhy+/S6u5wmMLCBdJnB9tPIx9Zmhvok= 4 | github.com/panjf2000/ants/v2 v2.2.2/go.mod h1:1GFm8bV8nyCQvU5K4WvBCTG1/YBFOD2VzjffD8fV55A= 5 | -------------------------------------------------------------------------------- /exec_test.go: -------------------------------------------------------------------------------- 1 | package conexec 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | 8 | . "github.com/go-playground/assert/v2" 9 | ) 10 | 11 | func TestExec(t *testing.T) { 12 | Equal(t, Exec(getTasks()...), true) 13 | tasks, _ := getErrorTask() 14 | Equal(t, Exec(tasks...), false) 15 | Equal(t, Exec(getPanicTask()), false) 16 | Equal(t, Exec(), true) 17 | } 18 | 19 | func TestExecWithError(t *testing.T) { 20 | Equal(t, ExecWithError(getTasks()...), nil) 21 | err := fmt.Errorf("TestErr") 22 | tasks, _ := getErrorTask() 23 | Equal(t, ExecWithError(tasks...), err) 24 | Equal(t, strings.Contains(ExecWithError(getPanicTask()).Error(), "panic"), true) 25 | Equal(t, ExecWithError(), nil) 26 | } 27 | -------------------------------------------------------------------------------- /actuator_test.go: -------------------------------------------------------------------------------- 1 | package conexec 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestTimeOut(t *testing.T) { 9 | timeout := time.Millisecond * 50 10 | opt := &Options{TimeOut: &timeout} 11 | c := NewActuator(opt) 12 | testTimeout(t, c) 13 | } 14 | 15 | func TestError(t *testing.T) { 16 | timeout := time.Second 17 | opt := &Options{TimeOut: &timeout} 18 | c := NewActuator(opt) 19 | testError(t, c) 20 | } 21 | 22 | func TestNormal(t *testing.T) { 23 | c := NewActuator() 24 | testNormal(t, c) 25 | 26 | timeout := time.Minute 27 | opt := &Options{TimeOut: &timeout} 28 | c = NewActuator(opt) 29 | testNormal(t, c) 30 | } 31 | 32 | func TestEmpty(t *testing.T) { 33 | c := NewActuator() 34 | testEmpty(t, c) 35 | } 36 | 37 | func TestPanic(t *testing.T) { 38 | c := NewActuator() 39 | testPanic(t, c) 40 | } 41 | 42 | func TestManyError(t *testing.T) { 43 | timeout := time.Second 44 | opt := &Options{TimeOut: &timeout} 45 | c := NewActuator(opt) 46 | testManyError(t, c) 47 | } 48 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package conexec 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "runtime/debug" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | // DurationPtr helps to make a duration ptr 12 | func DurationPtr(t time.Duration) *time.Duration { 13 | return &t 14 | } 15 | 16 | // wrapperTask will wrapper the task in order to notice execution result 17 | // to the main process 18 | func wrapperTask(ctx context.Context, task Task, 19 | wg *sync.WaitGroup, resChan chan error) func() { 20 | return func() { 21 | defer func() { 22 | if r := recover(); r != nil { 23 | err := fmt.Errorf("conexec panic:%v\n%s", r, string(debug.Stack())) 24 | resChan <- err 25 | } 26 | 27 | wg.Done() 28 | }() 29 | 30 | select { 31 | case <-ctx.Done(): 32 | return // fast return 33 | case resChan <- task(): 34 | } 35 | } 36 | } 37 | 38 | // setOptions set the options for actuator 39 | func setOptions(c TimedActuator, options ...*Options) { 40 | if options == nil || len(options) == 0 || options[0] == nil { 41 | return 42 | } 43 | 44 | opt := options[0] 45 | if opt.TimeOut != nil { 46 | c.setTimeout(opt.TimeOut) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /pooled_actuator_test.go: -------------------------------------------------------------------------------- 1 | package conexec 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/panjf2000/ants/v2" 8 | ) 9 | 10 | func TestPooledTimeOut(t *testing.T) { 11 | timeout := time.Millisecond * 50 12 | opt := &Options{TimeOut: &timeout} 13 | 14 | c := NewPooledActuator(5, opt) 15 | testTimeout(t, c) 16 | c = NewPooledActuator(-1, opt) 17 | testTimeout(t, c) 18 | } 19 | 20 | func TestPooledError(t *testing.T) { 21 | timeout := time.Second 22 | opt := &Options{TimeOut: &timeout} 23 | 24 | c := NewPooledActuator(5, opt) 25 | testError(t, c) 26 | } 27 | 28 | func TestPooledNormal(t *testing.T) { 29 | c := NewPooledActuator(5) 30 | testNormal(t, c) 31 | 32 | timeout := time.Minute 33 | opt := &Options{TimeOut: &timeout} 34 | c = NewPooledActuator(5, opt) 35 | testNormal(t, c) 36 | 37 | c.Release() 38 | c = &PooledActuator{} 39 | testNormal(t, c) 40 | } 41 | 42 | func TestPooledEmpty(t *testing.T) { 43 | c := NewPooledActuator(5) 44 | testEmpty(t, c) 45 | } 46 | 47 | func TestPooledPanic(t *testing.T) { 48 | c := NewPooledActuator(5) 49 | testPanic(t, c) 50 | } 51 | 52 | func TestWithPool(t *testing.T) { 53 | pool, _ := ants.NewPool(5) 54 | c := NewPooledActuator(5).WithPool(pool) 55 | testNormal(t, c) 56 | testError(t, c) 57 | } 58 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019, ITcathyh 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11 | 12 | -------------------------------------------------------------------------------- /actuator.go: -------------------------------------------------------------------------------- 1 | package conexec 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | // BaseActuator is the actuator interface 10 | type BaseActuator interface { 11 | Exec(tasks ...Task) error 12 | ExecWithContext(ctx context.Context, tasks ...Task) error 13 | } 14 | 15 | // TimedActuator is the actuator interface within timeout method 16 | type TimedActuator interface { 17 | BaseActuator 18 | GetTimeout() *time.Duration 19 | setTimeout(timeout *time.Duration) 20 | } 21 | 22 | // ErrorTimeOut is the error when executes tasks timeout 23 | var ErrorTimeOut = fmt.Errorf("TimeOut") 24 | 25 | // Task Type 26 | type Task func() error 27 | 28 | // Actuator is the base struct 29 | type Actuator struct { 30 | timeout *time.Duration 31 | } 32 | 33 | // NewActuator creates an Actuator instance 34 | func NewActuator(opt ...*Options) *Actuator { 35 | c := &Actuator{} 36 | setOptions(c, opt...) 37 | return c 38 | } 39 | 40 | // Exec is used to run tasks concurrently 41 | func (c *Actuator) Exec(tasks ...Task) error { 42 | return c.ExecWithContext(context.Background(), tasks...) 43 | } 44 | 45 | // ExecWithContext is used to run tasks concurrently 46 | // Return nil when tasks are all completed successfully, 47 | // or return error when some exception happen such as timeout 48 | func (c *Actuator) ExecWithContext(ctx context.Context, tasks ...Task) error { 49 | return execTasks(ctx, c, simplyRun, tasks...) 50 | } 51 | 52 | // GetTimeout return the timeout set before 53 | func (c *Actuator) GetTimeout() *time.Duration { 54 | return c.timeout 55 | } 56 | 57 | // setTimeout sets the timeout 58 | func (c *Actuator) setTimeout(timeout *time.Duration) { 59 | c.timeout = timeout 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | [![Build Status](https://travis-ci.org/ITcathyh/conexec.svg?branch=master)](https://travis-ci.org/ITcathyh/conexec) 3 | [![codecov](https://codecov.io/gh/ITcathyh/conexec/branch/master/graph/badge.svg)](https://codecov.io/gh/ITcathyh/conexec) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/ITcathyh/conexec)](https://goreportcard.com/report/github.com/ITcathyh/conexec) 5 | [![GoDoc](https://godoc.org/github.com/ITcathyh/conexec?status.svg)](https://godoc.org/github.com/ITcathyh/conexec) 6 | 7 | conexec is a concurrent toolkit to help execute functions concurrently in an efficient and safe way. It supports specifying the overall timeout to avoid blocking. 8 | 9 | ## How to use 10 | Generally it can be set as a singleton to save memory. There are some example to use it. 11 | ### Normal Actuator 12 | Actuator is a base struct to execute functions concurrently. 13 | ``` 14 | opt := &Options{TimeOut:DurationPtr(time.Millisecond*50)} 15 | c := NewActuator(opt) 16 | 17 | err := c.Exec( 18 | func() error { 19 | fmt.Println(1) 20 | time.Sleep(time.Second * 2) 21 | return nil 22 | }, 23 | func() error { 24 | fmt.Println(2) 25 | return nil 26 | }, 27 | func() error { 28 | time.Sleep(time.Second * 1) 29 | fmt.Println(3) 30 | return nil 31 | }, 32 | ) 33 | 34 | if err != nil { 35 | // ...do sth 36 | } 37 | ``` 38 | ### Pooled Actuator 39 | Pooled actuator uses the goroutine pool to execute functions. In some times it is a more efficient way. 40 | ``` 41 | opt := &Options{TimeOut:DurationPtr(time.Millisecond*50)} 42 | c := NewPooledActuator(5, opt) 43 | 44 | err := c.Exec(...) 45 | 46 | if err != nil { 47 | // ...do sth 48 | } 49 | ``` 50 | Use custom goroutine pool 51 | ``` 52 | c := NewPooledActuator(5).WithPool(pool) 53 | ``` 54 | ### Simply exec using goroutine 55 | ``` 56 | done := Exec(...) 57 | 58 | if !done { 59 | // ... do sth 60 | } 61 | ``` 62 | -------------------------------------------------------------------------------- /common_test.go: -------------------------------------------------------------------------------- 1 | package conexec 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "testing" 7 | "time" 8 | 9 | . "github.com/go-playground/assert/v2" 10 | ) 11 | 12 | func testTimeout(t *testing.T, c TimedActuator) { 13 | st := time.Now().UnixNano() 14 | err := c.Exec(getTasks()...) 15 | 16 | Equal(t, err, ErrorTimeOut) 17 | et := time.Now().UnixNano() 18 | t.Logf("used time:%dms", (et-st)/1000000) 19 | time.Sleep(time.Millisecond * 500) 20 | } 21 | 22 | func testError(t *testing.T, c TimedActuator) { 23 | st := time.Now().UnixNano() 24 | tasks, te := getErrorTask() 25 | err := c.Exec(tasks...) 26 | 27 | Equal(t, err, te) 28 | et := time.Now().UnixNano() 29 | t.Logf("used time:%dms", (et-st)/1000000) 30 | time.Sleep(time.Millisecond * 500) 31 | } 32 | 33 | func testManyError(t *testing.T, c TimedActuator) { 34 | tasks := make([]Task, 0) 35 | tmp, te := getErrorTask() 36 | tasks = append(tasks, tmp...) 37 | 38 | for i := 0; i < 100; i++ { 39 | tmp, _ = getErrorTask() 40 | tasks = append(tasks, tmp...) 41 | } 42 | 43 | st := time.Now().UnixNano() 44 | err := c.Exec(tasks...) 45 | 46 | Equal(t, err, te) 47 | et := time.Now().UnixNano() 48 | t.Logf("used time:%dms", (et-st)/1000000) 49 | time.Sleep(time.Millisecond * 500) 50 | } 51 | 52 | func testNormal(t *testing.T, c TimedActuator) { 53 | Equal(t, c.Exec(getTasks()...), nil) 54 | } 55 | 56 | func testPanic(t *testing.T, c TimedActuator) { 57 | NotEqual(t, c.Exec(getPanicTask()), nil) 58 | } 59 | 60 | func testEmpty(t *testing.T, c TimedActuator) { 61 | Equal(t, c.Exec(), nil) 62 | } 63 | 64 | func getTasks() []Task { 65 | return []Task{ 66 | func() error { 67 | fmt.Println(1) 68 | time.Sleep(time.Millisecond * 100) 69 | return nil 70 | }, 71 | func() error { 72 | fmt.Println(2) 73 | return nil 74 | }, 75 | func() error { 76 | time.Sleep(time.Millisecond * 200) 77 | fmt.Println(3) 78 | return nil 79 | }, 80 | } 81 | } 82 | 83 | func getErrorTask() ([]Task, error) { 84 | te := errors.New("TestErr") 85 | 86 | tasks := getTasks() 87 | tasks = append(tasks, 88 | func() error { 89 | fmt.Println("4") 90 | return te 91 | }, 92 | func() error { 93 | time.Sleep(time.Millisecond * 300) 94 | fmt.Println("5") 95 | return te 96 | }, 97 | func() error { 98 | time.Sleep(time.Second) 99 | fmt.Println("6") 100 | return te 101 | }) 102 | 103 | return tasks, te 104 | } 105 | 106 | func getPanicTask() Task { 107 | return func() error { 108 | var i *int64 109 | num := *i + 1 110 | fmt.Println(num) 111 | return nil 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /pooled_actuator.go: -------------------------------------------------------------------------------- 1 | package conexec 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "runtime" 7 | "sync" 8 | "time" 9 | 10 | "github.com/panjf2000/ants/v2" 11 | ) 12 | 13 | var ( 14 | // ErrorUsingActuator is the error when goroutine pool has exception 15 | ErrorUsingActuator = fmt.Errorf("ErrorUsingActuator") 16 | ) 17 | 18 | // GoroutinePool is the base routine pool interface 19 | // User can use custom goroutine pool by implementing this interface 20 | type GoroutinePool interface { 21 | Submit(f func()) error 22 | Release() 23 | } 24 | 25 | // PooledActuator is a actuator which has a worker pool 26 | type PooledActuator struct { 27 | timeout *time.Duration 28 | 29 | workerNum int 30 | pool GoroutinePool 31 | 32 | initOnce sync.Once 33 | } 34 | 35 | // NewPooledActuator creates an PooledActuator instance 36 | func NewPooledActuator(workerNum int, opt ...*Options) *PooledActuator { 37 | c := &PooledActuator{ 38 | workerNum: workerNum, 39 | } 40 | setOptions(c, opt...) 41 | return c 42 | } 43 | 44 | // WithPool will support for using custom goroutine pool 45 | func (c *PooledActuator) WithPool(pool GoroutinePool) *PooledActuator { 46 | newActuator := c.clone() 47 | newActuator.pool = pool 48 | return newActuator 49 | } 50 | 51 | // Exec is used to run tasks concurrently 52 | func (c *PooledActuator) Exec(tasks ...Task) error { 53 | return c.ExecWithContext(context.Background(), tasks...) 54 | } 55 | 56 | // ExecWithContext uses goroutine pool to run tasks concurrently 57 | // Return nil when tasks are all completed successfully, 58 | // or return error when some exception happen such as timeout 59 | func (c *PooledActuator) ExecWithContext(ctx context.Context, tasks ...Task) error { 60 | // ensure the actuator can init correctly 61 | c.initOnce.Do(func() { 62 | c.initPooledActuator() 63 | }) 64 | 65 | if c.workerNum == -1 { 66 | return ErrorUsingActuator 67 | } 68 | 69 | return execTasks(ctx, c, c.runWithPool, tasks...) 70 | } 71 | 72 | // GetTimeout return the timeout set before 73 | func (c *PooledActuator) GetTimeout() *time.Duration { 74 | return c.timeout 75 | } 76 | 77 | // Release will release the pool 78 | func (c *PooledActuator) Release() { 79 | if c.pool != nil { 80 | c.pool.Release() 81 | } 82 | } 83 | 84 | // initPooledActuator init the pooled actuator once while the runtime 85 | // If the workerNum is zero or negative, 86 | // default worker num will be used 87 | func (c *PooledActuator) initPooledActuator() { 88 | if c.pool != nil { 89 | // just pass 90 | c.workerNum = 1 91 | return 92 | } 93 | 94 | if c.workerNum <= 0 { 95 | c.workerNum = runtime.NumCPU() << 1 96 | } 97 | 98 | var err error 99 | c.pool, err = ants.NewPool(c.workerNum) 100 | 101 | if err != nil { 102 | c.workerNum = -1 103 | fmt.Println("initPooledActuator err") 104 | } 105 | } 106 | 107 | // runWithPool used the goroutine pool to execute the tasks 108 | func (c *PooledActuator) runWithPool(f func()) { 109 | err := c.pool.Submit(f) 110 | if err != nil { 111 | fmt.Printf("submit task err:%s\n", err.Error()) 112 | } 113 | } 114 | 115 | // setTimeout sets the timeout 116 | func (c *PooledActuator) setTimeout(timeout *time.Duration) { 117 | c.timeout = timeout 118 | } 119 | 120 | // clone will clone this PooledActuator without goroutine pool 121 | func (c *PooledActuator) clone() *PooledActuator { 122 | return &PooledActuator{ 123 | timeout: c.timeout, 124 | workerNum: c.workerNum, 125 | initOnce: sync.Once{}, 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /exec.go: -------------------------------------------------------------------------------- 1 | package conexec 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "runtime/debug" 7 | "sync" 8 | "sync/atomic" 9 | "time" 10 | ) 11 | 12 | // wait waits for the notification of execution result 13 | func wait(ctx context.Context, c TimedActuator, 14 | resChan chan error, cancel context.CancelFunc) error { 15 | if timeout := c.GetTimeout(); timeout != nil { 16 | return waitWithTimeout(ctx, resChan, *timeout, cancel) 17 | } 18 | 19 | for { 20 | select { 21 | case <-ctx.Done(): 22 | return nil 23 | case err := <-resChan: 24 | if err != nil { 25 | cancel() 26 | return err 27 | } 28 | } 29 | } 30 | } 31 | 32 | // waitWithTimeout waits for the notification of execution result 33 | // when the timeout is set 34 | func waitWithTimeout(ctx context.Context, resChan chan error, 35 | timeout time.Duration, cancel context.CancelFunc) error { 36 | for { 37 | select { 38 | case <-time.After(timeout): 39 | cancel() 40 | return ErrorTimeOut 41 | case <-ctx.Done(): 42 | return nil 43 | case err := <-resChan: 44 | if err != nil { 45 | cancel() 46 | return err 47 | } 48 | } 49 | } 50 | } 51 | 52 | // execTasks uses customized function to 53 | // execute every task, such as using the simplyRun 54 | func execTasks(parent context.Context, c TimedActuator, 55 | execFunc func(f func()), tasks ...Task) error { 56 | size := len(tasks) 57 | if size == 0 { 58 | return nil 59 | } 60 | 61 | ctx, cancel := context.WithCancel(parent) 62 | resChan := make(chan error, size) 63 | wg := &sync.WaitGroup{} 64 | wg.Add(size) 65 | 66 | // Make sure the tasks are completed and channel is closed 67 | go func() { 68 | wg.Wait() 69 | cancel() 70 | close(resChan) 71 | }() 72 | 73 | // Sadly we can not kill a goroutine manually 74 | // So when an error happens, the other tasks will continue 75 | // But the good news is that main progress 76 | // will know the error immediately 77 | for _, task := range tasks { 78 | child, _ := context.WithCancel(ctx) 79 | f := wrapperTask(child, task, wg, resChan) 80 | execFunc(f) 81 | } 82 | 83 | return wait(ctx, c, resChan, cancel) 84 | } 85 | 86 | // simplyRun uses a new goroutine to run the function 87 | func simplyRun(f func()) { 88 | go f() 89 | } 90 | 91 | // Exec simply runs the tasks concurrently 92 | // True will be returned is all tasks complete successfully 93 | // otherwise false will be returned 94 | func Exec(tasks ...Task) bool { 95 | var c int32 96 | wg := &sync.WaitGroup{} 97 | wg.Add(len(tasks)) 98 | 99 | for _, t := range tasks { 100 | go func(task Task) { 101 | defer func() { 102 | if r := recover(); r != nil { 103 | atomic.StoreInt32(&c, 1) 104 | fmt.Printf("conexec panic:%v\n%s\n", r, string(debug.Stack())) 105 | } 106 | 107 | wg.Done() 108 | }() 109 | 110 | if err := task(); err != nil { 111 | atomic.StoreInt32(&c, 1) 112 | } 113 | }(t) 114 | } 115 | 116 | wg.Wait() 117 | return c == 0 118 | } 119 | 120 | // ExecWithError simply runs the tasks concurrently 121 | // nil will be returned is all tasks complete successfully 122 | // otherwise custom error will be returned 123 | func ExecWithError(tasks ...Task) error { 124 | var err error 125 | wg := &sync.WaitGroup{} 126 | wg.Add(len(tasks)) 127 | 128 | for _, t := range tasks { 129 | go func(task Task) { 130 | defer func() { 131 | if r := recover(); r != nil { 132 | err = fmt.Errorf("conexec panic:%v\n%s\n", r, string(debug.Stack())) 133 | } 134 | 135 | wg.Done() 136 | }() 137 | 138 | if e := task(); e != nil { 139 | err = e 140 | } 141 | }(t) 142 | } 143 | 144 | wg.Wait() 145 | return err 146 | } 147 | --------------------------------------------------------------------------------