github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/utils/parallel/try_test.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package parallel_test 5 6 import ( 7 "errors" 8 "fmt" 9 "io" 10 "sort" 11 "sync" 12 "time" 13 14 gc "launchpad.net/gocheck" 15 16 "launchpad.net/juju-core/testing" 17 jc "launchpad.net/juju-core/testing/checkers" 18 "launchpad.net/juju-core/utils/parallel" 19 ) 20 21 type result string 22 23 func (r result) Close() error { 24 return nil 25 } 26 27 type trySuite struct{} 28 29 var _ = gc.Suite(&trySuite{}) 30 31 func tryFunc(delay time.Duration, val io.Closer, err error) func(<-chan struct{}) (io.Closer, error) { 32 return func(<-chan struct{}) (io.Closer, error) { 33 time.Sleep(delay) 34 return val, err 35 } 36 } 37 38 func (*trySuite) TestOneSuccess(c *gc.C) { 39 try := parallel.NewTry(0, nil) 40 try.Start(tryFunc(0, result("hello"), nil)) 41 val, err := try.Result() 42 c.Assert(err, gc.IsNil) 43 c.Assert(val, gc.Equals, result("hello")) 44 } 45 46 func (*trySuite) TestOneFailure(c *gc.C) { 47 try := parallel.NewTry(0, nil) 48 expectErr := errors.New("foo") 49 err := try.Start(tryFunc(0, nil, expectErr)) 50 c.Assert(err, gc.IsNil) 51 select { 52 case <-try.Dead(): 53 c.Fatalf("try died before it should") 54 case <-time.After(testing.ShortWait): 55 } 56 try.Close() 57 select { 58 case <-try.Dead(): 59 case <-time.After(testing.LongWait): 60 c.Fatalf("timed out waiting for Try to complete") 61 } 62 val, err := try.Result() 63 c.Assert(val, gc.IsNil) 64 c.Assert(err, gc.Equals, expectErr) 65 } 66 67 func (*trySuite) TestStartReturnsErrorAfterClose(c *gc.C) { 68 try := parallel.NewTry(0, nil) 69 expectErr := errors.New("foo") 70 err := try.Start(tryFunc(0, nil, expectErr)) 71 c.Assert(err, gc.IsNil) 72 try.Close() 73 err = try.Start(tryFunc(0, result("goodbye"), nil)) 74 c.Assert(err, gc.Equals, parallel.ErrClosed) 75 // Wait for the first try to deliver its result 76 time.Sleep(testing.ShortWait) 77 try.Kill() 78 err = try.Wait() 79 c.Assert(err, gc.Equals, expectErr) 80 } 81 82 func (*trySuite) TestOutOfOrderResults(c *gc.C) { 83 try := parallel.NewTry(0, nil) 84 try.Start(tryFunc(50*time.Millisecond, result("first"), nil)) 85 try.Start(tryFunc(10*time.Millisecond, result("second"), nil)) 86 r, err := try.Result() 87 c.Assert(err, gc.IsNil) 88 c.Assert(r, gc.Equals, result("second")) 89 } 90 91 func (*trySuite) TestMaxParallel(c *gc.C) { 92 try := parallel.NewTry(3, nil) 93 var ( 94 mu sync.Mutex 95 count int 96 max int 97 ) 98 99 for i := 0; i < 10; i++ { 100 try.Start(func(<-chan struct{}) (io.Closer, error) { 101 mu.Lock() 102 if count++; count > max { 103 max = count 104 } 105 c.Check(count, gc.Not(jc.GreaterThan), 3) 106 mu.Unlock() 107 time.Sleep(20 * time.Millisecond) 108 mu.Lock() 109 count-- 110 mu.Unlock() 111 return result("hello"), nil 112 }) 113 } 114 r, err := try.Result() 115 c.Assert(err, gc.IsNil) 116 c.Assert(r, gc.Equals, result("hello")) 117 mu.Lock() 118 defer mu.Unlock() 119 c.Assert(max, gc.Equals, 3) 120 } 121 122 func (*trySuite) TestStartBlocksForMaxParallel(c *gc.C) { 123 try := parallel.NewTry(3, nil) 124 125 started := make(chan struct{}) 126 begin := make(chan struct{}) 127 go func() { 128 for i := 0; i < 6; i++ { 129 err := try.Start(func(<-chan struct{}) (io.Closer, error) { 130 <-begin 131 return nil, fmt.Errorf("an error") 132 }) 133 started <- struct{}{} 134 if i < 5 { 135 c.Check(err, gc.IsNil) 136 } else { 137 c.Check(err, gc.Equals, parallel.ErrClosed) 138 } 139 } 140 close(started) 141 }() 142 // Check we can start the first three. 143 timeout := time.After(testing.LongWait) 144 for i := 0; i < 3; i++ { 145 select { 146 case <-started: 147 case <-timeout: 148 c.Fatalf("timed out") 149 } 150 } 151 // Check we block when going above maxParallel. 152 timeout = time.After(testing.ShortWait) 153 select { 154 case <-started: 155 c.Fatalf("Start did not block") 156 case <-timeout: 157 } 158 159 // Unblock two attempts. 160 begin <- struct{}{} 161 begin <- struct{}{} 162 163 // Check we can start another two. 164 timeout = time.After(testing.LongWait) 165 for i := 0; i < 2; i++ { 166 select { 167 case <-started: 168 case <-timeout: 169 c.Fatalf("timed out") 170 } 171 } 172 173 // Check we block again when going above maxParallel. 174 timeout = time.After(testing.ShortWait) 175 select { 176 case <-started: 177 c.Fatalf("Start did not block") 178 case <-timeout: 179 } 180 181 // Close the Try - the last request should be discarded, 182 // unblocking last remaining Start request. 183 try.Close() 184 185 timeout = time.After(testing.LongWait) 186 select { 187 case <-started: 188 case <-timeout: 189 c.Fatalf("Start did not unblock after Close") 190 } 191 192 // Ensure all checks are completed 193 select { 194 case _, ok := <-started: 195 c.Assert(ok, gc.Equals, false) 196 case <-timeout: 197 c.Fatalf("Start goroutine did not finish") 198 } 199 } 200 201 func (*trySuite) TestAllConcurrent(c *gc.C) { 202 try := parallel.NewTry(0, nil) 203 started := make(chan chan struct{}) 204 for i := 0; i < 10; i++ { 205 try.Start(func(<-chan struct{}) (io.Closer, error) { 206 reply := make(chan struct{}) 207 started <- reply 208 <-reply 209 return result("hello"), nil 210 }) 211 } 212 timeout := time.After(testing.LongWait) 213 for i := 0; i < 10; i++ { 214 select { 215 case reply := <-started: 216 reply <- struct{}{} 217 case <-timeout: 218 c.Fatalf("timed out") 219 } 220 } 221 } 222 223 type gradedError int 224 225 func (e gradedError) Error() string { 226 return fmt.Sprintf("error with importance %d", e) 227 } 228 229 func gradedErrorCombine(err0, err1 error) error { 230 if err0 == nil || err0.(gradedError) < err1.(gradedError) { 231 return err1 232 } 233 return err0 234 } 235 236 type multiError struct { 237 errs []int 238 } 239 240 func (e *multiError) Error() string { 241 return fmt.Sprintf("%v", e.errs) 242 } 243 244 func (*trySuite) TestErrorCombine(c *gc.C) { 245 // Use maxParallel=1 to guarantee that all errors are processed sequentially. 246 try := parallel.NewTry(1, func(err0, err1 error) error { 247 if err0 == nil { 248 err0 = &multiError{} 249 } 250 err0.(*multiError).errs = append(err0.(*multiError).errs, int(err1.(gradedError))) 251 return err0 252 }) 253 errors := []gradedError{3, 2, 4, 0, 5, 5, 3} 254 for _, err := range errors { 255 err := err 256 try.Start(func(<-chan struct{}) (io.Closer, error) { 257 return nil, err 258 }) 259 } 260 try.Close() 261 val, err := try.Result() 262 c.Assert(val, gc.IsNil) 263 grades := err.(*multiError).errs 264 sort.Ints(grades) 265 c.Assert(grades, gc.DeepEquals, []int{0, 2, 3, 3, 4, 5, 5}) 266 } 267 268 func (*trySuite) TestTriesAreStopped(c *gc.C) { 269 try := parallel.NewTry(0, nil) 270 stopped := make(chan struct{}) 271 try.Start(func(stop <-chan struct{}) (io.Closer, error) { 272 <-stop 273 stopped <- struct{}{} 274 return nil, parallel.ErrStopped 275 }) 276 try.Start(tryFunc(0, result("hello"), nil)) 277 val, err := try.Result() 278 c.Assert(err, gc.IsNil) 279 c.Assert(val, gc.Equals, result("hello")) 280 281 select { 282 case <-stopped: 283 case <-time.After(testing.LongWait): 284 c.Fatalf("timed out waiting for stop") 285 } 286 } 287 288 func (*trySuite) TestCloseTwice(c *gc.C) { 289 try := parallel.NewTry(0, nil) 290 try.Close() 291 try.Close() 292 val, err := try.Result() 293 c.Assert(val, gc.IsNil) 294 c.Assert(err, gc.IsNil) 295 } 296 297 type closeResult struct { 298 closed chan struct{} 299 } 300 301 func (r *closeResult) Close() error { 302 close(r.closed) 303 return nil 304 } 305 306 func (*trySuite) TestExtraResultsAreClosed(c *gc.C) { 307 try := parallel.NewTry(0, nil) 308 begin := make([]chan struct{}, 4) 309 results := make([]*closeResult, len(begin)) 310 for i := range begin { 311 begin[i] = make(chan struct{}) 312 results[i] = &closeResult{make(chan struct{})} 313 i := i 314 try.Start(func(<-chan struct{}) (io.Closer, error) { 315 <-begin[i] 316 return results[i], nil 317 }) 318 } 319 begin[0] <- struct{}{} 320 val, err := try.Result() 321 c.Assert(err, gc.IsNil) 322 c.Assert(val, gc.Equals, results[0]) 323 324 timeout := time.After(testing.ShortWait) 325 for i, r := range results[1:] { 326 begin[i+1] <- struct{}{} 327 select { 328 case <-r.closed: 329 case <-timeout: 330 c.Fatalf("timed out waiting for close") 331 } 332 } 333 select { 334 case <-results[0].closed: 335 c.Fatalf("result was inappropriately closed") 336 case <-time.After(testing.ShortWait): 337 } 338 } 339 340 func (*trySuite) TestEverything(c *gc.C) { 341 try := parallel.NewTry(5, gradedErrorCombine) 342 tries := []struct { 343 startAt time.Duration 344 wait time.Duration 345 val result 346 err error 347 }{{ 348 wait: 30 * time.Millisecond, 349 err: gradedError(3), 350 }, { 351 startAt: 10 * time.Millisecond, 352 wait: 20 * time.Millisecond, 353 val: result("result 1"), 354 }, { 355 startAt: 20 * time.Millisecond, 356 wait: 10 * time.Millisecond, 357 val: result("result 2"), 358 }, { 359 startAt: 20 * time.Millisecond, 360 wait: 5 * time.Second, 361 val: "delayed result", 362 }, { 363 startAt: 5 * time.Millisecond, 364 err: gradedError(4), 365 }} 366 for _, t := range tries { 367 t := t 368 go func() { 369 time.Sleep(t.startAt) 370 try.Start(tryFunc(t.wait, t.val, t.err)) 371 }() 372 } 373 val, err := try.Result() 374 if val != result("result 1") && val != result("result 2") { 375 c.Errorf(`expected "result 1" or "result 2" got %#v`, val) 376 } 377 c.Assert(err, gc.IsNil) 378 }