github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/metacache-stream.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "context" 22 "errors" 23 "fmt" 24 "io" 25 "strings" 26 "sync" 27 28 jsoniter "github.com/json-iterator/go" 29 "github.com/klauspost/compress/s2" 30 xioutil "github.com/minio/minio/internal/ioutil" 31 "github.com/minio/minio/internal/logger" 32 "github.com/tinylib/msgp/msgp" 33 "github.com/valyala/bytebufferpool" 34 ) 35 36 // metadata stream format: 37 // 38 // The stream is s2 compressed. 39 // https://github.com/klauspost/compress/tree/master/s2#s2-compression 40 // This ensures integrity and reduces the size typically by at least 50%. 41 // 42 // All stream elements are msgpack encoded. 43 // 44 // 1 Integer, metacacheStreamVersion of the writer. 45 // This can be used for managing breaking changes. 46 // 47 // For each element: 48 // 1. Bool. If false at end of stream. 49 // 2. String. Name of object. Directories contains a trailing slash. 50 // 3. Binary. Blob of metadata. Length 0 on directories. 51 // ... Next element. 52 // 53 // Streams can be assumed to be sorted in ascending order. 54 // If the stream ends before a false boolean it can be assumed it was truncated. 55 56 const metacacheStreamVersion = 2 57 58 // metacacheWriter provides a serializer of metacache objects. 59 type metacacheWriter struct { 60 streamErr error 61 mw *msgp.Writer 62 creator func() error 63 closer func() error 64 blockSize int 65 streamWg sync.WaitGroup 66 reuseBlocks bool 67 } 68 69 // newMetacacheWriter will create a serializer that will write objects in given order to the output. 70 // Provide a block size that affects latency. If 0 a default of 128KiB will be used. 71 // Block size can be up to 4MiB. 72 func newMetacacheWriter(out io.Writer, blockSize int) *metacacheWriter { 73 if blockSize < 8<<10 { 74 blockSize = 128 << 10 75 } 76 w := metacacheWriter{ 77 mw: nil, 78 blockSize: blockSize, 79 } 80 w.creator = func() error { 81 s2w := s2.NewWriter(out, s2.WriterBlockSize(blockSize), s2.WriterConcurrency(2)) 82 w.mw = msgp.NewWriter(s2w) 83 w.creator = nil 84 if err := w.mw.WriteByte(metacacheStreamVersion); err != nil { 85 return err 86 } 87 88 w.closer = func() (err error) { 89 defer func() { 90 cerr := s2w.Close() 91 if err == nil && cerr != nil { 92 err = cerr 93 } 94 }() 95 if w.streamErr != nil { 96 return w.streamErr 97 } 98 if err = w.mw.WriteBool(false); err != nil { 99 return err 100 } 101 return w.mw.Flush() 102 } 103 return nil 104 } 105 return &w 106 } 107 108 // write one or more objects to the stream in order. 109 // It is favorable to send as many objects as possible in a single write, 110 // but no more than math.MaxUint32 111 func (w *metacacheWriter) write(objs ...metaCacheEntry) error { 112 if w == nil { 113 return errors.New("metacacheWriter: nil writer") 114 } 115 if len(objs) == 0 { 116 return nil 117 } 118 if w.creator != nil { 119 err := w.creator() 120 w.creator = nil 121 if err != nil { 122 return fmt.Errorf("metacacheWriter: unable to create writer: %w", err) 123 } 124 if w.mw == nil { 125 return errors.New("metacacheWriter: writer not initialized") 126 } 127 } 128 for _, o := range objs { 129 if len(o.name) == 0 { 130 return errors.New("metacacheWriter: no name provided") 131 } 132 // Indicate EOS 133 err := w.mw.WriteBool(true) 134 if err != nil { 135 return err 136 } 137 err = w.mw.WriteString(o.name) 138 if err != nil { 139 return err 140 } 141 err = w.mw.WriteBytes(o.metadata) 142 if err != nil { 143 return err 144 } 145 if w.reuseBlocks || o.reusable { 146 metaDataPoolPut(o.metadata) 147 } 148 } 149 150 return nil 151 } 152 153 // stream entries to the output. 154 // The returned channel should be closed when done. 155 // Any error is reported when closing the metacacheWriter. 156 func (w *metacacheWriter) stream() (chan<- metaCacheEntry, error) { 157 if w.creator != nil { 158 err := w.creator() 159 w.creator = nil 160 if err != nil { 161 return nil, fmt.Errorf("metacacheWriter: unable to create writer: %w", err) 162 } 163 if w.mw == nil { 164 return nil, errors.New("metacacheWriter: writer not initialized") 165 } 166 } 167 objs := make(chan metaCacheEntry, 100) 168 w.streamErr = nil 169 w.streamWg.Add(1) 170 go func() { 171 defer w.streamWg.Done() 172 for o := range objs { 173 if len(o.name) == 0 || w.streamErr != nil { 174 continue 175 } 176 // Indicate EOS 177 err := w.mw.WriteBool(true) 178 if err != nil { 179 w.streamErr = err 180 continue 181 } 182 err = w.mw.WriteString(o.name) 183 if err != nil { 184 w.streamErr = err 185 continue 186 } 187 err = w.mw.WriteBytes(o.metadata) 188 if w.reuseBlocks || o.reusable { 189 metaDataPoolPut(o.metadata) 190 } 191 if err != nil { 192 w.streamErr = err 193 continue 194 } 195 } 196 }() 197 198 return objs, nil 199 } 200 201 // Close and release resources. 202 func (w *metacacheWriter) Close() error { 203 if w == nil || w.closer == nil { 204 return nil 205 } 206 w.streamWg.Wait() 207 err := w.closer() 208 w.closer = nil 209 return err 210 } 211 212 // Reset and start writing to new writer. 213 // Close must have been called before this. 214 func (w *metacacheWriter) Reset(out io.Writer) { 215 w.streamErr = nil 216 w.creator = func() error { 217 s2w := s2.NewWriter(out, s2.WriterBlockSize(w.blockSize), s2.WriterConcurrency(2)) 218 w.mw = msgp.NewWriter(s2w) 219 w.creator = nil 220 if err := w.mw.WriteByte(metacacheStreamVersion); err != nil { 221 return err 222 } 223 224 w.closer = func() error { 225 if w.streamErr != nil { 226 return w.streamErr 227 } 228 if err := w.mw.WriteBool(false); err != nil { 229 return err 230 } 231 if err := w.mw.Flush(); err != nil { 232 return err 233 } 234 return s2w.Close() 235 } 236 return nil 237 } 238 } 239 240 var s2DecPool = sync.Pool{New: func() interface{} { 241 // Default alloc block for network transfer. 242 return s2.NewReader(nil, s2.ReaderAllocBlock(16<<10)) 243 }} 244 245 // metacacheReader allows reading a cache stream. 246 type metacacheReader struct { 247 mr *msgp.Reader 248 current metaCacheEntry 249 err error // stateful error 250 closer func() 251 creator func() error 252 } 253 254 // newMetacacheReader creates a new cache reader. 255 // Nothing will be read from the stream yet. 256 func newMetacacheReader(r io.Reader) *metacacheReader { 257 dec := s2DecPool.Get().(*s2.Reader) 258 dec.Reset(r) 259 mr := msgpNewReader(dec) 260 return &metacacheReader{ 261 mr: mr, 262 closer: func() { 263 dec.Reset(nil) 264 s2DecPool.Put(dec) 265 readMsgpReaderPoolPut(mr) 266 }, 267 creator: func() error { 268 v, err := mr.ReadByte() 269 if err != nil { 270 return err 271 } 272 switch v { 273 case 1, 2: 274 default: 275 return fmt.Errorf("metacacheReader: Unknown version: %d", v) 276 } 277 return nil 278 }, 279 } 280 } 281 282 func (r *metacacheReader) checkInit() { 283 if r.creator == nil || r.err != nil { 284 return 285 } 286 r.err = r.creator() 287 r.creator = nil 288 } 289 290 // peek will return the name of the next object. 291 // Will return io.EOF if there are no more objects. 292 // Should be used sparingly. 293 func (r *metacacheReader) peek() (metaCacheEntry, error) { 294 r.checkInit() 295 if r.err != nil { 296 return metaCacheEntry{}, r.err 297 } 298 if r.current.name != "" { 299 return r.current, nil 300 } 301 if more, err := r.mr.ReadBool(); !more { 302 switch err { 303 case nil: 304 r.err = io.EOF 305 return metaCacheEntry{}, io.EOF 306 case io.EOF: 307 r.err = io.ErrUnexpectedEOF 308 return metaCacheEntry{}, io.ErrUnexpectedEOF 309 } 310 r.err = err 311 return metaCacheEntry{}, err 312 } 313 314 var err error 315 if r.current.name, err = r.mr.ReadString(); err != nil { 316 if err == io.EOF { 317 err = io.ErrUnexpectedEOF 318 } 319 r.err = err 320 return metaCacheEntry{}, err 321 } 322 r.current.metadata, err = r.mr.ReadBytes(r.current.metadata[:0]) 323 if err == io.EOF { 324 err = io.ErrUnexpectedEOF 325 } 326 r.err = err 327 return r.current, err 328 } 329 330 // next will read one entry from the stream. 331 // Generally not recommended for fast operation. 332 func (r *metacacheReader) next() (metaCacheEntry, error) { 333 r.checkInit() 334 if r.err != nil { 335 return metaCacheEntry{}, r.err 336 } 337 var m metaCacheEntry 338 var err error 339 if r.current.name != "" { 340 m.name = r.current.name 341 m.metadata = r.current.metadata 342 r.current.name = "" 343 r.current.metadata = nil 344 return m, nil 345 } 346 if more, err := r.mr.ReadBool(); !more { 347 switch err { 348 case nil: 349 r.err = io.EOF 350 return m, io.EOF 351 case io.EOF: 352 r.err = io.ErrUnexpectedEOF 353 return m, io.ErrUnexpectedEOF 354 } 355 r.err = err 356 return m, err 357 } 358 if m.name, err = r.mr.ReadString(); err != nil { 359 if err == io.EOF { 360 err = io.ErrUnexpectedEOF 361 } 362 r.err = err 363 return m, err 364 } 365 m.metadata, err = r.mr.ReadBytes(metaDataPoolGet()) 366 if err == io.EOF { 367 err = io.ErrUnexpectedEOF 368 } 369 if len(m.metadata) == 0 && cap(m.metadata) >= metaDataReadDefault { 370 metaDataPoolPut(m.metadata) 371 m.metadata = nil 372 } 373 r.err = err 374 return m, err 375 } 376 377 // next will read one entry from the stream. 378 // Generally not recommended for fast operation. 379 func (r *metacacheReader) nextEOF() bool { 380 r.checkInit() 381 if r.err != nil { 382 return r.err == io.EOF 383 } 384 if r.current.name != "" { 385 return false 386 } 387 _, err := r.peek() 388 if err != nil { 389 r.err = err 390 return r.err == io.EOF 391 } 392 return false 393 } 394 395 // forwardTo will forward to the first entry that is >= s. 396 // Will return io.EOF if end of stream is reached without finding any. 397 func (r *metacacheReader) forwardTo(s string) error { 398 r.checkInit() 399 if r.err != nil { 400 return r.err 401 } 402 403 if s == "" { 404 return nil 405 } 406 if r.current.name != "" { 407 if r.current.name >= s { 408 return nil 409 } 410 r.current.name = "" 411 r.current.metadata = nil 412 } 413 // temporary name buffer. 414 tmp := make([]byte, 0, 256) 415 for { 416 if more, err := r.mr.ReadBool(); !more { 417 switch err { 418 case nil: 419 r.err = io.EOF 420 return io.EOF 421 case io.EOF: 422 r.err = io.ErrUnexpectedEOF 423 return io.ErrUnexpectedEOF 424 } 425 r.err = err 426 return err 427 } 428 // Read name without allocating more than 1 buffer. 429 sz, err := r.mr.ReadStringHeader() 430 if err != nil { 431 r.err = err 432 return err 433 } 434 if cap(tmp) < int(sz) { 435 tmp = make([]byte, 0, sz+256) 436 } 437 tmp = tmp[:sz] 438 _, err = r.mr.R.ReadFull(tmp) 439 if err != nil { 440 r.err = err 441 return err 442 } 443 if string(tmp) >= s { 444 r.current.name = string(tmp) 445 r.current.metadata, r.err = r.mr.ReadBytes(nil) 446 return r.err 447 } 448 // Skip metadata 449 err = r.mr.Skip() 450 if err != nil { 451 if err == io.EOF { 452 err = io.ErrUnexpectedEOF 453 } 454 r.err = err 455 return err 456 } 457 } 458 } 459 460 // readN will return all the requested number of entries in order 461 // or all if n < 0. 462 // Will return io.EOF if end of stream is reached. 463 // If requesting 0 objects nil error will always be returned regardless of at end of stream. 464 // Use peek to determine if at end of stream. 465 func (r *metacacheReader) readN(n int, inclDeleted, inclDirs, inclVersions bool, prefix string) (metaCacheEntriesSorted, error) { 466 r.checkInit() 467 if n == 0 { 468 return metaCacheEntriesSorted{}, nil 469 } 470 if r.err != nil { 471 return metaCacheEntriesSorted{}, r.err 472 } 473 474 var res metaCacheEntries 475 if n > 0 { 476 res = make(metaCacheEntries, 0, n) 477 } 478 if prefix != "" { 479 if err := r.forwardTo(prefix); err != nil { 480 return metaCacheEntriesSorted{}, err 481 } 482 } 483 next, err := r.peek() 484 if err != nil { 485 return metaCacheEntriesSorted{}, err 486 } 487 if !next.hasPrefix(prefix) { 488 return metaCacheEntriesSorted{}, io.EOF 489 } 490 491 if r.current.name != "" { 492 if (inclDeleted || !r.current.isLatestDeletemarker()) && r.current.hasPrefix(prefix) && (inclDirs || r.current.isObject()) { 493 res = append(res, r.current) 494 } 495 r.current.name = "" 496 r.current.metadata = nil 497 } 498 499 for n < 0 || len(res) < n { 500 if more, err := r.mr.ReadBool(); !more { 501 switch err { 502 case nil: 503 r.err = io.EOF 504 return metaCacheEntriesSorted{o: res}, io.EOF 505 case io.EOF: 506 r.err = io.ErrUnexpectedEOF 507 return metaCacheEntriesSorted{o: res}, io.ErrUnexpectedEOF 508 } 509 r.err = err 510 return metaCacheEntriesSorted{o: res}, err 511 } 512 var err error 513 var meta metaCacheEntry 514 if meta.name, err = r.mr.ReadString(); err != nil { 515 if err == io.EOF { 516 err = io.ErrUnexpectedEOF 517 } 518 r.err = err 519 return metaCacheEntriesSorted{o: res}, err 520 } 521 if !meta.hasPrefix(prefix) { 522 r.mr.R.Skip(1) 523 return metaCacheEntriesSorted{o: res}, io.EOF 524 } 525 if meta.metadata, err = r.mr.ReadBytes(metaDataPoolGet()); err != nil { 526 if err == io.EOF { 527 err = io.ErrUnexpectedEOF 528 } 529 r.err = err 530 return metaCacheEntriesSorted{o: res}, err 531 } 532 if len(meta.metadata) == 0 { 533 metaDataPoolPut(meta.metadata) 534 meta.metadata = nil 535 } 536 if !inclDirs && (meta.isDir() || (!inclVersions && meta.isObjectDir() && meta.isLatestDeletemarker())) { 537 continue 538 } 539 if !inclDeleted && meta.isLatestDeletemarker() && meta.isObject() && !meta.isObjectDir() { 540 continue 541 } 542 res = append(res, meta) 543 } 544 return metaCacheEntriesSorted{o: res}, nil 545 } 546 547 // readAll will return all remaining objects on the dst channel and close it when done. 548 // The context allows the operation to be canceled. 549 func (r *metacacheReader) readAll(ctx context.Context, dst chan<- metaCacheEntry) error { 550 r.checkInit() 551 if r.err != nil { 552 return r.err 553 } 554 defer xioutil.SafeClose(dst) 555 if r.current.name != "" { 556 select { 557 case <-ctx.Done(): 558 r.err = ctx.Err() 559 return ctx.Err() 560 case dst <- r.current: 561 } 562 r.current.name = "" 563 r.current.metadata = nil 564 } 565 for { 566 if more, err := r.mr.ReadBool(); !more { 567 if err == io.EOF { 568 err = io.ErrUnexpectedEOF 569 } 570 r.err = err 571 return err 572 } 573 574 var err error 575 var meta metaCacheEntry 576 if meta.name, err = r.mr.ReadString(); err != nil { 577 if err == io.EOF { 578 err = io.ErrUnexpectedEOF 579 } 580 r.err = err 581 return err 582 } 583 if meta.metadata, err = r.mr.ReadBytes(metaDataPoolGet()); err != nil { 584 if err == io.EOF { 585 err = io.ErrUnexpectedEOF 586 } 587 r.err = err 588 return err 589 } 590 if len(meta.metadata) == 0 { 591 metaDataPoolPut(meta.metadata) 592 meta.metadata = nil 593 } 594 select { 595 case <-ctx.Done(): 596 r.err = ctx.Err() 597 return ctx.Err() 598 case dst <- meta: 599 } 600 } 601 } 602 603 // readFn will return all remaining objects 604 // and provide a callback for each entry read in order 605 // as long as true is returned on the callback. 606 func (r *metacacheReader) readFn(fn func(entry metaCacheEntry) bool) error { 607 r.checkInit() 608 if r.err != nil { 609 return r.err 610 } 611 if r.current.name != "" { 612 fn(r.current) 613 r.current.name = "" 614 r.current.metadata = nil 615 } 616 for { 617 if more, err := r.mr.ReadBool(); !more { 618 switch err { 619 case io.EOF: 620 r.err = io.ErrUnexpectedEOF 621 return io.ErrUnexpectedEOF 622 case nil: 623 r.err = io.EOF 624 return io.EOF 625 } 626 return err 627 } 628 629 var err error 630 var meta metaCacheEntry 631 if meta.name, err = r.mr.ReadString(); err != nil { 632 if err == io.EOF { 633 err = io.ErrUnexpectedEOF 634 } 635 r.err = err 636 return err 637 } 638 if meta.metadata, err = r.mr.ReadBytes(nil); err != nil { 639 if err == io.EOF { 640 err = io.ErrUnexpectedEOF 641 } 642 r.err = err 643 return err 644 } 645 // Send it! 646 if !fn(meta) { 647 return nil 648 } 649 } 650 } 651 652 // readNames will return all the requested number of names in order 653 // or all if n < 0. 654 // Will return io.EOF if end of stream is reached. 655 func (r *metacacheReader) readNames(n int) ([]string, error) { 656 r.checkInit() 657 if r.err != nil { 658 return nil, r.err 659 } 660 if n == 0 { 661 return nil, nil 662 } 663 var res []string 664 if n > 0 { 665 res = make([]string, 0, n) 666 } 667 if r.current.name != "" { 668 res = append(res, r.current.name) 669 r.current.name = "" 670 r.current.metadata = nil 671 } 672 for n < 0 || len(res) < n { 673 if more, err := r.mr.ReadBool(); !more { 674 switch err { 675 case nil: 676 r.err = io.EOF 677 return res, io.EOF 678 case io.EOF: 679 r.err = io.ErrUnexpectedEOF 680 return res, io.ErrUnexpectedEOF 681 } 682 return res, err 683 } 684 685 var err error 686 var name string 687 if name, err = r.mr.ReadString(); err != nil { 688 r.err = err 689 return res, err 690 } 691 if err = r.mr.Skip(); err != nil { 692 if err == io.EOF { 693 err = io.ErrUnexpectedEOF 694 } 695 r.err = err 696 return res, err 697 } 698 res = append(res, name) 699 } 700 return res, nil 701 } 702 703 // skip n entries on the input stream. 704 // If there are less entries left io.EOF is returned. 705 func (r *metacacheReader) skip(n int) error { 706 r.checkInit() 707 if r.err != nil { 708 return r.err 709 } 710 if n <= 0 { 711 return nil 712 } 713 if r.current.name != "" { 714 n-- 715 r.current.name = "" 716 r.current.metadata = nil 717 } 718 for n > 0 { 719 if more, err := r.mr.ReadBool(); !more { 720 switch err { 721 case nil: 722 r.err = io.EOF 723 return io.EOF 724 case io.EOF: 725 r.err = io.ErrUnexpectedEOF 726 return io.ErrUnexpectedEOF 727 } 728 return err 729 } 730 731 if err := r.mr.Skip(); err != nil { 732 if err == io.EOF { 733 err = io.ErrUnexpectedEOF 734 } 735 r.err = err 736 return err 737 } 738 if err := r.mr.Skip(); err != nil { 739 if err == io.EOF { 740 err = io.ErrUnexpectedEOF 741 } 742 r.err = err 743 return err 744 } 745 n-- 746 } 747 return nil 748 } 749 750 // Close and release resources. 751 func (r *metacacheReader) Close() error { 752 if r == nil || r.closer == nil { 753 return nil 754 } 755 r.closer() 756 r.closer = nil 757 r.creator = nil 758 return nil 759 } 760 761 // metacacheBlockWriter collects blocks and provides a callaback to store them. 762 type metacacheBlockWriter struct { 763 wg sync.WaitGroup 764 streamErr error 765 blockEntries int 766 } 767 768 // newMetacacheBlockWriter provides a streaming block writer. 769 // Each block is the size of the capacity of the input channel. 770 // The caller should close to indicate the stream has ended. 771 func newMetacacheBlockWriter(in <-chan metaCacheEntry, nextBlock func(b *metacacheBlock) error) *metacacheBlockWriter { 772 w := metacacheBlockWriter{blockEntries: cap(in)} 773 w.wg.Add(1) 774 go func() { 775 defer w.wg.Done() 776 var current metacacheBlock 777 var n int 778 779 buf := bytebufferpool.Get() 780 defer func() { 781 buf.Reset() 782 bytebufferpool.Put(buf) 783 }() 784 785 block := newMetacacheWriter(buf, 1<<20) 786 defer block.Close() 787 finishBlock := func() { 788 if err := block.Close(); err != nil { 789 w.streamErr = err 790 return 791 } 792 current.data = buf.Bytes() 793 w.streamErr = nextBlock(¤t) 794 // Prepare for next 795 current.n++ 796 buf.Reset() 797 block.Reset(buf) 798 current.First = "" 799 } 800 for o := range in { 801 if len(o.name) == 0 || w.streamErr != nil { 802 continue 803 } 804 if current.First == "" { 805 current.First = o.name 806 } 807 808 if n >= w.blockEntries-1 { 809 finishBlock() 810 n = 0 811 } 812 n++ 813 814 w.streamErr = block.write(o) 815 if w.streamErr != nil { 816 continue 817 } 818 current.Last = o.name 819 } 820 if n > 0 || current.n == 0 { 821 current.EOS = true 822 finishBlock() 823 } 824 }() 825 return &w 826 } 827 828 // Close the stream. 829 // The incoming channel must be closed before calling this. 830 // Returns the first error the occurred during the writing if any. 831 func (w *metacacheBlockWriter) Close() error { 832 w.wg.Wait() 833 return w.streamErr 834 } 835 836 type metacacheBlock struct { 837 data []byte 838 n int 839 First string `json:"f"` 840 Last string `json:"l"` 841 EOS bool `json:"eos,omitempty"` 842 } 843 844 func (b metacacheBlock) headerKV() map[string]string { 845 json := jsoniter.ConfigCompatibleWithStandardLibrary 846 v, err := json.Marshal(b) 847 if err != nil { 848 logger.LogIf(context.Background(), err) // Unlikely 849 return nil 850 } 851 return map[string]string{fmt.Sprintf("%s-metacache-part-%d", ReservedMetadataPrefixLower, b.n): string(v)} 852 } 853 854 // pastPrefix returns true if the given prefix is before start of the block. 855 func (b metacacheBlock) pastPrefix(prefix string) bool { 856 if prefix == "" || strings.HasPrefix(b.First, prefix) { 857 return false 858 } 859 // We have checked if prefix matches, so we can do direct compare. 860 return b.First > prefix 861 } 862 863 // endedPrefix returns true if the given prefix ends within the block. 864 func (b metacacheBlock) endedPrefix(prefix string) bool { 865 if prefix == "" || strings.HasPrefix(b.Last, prefix) { 866 return false 867 } 868 869 // We have checked if prefix matches, so we can do direct compare. 870 return b.Last > prefix 871 }