github.com/pachyderm/pachyderm@v1.13.4/src/server/pkg/collection/collection.go (about) 1 package collection 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 "path" 8 "path/filepath" 9 "reflect" 10 "strconv" 11 "strings" 12 "sync/atomic" 13 "time" 14 15 "github.com/pachyderm/pachyderm/src/client/pkg/errors" 16 "github.com/pachyderm/pachyderm/src/client/pkg/tracing" 17 "github.com/pachyderm/pachyderm/src/server/pkg/errutil" 18 "github.com/pachyderm/pachyderm/src/server/pkg/watch" 19 20 etcd "github.com/coreos/etcd/clientv3" 21 "github.com/coreos/etcd/mvcc/mvccpb" 22 "github.com/gogo/protobuf/proto" 23 ) 24 25 // defaultLimit was experimentally determined to be the highest value that could work 26 // (It gets scaled down for specific collections if they trip the max-message size.) 27 const ( 28 defaultLimit int64 = 262144 29 DefaultPrefix string = "pachyderm/1.7.0" 30 indexIdentifier string = "__index_" 31 ) 32 33 var ( 34 // ErrNotClaimed is an error used to indicate that a different requester beat 35 // the current requester to a key claim. 36 ErrNotClaimed = errors.Errorf("NOT_CLAIMED") 37 ttl = int64(30) 38 ) 39 40 type collection struct { 41 etcdClient *etcd.Client 42 prefix string 43 indexes []*Index 44 // The limit used when listing the collection. This gets automatically 45 // tuned when requests fail so it's stored per collection. 46 limit int64 47 // We need this to figure out the concrete type of the objects 48 // that this collection is storing. It's pretty retarded, but 49 // not sure what else we can do since types in Go are not first-class 50 // objects. 51 // To be clear, this is only necessary because of `Delete`, where we 52 // need to know the type in order to properly remove secondary indexes. 53 template proto.Message 54 // keyCheck is a function that checks if a key is valid. Invalid keys 55 // cannot be created. 56 keyCheck func(string) error 57 58 // valCheck is a function that checks if a value is valid. 59 valCheck func(proto.Message) error 60 } 61 62 // NewCollection creates a new collection. 63 func NewCollection(etcdClient *etcd.Client, prefix string, indexes []*Index, template proto.Message, keyCheck func(string) error, valCheck func(proto.Message) error) Collection { 64 // We want to ensure that the prefix always ends with a trailing 65 // slash. Otherwise, when you list the items under a collection 66 // such as `foo`, you might end up listing items under `foobar` 67 // as well. 68 if len(prefix) > 0 && prefix[len(prefix)-1] != '/' { 69 prefix = prefix + "/" 70 } 71 72 return &collection{ 73 prefix: prefix, 74 etcdClient: etcdClient, 75 indexes: indexes, 76 limit: defaultLimit, 77 template: template, 78 keyCheck: keyCheck, 79 valCheck: valCheck, 80 } 81 } 82 83 func (c *collection) ReadWrite(stm STM) ReadWriteCollection { 84 return &readWriteCollection{ 85 collection: c, 86 stm: stm, 87 } 88 } 89 90 func (c *collection) ReadWriteInt(stm STM) ReadWriteIntCollection { 91 return &readWriteIntCollection{ 92 collection: c, 93 stm: stm, 94 } 95 } 96 97 func (c *collection) ReadOnly(ctx context.Context) ReadonlyCollection { 98 return &readonlyCollection{ 99 collection: c, 100 ctx: ctx, 101 } 102 } 103 104 func (c *collection) Claim(ctx context.Context, key string, val proto.Message, f func(context.Context) error) error { 105 var claimed bool 106 if _, err := NewSTM(ctx, c.etcdClient, func(stm STM) error { 107 readWriteC := c.ReadWrite(stm) 108 if err := readWriteC.Get(key, val); err != nil { 109 if !IsErrNotFound(err) { 110 return err 111 } 112 claimed = true 113 return readWriteC.PutTTL(key, val, ttl) 114 } 115 claimed = false 116 return nil 117 }); err != nil { 118 return err 119 } 120 if !claimed { 121 return ErrNotClaimed 122 } 123 claimCtx, cancel := context.WithCancel(ctx) 124 defer cancel() 125 go func() { 126 for { 127 select { 128 case <-time.After((time.Second * time.Duration(ttl)) / 2): 129 // (bryce) potential race condition, goroutine does PutTTL after Put for completion which deletes work 130 // potential way around this is to have this only update the lease and not do a put (maybe through keepalive?) 131 if _, err := NewSTM(claimCtx, c.etcdClient, func(stm STM) error { 132 readWriteC := c.ReadWrite(stm) 133 if err := readWriteC.Get(key, val); err != nil { 134 return err 135 } 136 return readWriteC.PutTTL(key, val, ttl) 137 }); err != nil { 138 cancel() 139 return 140 } 141 case <-claimCtx.Done(): 142 return 143 } 144 } 145 }() 146 return f(claimCtx) 147 } 148 149 // Path returns the full path of a key in the etcd namespace 150 func (c *collection) Path(key string) string { 151 return path.Join(c.prefix, key) 152 } 153 154 func (c *collection) indexRoot(index *Index) string { 155 // remove trailing slash from c.prefix 156 return fmt.Sprintf("%s%s%s/", 157 strings.TrimRight(c.prefix, "/"), indexIdentifier, index.Field) 158 } 159 160 // See the documentation for `Index` for details. 161 func (c *collection) indexDir(index *Index, indexVal interface{}) string { 162 var indexValStr string 163 if marshaller, ok := indexVal.(proto.Marshaler); ok { 164 if indexValBytes, err := marshaller.Marshal(); err == nil { 165 // use marshalled proto as index. This way we can rename fields without 166 // breaking our index. 167 indexValStr = string(indexValBytes) 168 } else { 169 // log error but keep going (this used to be the only codepath) 170 log.Printf("ERROR trying to marshal index value: %v", err) 171 indexValStr = fmt.Sprintf("%v", indexVal) 172 } 173 } else { 174 indexValStr = fmt.Sprintf("%v", indexVal) 175 } 176 return path.Join(c.indexRoot(index), indexValStr) 177 } 178 179 // See the documentation for `Index` for details. 180 func (c *collection) indexPath(index *Index, indexVal interface{}, key string) string { 181 return path.Join(c.indexDir(index, indexVal), key) 182 } 183 184 type readWriteCollection struct { 185 *collection 186 stm STM 187 } 188 189 func (c *readWriteCollection) Get(key string, val proto.Message) (retErr error) { 190 span, _ := tracing.AddSpanToAnyExisting(c.stm.Context(), "/etcd.RW/Get", 191 "col", c.prefix, "key", strings.TrimPrefix(key, c.prefix)) 192 defer func() { 193 tracing.TagAnySpan(span, "err", retErr) 194 tracing.FinishAnySpan(span) 195 }() 196 if err := watch.CheckType(c.template, val); err != nil { 197 return err 198 } 199 valStr, err := c.stm.Get(c.Path(key)) 200 if err != nil { 201 if IsErrNotFound(err) { 202 return ErrNotFound{c.prefix, key} 203 } 204 return err 205 } 206 c.stm.SetSafePutCheck(c.Path(key), reflect.ValueOf(val).Pointer()) 207 return proto.Unmarshal([]byte(valStr), val) 208 } 209 210 func cloneProtoMsg(original proto.Message) proto.Message { 211 val := reflect.ValueOf(original) 212 if val.Kind() == reflect.Ptr { 213 val = reflect.Indirect(val) 214 } 215 return reflect.New(val.Type()).Interface().(proto.Message) 216 } 217 218 // Giving a value, an index, and the key of the item, return the path 219 // under which the new index item should be stored. 220 func (c *readWriteCollection) getIndexPath(val interface{}, index *Index, key string) string { 221 reflVal := reflect.ValueOf(val) 222 field := reflect.Indirect(reflVal).FieldByName(index.Field).Interface() 223 return c.indexPath(index, field, key) 224 } 225 226 // Giving a value, a multi-index, and the key of the item, return the 227 // paths under which the multi-index items should be stored. 228 func (c *readWriteCollection) getMultiIndexPaths(val interface{}, index *Index, key string) []string { 229 var indexPaths []string 230 field := reflect.Indirect(reflect.ValueOf(val)).FieldByName(index.Field) 231 for i := 0; i < field.Len(); i++ { 232 indexPaths = append(indexPaths, c.indexPath(index, field.Index(i).Interface(), key)) 233 } 234 return indexPaths 235 } 236 237 func (c *readWriteCollection) Put(key string, val proto.Message) error { 238 return c.PutTTL(key, val, 0) 239 } 240 241 func (c *readWriteCollection) TTL(key string) (int64, error) { 242 ttl, err := c.stm.TTL(c.Path(key)) 243 if IsErrNotFound(err) { 244 return ttl, ErrNotFound{c.prefix, key} 245 } 246 return ttl, err 247 } 248 249 func (c *readWriteCollection) PutTTL(key string, val proto.Message, ttl int64) error { 250 if strings.Contains(key, indexIdentifier) { 251 return errors.Errorf("cannot put key %q which contains reserved string %q", key, indexIdentifier) 252 } 253 if err := watch.CheckType(c.template, val); err != nil { 254 return err 255 } 256 257 if c.collection.keyCheck != nil { 258 if err := c.collection.keyCheck(key); err != nil { 259 return err 260 } 261 } 262 263 if c.collection.valCheck != nil { 264 if err := c.collection.valCheck(val); err != nil { 265 return err 266 } 267 } 268 269 ptr := reflect.ValueOf(val).Pointer() 270 if !c.stm.IsSafePut(c.Path(key), ptr) { 271 return errors.Errorf("unsafe put for key %v (passed ptr did not receive updated value)", key) 272 } 273 274 if c.indexes != nil { 275 clone := cloneProtoMsg(val) 276 277 // Put the appropriate record in any of c's secondary indexes 278 for _, index := range c.indexes { 279 if index.Multi { 280 indexPaths := c.getMultiIndexPaths(val, index, key) 281 for _, indexPath := range indexPaths { 282 // Only put the index if it doesn't already exist; otherwise 283 // we might trigger an unnecessary event if someone is 284 // watching the index 285 if _, err := c.stm.Get(indexPath); err != nil && IsErrNotFound(err) { 286 if err := c.stm.Put(indexPath, key, ttl, 0); err != nil { 287 return err 288 } 289 } 290 } 291 // If we can get the original value, we remove the original indexes 292 if err := c.Get(key, clone); err == nil { 293 for _, originalIndexPath := range c.getMultiIndexPaths(clone, index, key) { 294 var found bool 295 for _, indexPath := range indexPaths { 296 if originalIndexPath == indexPath { 297 found = true 298 } 299 } 300 if !found { 301 c.stm.Del(originalIndexPath) 302 } 303 } 304 } 305 } else { 306 indexPath := c.getIndexPath(val, index, key) 307 // If we can get the original value, we remove the original indexes 308 if err := c.Get(key, clone); err == nil { 309 originalIndexPath := c.getIndexPath(clone, index, key) 310 if originalIndexPath != indexPath { 311 c.stm.Del(originalIndexPath) 312 } 313 } 314 // Only put the index if it doesn't already exist; otherwise 315 // we might trigger an unnecessary event if someone is 316 // watching the index 317 if _, err := c.stm.Get(indexPath); err != nil && IsErrNotFound(err) { 318 if err := c.stm.Put(indexPath, key, ttl, 0); err != nil { 319 return err 320 } 321 } 322 } 323 } 324 } 325 bytes, err := proto.Marshal(val) 326 if err != nil { 327 return err 328 } 329 return c.stm.Put(c.Path(key), string(bytes), ttl, ptr) 330 } 331 332 // Update reads the current value associated with 'key', calls 'f' to update 333 // the value, and writes the new value back to the collection. 'key' must be 334 // present in the collection, or a 'Not Found' error is returned 335 func (c *readWriteCollection) Update(key string, val proto.Message, f func() error) error { 336 if err := watch.CheckType(c.template, val); err != nil { 337 return err 338 } 339 if err := c.Get(key, val); err != nil { 340 if IsErrNotFound(err) { 341 return ErrNotFound{c.prefix, key} 342 } 343 return err 344 } 345 if err := f(); err != nil { 346 return err 347 } 348 return c.Put(key, val) 349 } 350 351 // Upsert is like Update but 'key' is not required to be present 352 func (c *readWriteCollection) Upsert(key string, val proto.Message, f func() error) error { 353 if err := watch.CheckType(c.template, val); err != nil { 354 return err 355 } 356 if err := c.Get(key, val); err != nil && !IsErrNotFound(err) { 357 return err 358 } 359 if err := f(); err != nil { 360 return err 361 } 362 return c.Put(key, val) 363 } 364 365 func (c *readWriteCollection) Create(key string, val proto.Message) error { 366 if err := watch.CheckType(c.template, val); err != nil { 367 return err 368 } 369 fullKey := c.Path(key) 370 _, err := c.stm.Get(fullKey) 371 if err != nil && !IsErrNotFound(err) { 372 return err 373 } 374 if err == nil { 375 return ErrExists{c.prefix, key} 376 } 377 return c.Put(key, val) 378 } 379 380 func (c *readWriteCollection) Delete(key string) error { 381 fullKey := c.Path(key) 382 if _, err := c.stm.Get(fullKey); err != nil { 383 return err 384 } 385 if c.indexes != nil && c.template != nil { 386 val := proto.Clone(c.template) 387 for _, index := range c.indexes { 388 if err := c.Get(key, val); err == nil { 389 if index.Multi { 390 indexPaths := c.getMultiIndexPaths(val, index, key) 391 for _, indexPath := range indexPaths { 392 c.stm.Del(indexPath) 393 } 394 } else { 395 indexPath := c.getIndexPath(val, index, key) 396 c.stm.Del(indexPath) 397 } 398 } 399 } 400 } 401 c.stm.Del(fullKey) 402 return nil 403 } 404 405 func (c *readWriteCollection) DeleteAll() { 406 // Delete indexes 407 for _, index := range c.indexes { 408 c.stm.DelAll(c.indexRoot(index)) 409 } 410 c.stm.DelAll(c.prefix) 411 } 412 413 func (c *readWriteCollection) DeleteAllPrefix(prefix string) { 414 c.stm.DelAll(path.Join(c.prefix, prefix) + "/") 415 } 416 417 type readWriteIntCollection struct { 418 *collection 419 stm STM 420 } 421 422 func (c *readWriteIntCollection) Create(key string, val int) error { 423 fullKey := c.Path(key) 424 _, err := c.stm.Get(fullKey) 425 if err != nil && !IsErrNotFound(err) { 426 return err 427 } 428 if err == nil { 429 return ErrExists{c.prefix, key} 430 } 431 return c.stm.Put(fullKey, strconv.Itoa(val), 0, 0) 432 } 433 434 func (c *readWriteIntCollection) Get(key string) (int, error) { 435 valStr, err := c.stm.Get(c.Path(key)) 436 if err != nil { 437 return 0, err 438 } 439 return strconv.Atoi(valStr) 440 } 441 442 func (c *readWriteIntCollection) Increment(key string) error { 443 return c.IncrementBy(key, 1) 444 } 445 446 func (c *readWriteIntCollection) IncrementBy(key string, n int) error { 447 fullKey := c.Path(key) 448 valStr, err := c.stm.Get(fullKey) 449 if err != nil { 450 return err 451 } 452 val, err := strconv.Atoi(valStr) 453 if err != nil { 454 return ErrMalformedValue{c.prefix, key, valStr} 455 } 456 return c.stm.Put(fullKey, strconv.Itoa(val+n), 0, 0) 457 } 458 459 func (c *readWriteIntCollection) Decrement(key string) error { 460 return c.DecrementBy(key, 1) 461 } 462 463 func (c *readWriteIntCollection) DecrementBy(key string, n int) error { 464 fullKey := c.Path(key) 465 valStr, err := c.stm.Get(fullKey) 466 if err != nil { 467 return err 468 } 469 val, err := strconv.Atoi(valStr) 470 if err != nil { 471 return ErrMalformedValue{c.prefix, key, valStr} 472 } 473 return c.stm.Put(fullKey, strconv.Itoa(val-n), 0, 0) 474 } 475 476 func (c *readWriteIntCollection) Delete(key string) error { 477 fullKey := c.Path(key) 478 if _, err := c.stm.Get(fullKey); err != nil { 479 return err 480 } 481 c.stm.Del(fullKey) 482 return nil 483 } 484 485 type readonlyCollection struct { 486 *collection 487 ctx context.Context 488 } 489 490 // get is an internal wrapper around etcdClient.Get that wraps the call in a 491 // trace 492 func (c *readonlyCollection) get(key string, opts ...etcd.OpOption) (resp *etcd.GetResponse, retErr error) { 493 span, ctx := tracing.AddSpanToAnyExisting(c.ctx, "/etcd.RO/Get", 494 "col", c.prefix, "key", strings.TrimPrefix(key, c.prefix)) 495 defer func() { 496 tracing.TagAnySpan(span, "err", retErr) 497 tracing.FinishAnySpan(span) 498 }() 499 resp, err := c.etcdClient.Get(ctx, key, opts...) 500 return resp, err 501 } 502 503 func (c *readonlyCollection) Get(key string, val proto.Message) error { 504 if err := watch.CheckType(c.template, val); err != nil { 505 return err 506 } 507 resp, err := c.get(c.Path(key)) 508 if err != nil { 509 return err 510 } 511 512 if len(resp.Kvs) == 0 { 513 return ErrNotFound{c.prefix, key} 514 } 515 516 return proto.Unmarshal(resp.Kvs[0].Value, val) 517 } 518 519 func (c *readonlyCollection) GetByIndex(index *Index, indexVal interface{}, val proto.Message, opts *Options, f func(key string) error) error { 520 span, _ := tracing.AddSpanToAnyExisting(c.ctx, "/etcd.RO/GetByIndex", "col", c.prefix, "index", index, "indexVal", indexVal) 521 defer tracing.FinishAnySpan(span) 522 if atomic.LoadInt64(&index.limit) == 0 { 523 atomic.CompareAndSwapInt64(&index.limit, 0, defaultLimit) 524 } 525 return c.list(c.indexDir(index, indexVal), &index.limit, opts, func(kv *mvccpb.KeyValue) error { 526 key := path.Base(string(kv.Key)) 527 if err := c.Get(key, val); err != nil { 528 if IsErrNotFound(err) { 529 // In cases where we changed how certain objects are 530 // indexed, we could end up in a situation where the 531 // object is deleted but the old indexes still exist. 532 return nil 533 } 534 return err 535 } 536 return f(key) 537 }) 538 } 539 540 func (c *readonlyCollection) GetBlock(key string, val proto.Message) error { 541 span, ctx := tracing.AddSpanToAnyExisting(c.ctx, "/etcd.RO/GetBlock", 542 "col", c.prefix, "key", strings.TrimPrefix(key, c.prefix)) 543 defer tracing.FinishAnySpan(span) 544 if err := watch.CheckType(c.template, val); err != nil { 545 return err 546 } 547 watcher, err := watch.NewWatcher(ctx, c.etcdClient, c.prefix, c.Path(key), c.template) 548 if err != nil { 549 return err 550 } 551 defer watcher.Close() 552 e := <-watcher.Watch() 553 if e.Err != nil { 554 return e.Err 555 } 556 return e.Unmarshal(&key, val) 557 } 558 559 func (c *readonlyCollection) TTL(key string) (int64, error) { 560 resp, err := c.get(c.Path(key)) 561 if err != nil { 562 return 0, err 563 } 564 if len(resp.Kvs) == 0 { 565 return 0, ErrNotFound{c.prefix, key} 566 } 567 leaseID := etcd.LeaseID(resp.Kvs[0].Lease) 568 569 span, ctx := tracing.AddSpanToAnyExisting(c.ctx, "/etcd.RO/TimeToLive") 570 defer tracing.FinishAnySpan(span) 571 leaseTTLResp, err := c.etcdClient.TimeToLive(ctx, leaseID) 572 if err != nil { 573 return 0, errors.Wrapf(err, "could not fetch lease TTL") 574 } 575 return leaseTTLResp.TTL, nil 576 } 577 578 // ListPrefix returns keys (and values) that begin with prefix, f will be 579 // called with each key, val will contain the value for the key. 580 // You can break out of iteration by returning errutil.ErrBreak. 581 func (c *readonlyCollection) ListPrefix(prefix string, val proto.Message, opts *Options, f func(string) error) error { 582 span, _ := tracing.AddSpanToAnyExisting(c.ctx, "/etcd.RO/ListPrefix", "col", c.prefix, "prefix", prefix) 583 defer tracing.FinishAnySpan(span) 584 queryPrefix := c.prefix 585 if prefix != "" { 586 // If we always call join, we'll get rid of the trailing slash we need 587 // on the root c.prefix 588 queryPrefix = filepath.Join(c.prefix, prefix) 589 } 590 return c.list(queryPrefix, &c.limit, opts, func(kv *mvccpb.KeyValue) error { 591 if err := proto.Unmarshal(kv.Value, val); err != nil { 592 return err 593 } 594 return f(strings.TrimPrefix(string(kv.Key), queryPrefix)) 595 }) 596 } 597 598 // List returns objects sorted based on the options passed in. f will be called with each key, val will contain the 599 // corresponding value. Val is not an argument to f because that would require 600 // f to perform a cast before it could be used. 601 // You can break out of iteration by returning errutil.ErrBreak. 602 func (c *readonlyCollection) List(val proto.Message, opts *Options, f func(key string) error) error { 603 span, _ := tracing.AddSpanToAnyExisting(c.ctx, "/etcd.RO/List", "col", c.prefix) 604 defer tracing.FinishAnySpan(span) 605 if err := watch.CheckType(c.template, val); err != nil { 606 return err 607 } 608 return c.list(c.prefix, &c.limit, opts, func(kv *mvccpb.KeyValue) error { 609 if err := proto.Unmarshal(kv.Value, val); err != nil { 610 return err 611 } 612 return f(strings.TrimPrefix(string(kv.Key), c.prefix)) 613 }) 614 } 615 616 // ListRev returns objects sorted based on the options passed in. f will be called 617 // with each key and the create-revision of the key, val will contain the 618 // corresponding value. Val is not an argument to f because that would require 619 // f to perform a cast before it could be used. You can break out of iteration 620 // by returning errutil.ErrBreak. 621 func (c *readonlyCollection) ListRev(val proto.Message, opts *Options, f func(key string, createRev int64) error) error { 622 span, _ := tracing.AddSpanToAnyExisting(c.ctx, "/etcd.RO/List", "col", c.prefix) 623 defer tracing.FinishAnySpan(span) 624 if err := watch.CheckType(c.template, val); err != nil { 625 return err 626 } 627 return c.list(c.prefix, &c.limit, opts, func(kv *mvccpb.KeyValue) error { 628 if err := proto.Unmarshal(kv.Value, val); err != nil { 629 return err 630 } 631 return f(strings.TrimPrefix(string(kv.Key), c.prefix), kv.CreateRevision) 632 }) 633 } 634 635 func (c *readonlyCollection) list(prefix string, limitPtr *int64, opts *Options, f func(*mvccpb.KeyValue) error) error { 636 if opts.SelfSort { 637 return listSelfSortRevision(c, prefix, limitPtr, opts, f) 638 } 639 640 return listRevision(c, prefix, limitPtr, opts, f) 641 } 642 643 var ( 644 countOpts = []etcd.OpOption{etcd.WithPrefix(), etcd.WithCountOnly()} 645 ) 646 647 func (c *readonlyCollection) Count() (int64, error) { 648 resp, err := c.get(c.prefix, countOpts...) 649 if err != nil { 650 return 0, err 651 } 652 return resp.Count, err 653 } 654 655 func (c *readonlyCollection) CountRev(rev int64) (int64, int64, error) { 656 resp, err := c.get(c.prefix, append(countOpts, etcd.WithRev(rev))...) 657 if err != nil { 658 return 0, 0, err 659 } 660 return resp.Count, resp.Header.Revision, err 661 } 662 663 // Watch a collection, returning the current content of the collection as 664 // well as any future additions. 665 func (c *readonlyCollection) Watch(opts ...watch.OpOption) (watch.Watcher, error) { 666 return watch.NewWatcher(c.ctx, c.etcdClient, c.prefix, c.prefix, c.template, opts...) 667 } 668 669 // WatchF watches a collection and executes a callback function each time an event occurs. 670 func (c *readonlyCollection) WatchF(f func(e *watch.Event) error, opts ...watch.OpOption) error { 671 watcher, err := c.Watch(opts...) 672 if err != nil { 673 return err 674 } 675 defer watcher.Close() 676 return c.watchF(watcher, f) 677 } 678 679 func (c *readonlyCollection) watchF(watcher watch.Watcher, f func(e *watch.Event) error) error { 680 for { 681 select { 682 case e, ok := <-watcher.Watch(): 683 if !ok { 684 return nil 685 } 686 if e.Type == watch.EventError { 687 return e.Err 688 } 689 if err := f(e); err != nil { 690 if errors.Is(err, errutil.ErrBreak) { 691 return nil 692 } 693 return err 694 } 695 case <-c.ctx.Done(): 696 return c.ctx.Err() 697 } 698 } 699 } 700 701 // WatchByIndex watches items in a collection that match a particular index 702 func (c *readonlyCollection) WatchByIndex(index *Index, val interface{}) (watch.Watcher, error) { 703 eventCh := make(chan *watch.Event) 704 done := make(chan struct{}) 705 watcher, err := watch.NewWatcher(c.ctx, c.etcdClient, c.prefix, c.indexDir(index, val), c.template) 706 if err != nil { 707 return nil, err 708 } 709 go func() (retErr error) { 710 defer func() { 711 if retErr != nil { 712 eventCh <- &watch.Event{ 713 Type: watch.EventError, 714 Err: retErr, 715 } 716 watcher.Close() 717 } 718 close(eventCh) 719 }() 720 for { 721 var ev *watch.Event 722 var ok bool 723 select { 724 case ev, ok = <-watcher.Watch(): 725 case <-done: 726 watcher.Close() 727 return nil 728 } 729 if !ok { 730 watcher.Close() 731 return nil 732 } 733 734 var directEv *watch.Event 735 switch ev.Type { 736 case watch.EventError: 737 // pass along the error 738 return ev.Err 739 case watch.EventPut: 740 resp, err := c.get(c.Path(path.Base(string(ev.Key)))) 741 if err != nil { 742 return err 743 } 744 if len(resp.Kvs) == 0 { 745 // this happens only if the item was deleted shortly after 746 // we receive this event. 747 continue 748 } 749 directEv = &watch.Event{ 750 Key: []byte(path.Base(string(ev.Key))), 751 Value: resp.Kvs[0].Value, 752 Type: ev.Type, 753 Template: c.template, 754 } 755 case watch.EventDelete: 756 directEv = &watch.Event{ 757 Key: []byte(path.Base(string(ev.Key))), 758 Type: ev.Type, 759 Template: c.template, 760 } 761 } 762 eventCh <- directEv 763 } 764 }() 765 return watch.MakeWatcher(eventCh, done), nil 766 } 767 768 // WatchOne watches a given item. The first value returned from the watch 769 // will be the current value of the item. 770 func (c *readonlyCollection) WatchOne(key string, opts ...watch.OpOption) (watch.Watcher, error) { 771 return watch.NewWatcher(c.ctx, c.etcdClient, c.prefix, c.Path(key), c.template, opts...) 772 } 773 774 // WatchOneF watches a given item and executes a callback function each time an event occurs. 775 // The first value returned from the watch will be the current value of the item. 776 func (c *readonlyCollection) WatchOneF(key string, f func(e *watch.Event) error, opts ...watch.OpOption) error { 777 watcher, err := watch.NewWatcher(c.ctx, c.etcdClient, c.prefix, c.Path(key), c.template, opts...) 778 if err != nil { 779 return err 780 } 781 defer watcher.Close() 782 return c.watchF(watcher, f) 783 }