github.com/pachyderm/pachyderm@v1.13.4/src/server/pkg/watch/watch.go (about) 1 // Package watch implements better watch semantics on top of etcd. 2 // See this issue for the reasoning behind the package: 3 // https://github.com/coreos/etcd/issues/7362 4 package watch 5 6 import ( 7 "bytes" 8 "context" 9 "reflect" 10 11 etcd "github.com/coreos/etcd/clientv3" 12 "github.com/gogo/protobuf/proto" 13 "github.com/pachyderm/pachyderm/src/client/pkg/errors" 14 ) 15 16 // EventType is the type of event 17 type EventType int 18 19 const ( 20 // EventPut happens when an item is added 21 EventPut EventType = iota 22 // EventDelete happens when an item is removed 23 EventDelete 24 // EventError happens when an error occurred 25 EventError 26 ) 27 28 // Event is an event that occurred to an item in etcd. 29 type Event struct { 30 Key []byte 31 Value []byte 32 Type EventType 33 Rev int64 34 Ver int64 35 Err error 36 Template proto.Message 37 } 38 39 // Unmarshal unmarshals the item in an event into a protobuf message. 40 func (e *Event) Unmarshal(key *string, val proto.Message) error { 41 if err := CheckType(e.Template, val); err != nil { 42 return err 43 } 44 *key = string(e.Key) 45 return proto.Unmarshal(e.Value, val) 46 } 47 48 // Watcher ... 49 type Watcher interface { 50 // Watch returns a channel that delivers events 51 Watch() <-chan *Event 52 // Close this channel when you are done receiving events 53 Close() 54 } 55 56 type watcher struct { 57 eventCh chan *Event 58 done chan struct{} 59 } 60 61 func (w *watcher) Watch() <-chan *Event { 62 return w.eventCh 63 } 64 65 func (w *watcher) Close() { 66 close(w.done) 67 } 68 69 // NewWatcher watches a given etcd prefix for events. 70 func NewWatcher(ctx context.Context, client *etcd.Client, trimPrefix, prefix string, template proto.Message, opts ...OpOption) (Watcher, error) { 71 eventCh := make(chan *Event) 72 done := make(chan struct{}) 73 // First list the collection to get the current items 74 // Sort by mod revision--how the items would have been returned if we watched 75 // them from the beginning. 76 getOptions := []etcd.OpOption{etcd.WithPrefix(), etcd.WithSort(etcd.SortByModRevision, etcd.SortAscend)} 77 for _, opt := range opts { 78 if opt.Get != nil { 79 getOptions = append(getOptions, etcd.OpOption(opt.Get)) 80 } 81 } 82 resp, err := client.Get(ctx, prefix, getOptions...) 83 if err != nil { 84 return nil, err 85 } 86 nextRevision := resp.Header.Revision + 1 87 watchOptions := []etcd.OpOption{etcd.WithPrefix(), etcd.WithRev(nextRevision)} 88 for _, opt := range opts { 89 if opt.Watch != nil { 90 watchOptions = append(watchOptions, etcd.OpOption(opt.Watch)) 91 } 92 } 93 94 etcdWatcher := etcd.NewWatcher(client) 95 // Issue a watch that uses the revision timestamp returned by the 96 // Get request earlier. That way even if some items are added between 97 // when we list the collection and when we start watching the collection, 98 // we won't miss any items. 99 100 rch := etcdWatcher.Watch(ctx, prefix, watchOptions...) 101 102 go func() (retErr error) { 103 defer func() { 104 if retErr != nil { 105 select { 106 case eventCh <- &Event{ 107 Err: retErr, 108 Type: EventError, 109 }: 110 case <-done: 111 } 112 } 113 close(eventCh) 114 etcdWatcher.Close() 115 }() 116 for _, etcdKv := range resp.Kvs { 117 eventCh <- &Event{ 118 Key: bytes.TrimPrefix(etcdKv.Key, []byte(trimPrefix)), 119 Value: etcdKv.Value, 120 Type: EventPut, 121 Rev: etcdKv.ModRevision, 122 Ver: etcdKv.Version, 123 Template: template, 124 } 125 } 126 for { 127 var resp etcd.WatchResponse 128 var ok bool 129 select { 130 case resp, ok = <-rch: 131 case <-done: 132 return nil 133 } 134 if !ok { 135 if err := etcdWatcher.Close(); err != nil { 136 return err 137 } 138 etcdWatcher = etcd.NewWatcher(client) 139 // use new "nextRevision" 140 options := []etcd.OpOption{etcd.WithPrefix(), etcd.WithRev(nextRevision)} 141 for _, opt := range opts { 142 if opt.Watch != nil { 143 options = append(options, etcd.OpOption(opt.Watch)) 144 } 145 } 146 rch = etcdWatcher.Watch(ctx, prefix, options...) 147 continue 148 } 149 if err := resp.Err(); err != nil { 150 return err 151 } 152 for _, etcdEv := range resp.Events { 153 ev := &Event{ 154 Key: bytes.TrimPrefix(etcdEv.Kv.Key, []byte(trimPrefix)), 155 Value: etcdEv.Kv.Value, 156 Rev: etcdEv.Kv.ModRevision, 157 Ver: etcdEv.Kv.Version, 158 Template: template, 159 } 160 if etcdEv.Type == etcd.EventTypePut { 161 ev.Type = EventPut 162 } else { 163 ev.Type = EventDelete 164 } 165 select { 166 case eventCh <- ev: 167 case <-done: 168 return nil 169 } 170 } 171 nextRevision = resp.Header.Revision + 1 172 } 173 }() 174 175 return &watcher{ 176 eventCh: eventCh, 177 done: done, 178 }, nil 179 } 180 181 // MakeWatcher returns a Watcher that uses the given event channel and done 182 // channel internally to deliver events and signal closure, respectively. 183 func MakeWatcher(eventCh chan *Event, done chan struct{}) Watcher { 184 return &watcher{ 185 eventCh: eventCh, 186 done: done, 187 } 188 } 189 190 // CheckType checks to make sure val has the same type as template, unless 191 // template is nil in which case it always returns nil. 192 func CheckType(template proto.Message, val interface{}) error { 193 if template != nil { 194 valType, templateType := reflect.TypeOf(val), reflect.TypeOf(template) 195 if valType != templateType { 196 return errors.Errorf("invalid type, got: %s, expected: %s", valType, templateType) 197 } 198 } 199 return nil 200 }