github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/agent/reporter.go (about)

     1  package agent
     2  
     3  import (
     4  	"context"
     5  	"reflect"
     6  	"sync"
     7  
     8  	"github.com/docker/swarmkit/api"
     9  	"github.com/docker/swarmkit/log"
    10  )
    11  
    12  // StatusReporter receives updates to task status. Method may be called
    13  // concurrently, so implementations should be goroutine-safe.
    14  type StatusReporter interface {
    15  	UpdateTaskStatus(ctx context.Context, taskID string, status *api.TaskStatus) error
    16  }
    17  
    18  type statusReporterFunc func(ctx context.Context, taskID string, status *api.TaskStatus) error
    19  
    20  func (fn statusReporterFunc) UpdateTaskStatus(ctx context.Context, taskID string, status *api.TaskStatus) error {
    21  	return fn(ctx, taskID, status)
    22  }
    23  
    24  // statusReporter creates a reliable StatusReporter that will always succeed.
    25  // It handles several tasks at once, ensuring all statuses are reported.
    26  //
    27  // The reporter will continue reporting the current status until it succeeds.
    28  type statusReporter struct {
    29  	reporter StatusReporter
    30  	statuses map[string]*api.TaskStatus
    31  	mu       sync.Mutex
    32  	cond     sync.Cond
    33  	closed   bool
    34  }
    35  
    36  func newStatusReporter(ctx context.Context, upstream StatusReporter) *statusReporter {
    37  	r := &statusReporter{
    38  		reporter: upstream,
    39  		statuses: make(map[string]*api.TaskStatus),
    40  	}
    41  
    42  	r.cond.L = &r.mu
    43  
    44  	go r.run(ctx)
    45  	return r
    46  }
    47  
    48  func (sr *statusReporter) UpdateTaskStatus(ctx context.Context, taskID string, status *api.TaskStatus) error {
    49  	sr.mu.Lock()
    50  	defer sr.mu.Unlock()
    51  
    52  	current, ok := sr.statuses[taskID]
    53  	if ok {
    54  		if reflect.DeepEqual(current, status) {
    55  			return nil
    56  		}
    57  
    58  		if current.State > status.State {
    59  			return nil // ignore old updates
    60  		}
    61  	}
    62  	sr.statuses[taskID] = status
    63  	sr.cond.Signal()
    64  
    65  	return nil
    66  }
    67  
    68  func (sr *statusReporter) Close() error {
    69  	sr.mu.Lock()
    70  	defer sr.mu.Unlock()
    71  
    72  	sr.closed = true
    73  	sr.cond.Signal()
    74  
    75  	return nil
    76  }
    77  
    78  func (sr *statusReporter) run(ctx context.Context) {
    79  	done := make(chan struct{})
    80  	defer close(done)
    81  
    82  	sr.mu.Lock() // released during wait, below.
    83  	defer sr.mu.Unlock()
    84  
    85  	go func() {
    86  		select {
    87  		case <-ctx.Done():
    88  			sr.Close()
    89  		case <-done:
    90  			return
    91  		}
    92  	}()
    93  
    94  	for {
    95  		if len(sr.statuses) == 0 {
    96  			sr.cond.Wait()
    97  		}
    98  
    99  		if sr.closed {
   100  			// TODO(stevvooe): Add support here for waiting until all
   101  			// statuses are flushed before shutting down.
   102  			return
   103  		}
   104  
   105  		for taskID, status := range sr.statuses {
   106  			delete(sr.statuses, taskID) // delete the entry, while trying to send.
   107  
   108  			sr.mu.Unlock()
   109  			err := sr.reporter.UpdateTaskStatus(ctx, taskID, status)
   110  			sr.mu.Lock()
   111  
   112  			// reporter might be closed during UpdateTaskStatus call
   113  			if sr.closed {
   114  				return
   115  			}
   116  
   117  			if err != nil {
   118  				log.G(ctx).WithError(err).Error("status reporter failed to report status to agent")
   119  
   120  				// place it back in the map, if not there, allowing us to pick
   121  				// the value if a new one came in when we were sending the last
   122  				// update.
   123  				if _, ok := sr.statuses[taskID]; !ok {
   124  					sr.statuses[taskID] = status
   125  				}
   126  			}
   127  		}
   128  	}
   129  }