launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/utils/parallel/try.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package parallel 5 6 import ( 7 "io" 8 "launchpad.net/errgo/errors" 9 "sync" 10 11 "launchpad.net/tomb" 12 ) 13 14 var ( 15 ErrStopped = errors.New("try was stopped") 16 ErrClosed = errors.New("try was closed") 17 ) 18 19 // Try represents an attempt made concurrently 20 // by a number of goroutines. 21 type Try struct { 22 tomb tomb.Tomb 23 closeMutex sync.Mutex 24 close chan struct{} 25 limiter chan struct{} 26 start chan func() 27 result chan result 28 combineErrors func(err0, err1 error) error 29 maxParallel int 30 endResult io.Closer 31 } 32 33 // NewTry returns an object that runs functions concurrently until one 34 // succeeds. The result of the first function that returns without an 35 // error is available from the Result method. If maxParallel is 36 // positive, it limits the number of concurrently running functions. 37 // 38 // The function combineErrors(oldErr, newErr) is called to determine 39 // the error return (see the Result method). The first time it is called, 40 // oldErr will be nil; subsequently oldErr will be the error previously 41 // returned by combineErrors. If combineErrors is nil, the last 42 // encountered error is chosen. 43 func NewTry(maxParallel int, combineErrors func(err0, err1 error) error) *Try { 44 if combineErrors == nil { 45 combineErrors = chooseLastError 46 } 47 t := &Try{ 48 combineErrors: combineErrors, 49 maxParallel: maxParallel, 50 close: make(chan struct{}, 1), 51 result: make(chan result), 52 start: make(chan func()), 53 } 54 if t.maxParallel > 0 { 55 t.limiter = make(chan struct{}, t.maxParallel) 56 for i := 0; i < t.maxParallel; i++ { 57 t.limiter <- struct{}{} 58 } 59 } 60 go func() { 61 defer t.tomb.Done() 62 val, err := t.loop() 63 t.endResult = val 64 t.tomb.Kill(err) 65 }() 66 return t 67 } 68 69 func chooseLastError(err0, err1 error) error { 70 return err1 71 } 72 73 type result struct { 74 val io.Closer 75 err error 76 } 77 78 func (t *Try) loop() (io.Closer, error) { 79 var err error 80 close := t.close 81 nrunning := 0 82 for { 83 select { 84 case f := <-t.start: 85 nrunning++ 86 go f() 87 case r := <-t.result: 88 if r.err == nil { 89 return r.val, r.err 90 } 91 err = t.combineErrors(err, r.err) 92 nrunning-- 93 if close == nil && nrunning == 0 { 94 return nil, err 95 } 96 case <-t.tomb.Dying(): 97 if err == nil { 98 return nil, ErrStopped 99 } 100 return nil, err 101 case <-close: 102 close = nil 103 if nrunning == 0 { 104 return nil, err 105 } 106 } 107 } 108 } 109 110 // Start requests the given function to be started, waiting until there 111 // are less than maxParallel functions running if necessary. It returns 112 // an error if the function has not been started (ErrClosed if the Try 113 // has been closed, and ErrStopped if the try is finishing). 114 // 115 // The function should listen on the stop channel and return if it 116 // receives a value, though this is advisory only - the Try does not 117 // wait for all started functions to return before completing. 118 // 119 // If the function returns a nil error but some earlier try was 120 // successful (that is, the returned value is being discarded), 121 // its returned value will be closed by calling its Close method. 122 func (t *Try) Start(try func(stop <-chan struct{}) (io.Closer, error)) error { 123 if t.limiter != nil { 124 // Wait for availability slot. 125 select { 126 case <-t.limiter: 127 case <-t.tomb.Dying(): 128 return ErrStopped 129 case <-t.close: 130 return ErrClosed 131 } 132 } 133 dying := t.tomb.Dying() 134 f := func() { 135 val, err := try(dying) 136 if t.limiter != nil { 137 // Signal availability slot is now free. 138 t.limiter <- struct{}{} 139 } 140 // Deliver result. 141 select { 142 case t.result <- result{val, err}: 143 case <-dying: 144 if err == nil { 145 val.Close() 146 } 147 } 148 } 149 select { 150 case t.start <- f: 151 return nil 152 case <-dying: 153 return ErrStopped 154 case <-t.close: 155 return ErrClosed 156 } 157 } 158 159 // Close closes the Try. No more functions will be started 160 // if Start is called, and the Try will terminate when all 161 // outstanding functions have completed (or earlier 162 // if one succeeds) 163 func (t *Try) Close() { 164 t.closeMutex.Lock() 165 defer t.closeMutex.Unlock() 166 select { 167 case <-t.close: 168 default: 169 close(t.close) 170 } 171 } 172 173 // Dead returns a channel that is closed when the 174 // Try completes. 175 func (t *Try) Dead() <-chan struct{} { 176 return t.tomb.Dead() 177 } 178 179 // Wait waits for the Try to complete and returns the same 180 // error returned by Result. 181 func (t *Try) Wait() error { 182 return t.tomb.Wait() 183 } 184 185 // Result waits for the Try to complete and returns the result of the 186 // first successful function started by Start. 187 // 188 // If no function succeeded, the last error returned by 189 // combineErrors is returned. If there were no errors or 190 // combineErrors returned nil, ErrStopped is returned. 191 func (t *Try) Result() (io.Closer, error) { 192 err := t.tomb.Wait() 193 return t.endResult, err 194 } 195 196 // Kill stops the try and all its currently executing functions. 197 func (t *Try) Kill() { 198 t.tomb.Kill(nil) 199 }