github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/client/allocrunner/taskrunner/lazy_handle.go (about) 1 package taskrunner 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 "time" 8 9 log "github.com/hashicorp/go-hclog" 10 cstructs "github.com/hashicorp/nomad/client/structs" 11 bstructs "github.com/hashicorp/nomad/plugins/base/structs" 12 ) 13 14 const ( 15 // retrieveBackoffBaseline is the baseline time for exponential backoff while 16 // retrieving a handle. 17 retrieveBackoffBaseline = 250 * time.Millisecond 18 19 // retrieveBackoffLimit is the limit of the exponential backoff for 20 // retrieving a handle. 21 retrieveBackoffLimit = 5 * time.Second 22 23 // retrieveFailureLimit is how many times we will attempt to retrieve a 24 // new handle before giving up. 25 retrieveFailureLimit = 5 26 ) 27 28 // retrieveHandleFn is used to retrieve the latest driver handle 29 type retrieveHandleFn func() *DriverHandle 30 31 // LazyHandle is used to front calls to a DriverHandle where it is expected the 32 // existing handle may no longer be valid because the backing plugin has 33 // shutdown. LazyHandle detects the plugin shutting down and retrieves a new 34 // handle so that the consumer does not need to worry whether the handle is to 35 // the latest driver instance. 36 type LazyHandle struct { 37 // retrieveHandle is used to retrieve the latest handle 38 retrieveHandle retrieveHandleFn 39 40 // h is the current handle and may be nil 41 h *DriverHandle 42 43 // shutdownCtx is used to cancel retries if the agent is shutting down 44 shutdownCtx context.Context 45 46 logger log.Logger 47 sync.Mutex 48 } 49 50 // NewLazyHandle takes the function to receive the latest handle and a logger 51 // and returns a LazyHandle 52 func NewLazyHandle(shutdownCtx context.Context, fn retrieveHandleFn, logger log.Logger) *LazyHandle { 53 return &LazyHandle{ 54 retrieveHandle: fn, 55 h: fn(), 56 shutdownCtx: shutdownCtx, 57 logger: logger.Named("lazy_handle"), 58 } 59 } 60 61 // getHandle returns the current handle or retrieves a new one 62 func (l *LazyHandle) getHandle() (*DriverHandle, error) { 63 l.Lock() 64 defer l.Unlock() 65 66 if l.h != nil { 67 return l.h, nil 68 } 69 70 return l.refreshHandleLocked() 71 } 72 73 // refreshHandle retrieves a new handle 74 func (l *LazyHandle) refreshHandle() (*DriverHandle, error) { 75 l.Lock() 76 defer l.Unlock() 77 return l.refreshHandleLocked() 78 } 79 80 // refreshHandleLocked retrieves a new handle and should be called with the lock 81 // held. It will retry to give the client time to restart the driver and restore 82 // the handle. 83 func (l *LazyHandle) refreshHandleLocked() (*DriverHandle, error) { 84 for i := 0; i < retrieveFailureLimit; i++ { 85 l.h = l.retrieveHandle() 86 if l.h != nil { 87 return l.h, nil 88 } 89 90 // Calculate the new backoff 91 backoff := (1 << (2 * uint64(i))) * retrieveBackoffBaseline 92 if backoff > retrieveBackoffLimit { 93 backoff = retrieveBackoffLimit 94 } 95 96 l.logger.Debug("failed to retrieve handle", "backoff", backoff) 97 98 select { 99 case <-l.shutdownCtx.Done(): 100 return nil, l.shutdownCtx.Err() 101 case <-time.After(backoff): 102 } 103 } 104 105 return nil, fmt.Errorf("no driver handle") 106 } 107 108 func (l *LazyHandle) Exec(timeout time.Duration, cmd string, args []string) ([]byte, int, error) { 109 h, err := l.getHandle() 110 if err != nil { 111 return nil, 0, err 112 } 113 114 // Only retry once 115 first := true 116 117 TRY: 118 out, c, err := h.Exec(timeout, cmd, args) 119 if err == bstructs.ErrPluginShutdown && first { 120 first = false 121 122 h, err = l.refreshHandle() 123 if err == nil { 124 goto TRY 125 } 126 } 127 128 return out, c, err 129 } 130 131 func (l *LazyHandle) Stats(ctx context.Context, interval time.Duration) (<-chan *cstructs.TaskResourceUsage, error) { 132 h, err := l.getHandle() 133 if err != nil { 134 return nil, err 135 } 136 137 // Only retry once 138 first := true 139 140 TRY: 141 out, err := h.Stats(ctx, interval) 142 if err == bstructs.ErrPluginShutdown && first { 143 first = false 144 145 h, err = l.refreshHandle() 146 if err == nil { 147 goto TRY 148 } 149 } 150 151 return out, err 152 }