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 }