github.com/searKing/golang/go@v1.2.117/sync/until.go (about) 1 // Copyright 2021 The searKing Author. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package sync 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "sync" 12 ) 13 14 // ErrUntilClosed is returned by the Until's Do method after a call to Close. 15 var ErrUntilClosed = errors.New("sync: Until closed") 16 17 // Until represents a class of work and forms a namespace in 18 // which units of work can be executed with duplicate suppression. 19 // It blocks on certain Do actions and unblock when Retry is called. 20 type Until struct { 21 mu sync.Mutex 22 done bool 23 blockingCh chan struct{} 24 } 25 26 // Retry unblocks all blocked pick. 27 func (u *Until) Retry() { 28 u.mu.Lock() 29 if u.done { 30 u.mu.Unlock() 31 return 32 } 33 if u.blockingCh != nil { 34 close(u.blockingCh) 35 } 36 u.blockingCh = make(chan struct{}) 37 u.mu.Unlock() 38 } 39 40 // Do executes and returns the results of the given function. 41 // It may block in the following cases: 42 // - the current fn is nil 43 // - the err returned by the current fn is not nil 44 // When one of these situations happens, Do blocks until the Retry is called. 45 func (u *Until) Do(ctx context.Context, fn func() (any, error)) (val any, err error) { 46 u.mu.Lock() 47 if u.blockingCh == nil { 48 u.blockingCh = make(chan struct{}) 49 } 50 u.mu.Unlock() 51 var ch chan struct{} 52 53 var lastPickErr error 54 for { 55 u.mu.Lock() 56 if u.done { 57 u.mu.Unlock() 58 return nil, ErrUntilClosed 59 } 60 if fn == nil { 61 ch = u.blockingCh 62 } 63 if ch == u.blockingCh { 64 // This could happen when either: 65 // - forget (the previous if condition), or 66 // - has called Do on the current Do. 67 u.mu.Unlock() 68 select { 69 case <-ctx.Done(): 70 var errStr string 71 if lastPickErr != nil { 72 errStr = "latest single_flight_group error: " + lastPickErr.Error() 73 } else { 74 errStr = ctx.Err().Error() 75 } 76 return nil, fmt.Errorf("%s: %w", errStr, ctx.Err()) 77 case <-ch: 78 } 79 continue 80 } 81 82 ch = u.blockingCh 83 u.mu.Unlock() 84 val, err = fn() 85 if err != nil { 86 lastPickErr = err 87 // continue back to the beginning of the for loop to redo. 88 continue 89 } 90 return val, nil 91 } 92 } 93 94 func (u *Until) Close() { 95 u.mu.Lock() 96 defer u.mu.Unlock() 97 if u.done { 98 return 99 } 100 u.done = true 101 close(u.blockingCh) 102 }