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  }