github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/watch/watch.go (about) 1 package watch 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 "time" 8 9 "github.com/docker/go-events" 10 "github.com/docker/swarmkit/watch/queue" 11 ) 12 13 // ChannelSinkGenerator is a constructor of sinks that eventually lead to a 14 // channel. 15 type ChannelSinkGenerator interface { 16 NewChannelSink() (events.Sink, *events.Channel) 17 } 18 19 // Queue is the structure used to publish events and watch for them. 20 type Queue struct { 21 sinkGen ChannelSinkGenerator 22 // limit is the max number of items to be held in memory for a watcher 23 limit uint64 24 mu sync.Mutex 25 broadcast *events.Broadcaster 26 cancelFuncs map[events.Sink]func() 27 28 // closeOutChan indicates whether the watchers' channels should be closed 29 // when a watcher queue reaches its limit or when the Close method of the 30 // sink is called. 31 closeOutChan bool 32 } 33 34 // NewQueue creates a new publish/subscribe queue which supports watchers. 35 // The channels that it will create for subscriptions will have the buffer 36 // size specified by buffer. 37 func NewQueue(options ...func(*Queue) error) *Queue { 38 // Create a queue with the default values 39 q := &Queue{ 40 sinkGen: &dropErrClosedChanGen{}, 41 broadcast: events.NewBroadcaster(), 42 cancelFuncs: make(map[events.Sink]func()), 43 limit: 0, 44 closeOutChan: false, 45 } 46 47 for _, option := range options { 48 err := option(q) 49 if err != nil { 50 panic(fmt.Sprintf("Failed to apply options to queue: %s", err)) 51 } 52 } 53 54 return q 55 } 56 57 // WithTimeout returns a functional option for a queue that sets a write timeout 58 func WithTimeout(timeout time.Duration) func(*Queue) error { 59 return func(q *Queue) error { 60 q.sinkGen = NewTimeoutDropErrSinkGen(timeout) 61 return nil 62 } 63 } 64 65 // WithCloseOutChan returns a functional option for a queue whose watcher 66 // channel is closed when no more events are expected to be sent to the watcher. 67 func WithCloseOutChan() func(*Queue) error { 68 return func(q *Queue) error { 69 q.closeOutChan = true 70 return nil 71 } 72 } 73 74 // WithLimit returns a functional option for a queue with a max size limit. 75 func WithLimit(limit uint64) func(*Queue) error { 76 return func(q *Queue) error { 77 q.limit = limit 78 return nil 79 } 80 } 81 82 // Watch returns a channel which will receive all items published to the 83 // queue from this point, until cancel is called. 84 func (q *Queue) Watch() (eventq chan events.Event, cancel func()) { 85 return q.CallbackWatch(nil) 86 } 87 88 // WatchContext returns a channel where all items published to the queue will 89 // be received. The channel will be closed when the provided context is 90 // cancelled. 91 func (q *Queue) WatchContext(ctx context.Context) (eventq chan events.Event) { 92 return q.CallbackWatchContext(ctx, nil) 93 } 94 95 // CallbackWatch returns a channel which will receive all events published to 96 // the queue from this point that pass the check in the provided callback 97 // function. The returned cancel function will stop the flow of events and 98 // close the channel. 99 func (q *Queue) CallbackWatch(matcher events.Matcher) (eventq chan events.Event, cancel func()) { 100 chanSink, ch := q.sinkGen.NewChannelSink() 101 lq := queue.NewLimitQueue(chanSink, q.limit) 102 sink := events.Sink(lq) 103 104 if matcher != nil { 105 sink = events.NewFilter(sink, matcher) 106 } 107 108 q.broadcast.Add(sink) 109 110 cancelFunc := func() { 111 q.broadcast.Remove(sink) 112 ch.Close() 113 sink.Close() 114 } 115 116 externalCancelFunc := func() { 117 q.mu.Lock() 118 cancelFunc := q.cancelFuncs[sink] 119 delete(q.cancelFuncs, sink) 120 q.mu.Unlock() 121 122 if cancelFunc != nil { 123 cancelFunc() 124 } 125 } 126 127 q.mu.Lock() 128 q.cancelFuncs[sink] = cancelFunc 129 q.mu.Unlock() 130 131 // If the output channel shouldn't be closed and the queue is limitless, 132 // there's no need for an additional goroutine. 133 if !q.closeOutChan && q.limit == 0 { 134 return ch.C, externalCancelFunc 135 } 136 137 outChan := make(chan events.Event) 138 go func() { 139 for { 140 select { 141 case <-ch.Done(): 142 // Close the output channel if the ChannelSink is Done for any 143 // reason. This can happen if the cancelFunc is called 144 // externally or if it has been closed by a wrapper sink, such 145 // as the TimeoutSink. 146 if q.closeOutChan { 147 close(outChan) 148 } 149 externalCancelFunc() 150 return 151 case <-lq.Full(): 152 // Close the output channel and tear down the Queue if the 153 // LimitQueue becomes full. 154 if q.closeOutChan { 155 close(outChan) 156 } 157 externalCancelFunc() 158 return 159 case event := <-ch.C: 160 outChan <- event 161 } 162 } 163 }() 164 165 return outChan, externalCancelFunc 166 } 167 168 // CallbackWatchContext returns a channel where all items published to the queue will 169 // be received. The channel will be closed when the provided context is 170 // cancelled. 171 func (q *Queue) CallbackWatchContext(ctx context.Context, matcher events.Matcher) (eventq chan events.Event) { 172 c, cancel := q.CallbackWatch(matcher) 173 go func() { 174 <-ctx.Done() 175 cancel() 176 }() 177 return c 178 } 179 180 // Publish adds an item to the queue. 181 func (q *Queue) Publish(item events.Event) { 182 q.broadcast.Write(item) 183 } 184 185 // Close closes the queue and frees the associated resources. 186 func (q *Queue) Close() error { 187 // Make sure all watchers have been closed to avoid a deadlock when 188 // closing the broadcaster. 189 q.mu.Lock() 190 for _, cancelFunc := range q.cancelFuncs { 191 cancelFunc() 192 } 193 q.cancelFuncs = make(map[events.Sink]func()) 194 q.mu.Unlock() 195 196 return q.broadcast.Close() 197 }