github.com/supragya/TendermintConnector@v0.0.0-20210619045051-113e32b84fb1/_deprecated_chains/irisnet/libs/autofile/group.go (about) 1 package autofile 2 3 import ( 4 "bufio" 5 "errors" 6 "fmt" 7 "io" 8 "os" 9 "path" 10 "path/filepath" 11 "regexp" 12 "strconv" 13 "strings" 14 "sync" 15 "time" 16 17 cmn "github.com/tendermint/tendermint/libs/common" 18 ) 19 20 const ( 21 defaultGroupCheckDuration = 5000 * time.Millisecond 22 defaultHeadSizeLimit = 10 * 1024 * 1024 // 10MB 23 defaultTotalSizeLimit = 1 * 1024 * 1024 * 1024 // 1GB 24 maxFilesToRemove = 4 // needs to be greater than 1 25 ) 26 27 /* 28 You can open a Group to keep restrictions on an AutoFile, like 29 the maximum size of each chunk, and/or the total amount of bytes 30 stored in the group. 31 32 The first file to be written in the Group.Dir is the head file. 33 34 Dir/ 35 - <HeadPath> 36 37 Once the Head file reaches the size limit, it will be rotated. 38 39 Dir/ 40 - <HeadPath>.000 // First rolled file 41 - <HeadPath> // New head path, starts empty. 42 // The implicit index is 001. 43 44 As more files are written, the index numbers grow... 45 46 Dir/ 47 - <HeadPath>.000 // First rolled file 48 - <HeadPath>.001 // Second rolled file 49 - ... 50 - <HeadPath> // New head path 51 52 The Group can also be used to binary-search for some line, 53 assuming that marker lines are written occasionally. 54 */ 55 type Group struct { 56 cmn.BaseService 57 58 ID string 59 Head *AutoFile // The head AutoFile to write to 60 headBuf *bufio.Writer 61 Dir string // Directory that contains .Head 62 ticker *time.Ticker 63 mtx sync.Mutex 64 headSizeLimit int64 65 totalSizeLimit int64 66 groupCheckDuration time.Duration 67 minIndex int // Includes head 68 maxIndex int // Includes head, where Head will move to 69 70 // close this when the processTicks routine is done. 71 // this ensures we can cleanup the dir after calling Stop 72 // and the routine won't be trying to access it anymore 73 doneProcessTicks chan struct{} 74 } 75 76 // OpenGroup creates a new Group with head at headPath. It returns an error if 77 // it fails to open head file. 78 func OpenGroup(headPath string, groupOptions ...func(*Group)) (g *Group, err error) { 79 dir := path.Dir(headPath) 80 head, err := OpenAutoFile(headPath) 81 if err != nil { 82 return nil, err 83 } 84 85 g = &Group{ 86 ID: "group:" + head.ID, 87 Head: head, 88 headBuf: bufio.NewWriterSize(head, 4096*10), 89 Dir: dir, 90 headSizeLimit: defaultHeadSizeLimit, 91 totalSizeLimit: defaultTotalSizeLimit, 92 groupCheckDuration: defaultGroupCheckDuration, 93 minIndex: 0, 94 maxIndex: 0, 95 doneProcessTicks: make(chan struct{}), 96 } 97 98 for _, option := range groupOptions { 99 option(g) 100 } 101 102 g.BaseService = *cmn.NewBaseService(nil, "Group", g) 103 104 gInfo := g.readGroupInfo() 105 g.minIndex = gInfo.MinIndex 106 g.maxIndex = gInfo.MaxIndex 107 return 108 } 109 110 // GroupCheckDuration allows you to overwrite default groupCheckDuration. 111 func GroupCheckDuration(duration time.Duration) func(*Group) { 112 return func(g *Group) { 113 g.groupCheckDuration = duration 114 } 115 } 116 117 // GroupHeadSizeLimit allows you to overwrite default head size limit - 10MB. 118 func GroupHeadSizeLimit(limit int64) func(*Group) { 119 return func(g *Group) { 120 g.headSizeLimit = limit 121 } 122 } 123 124 // GroupTotalSizeLimit allows you to overwrite default total size limit of the group - 1GB. 125 func GroupTotalSizeLimit(limit int64) func(*Group) { 126 return func(g *Group) { 127 g.totalSizeLimit = limit 128 } 129 } 130 131 // OnStart implements Service by starting the goroutine that checks file and 132 // group limits. 133 func (g *Group) OnStart() error { 134 g.ticker = time.NewTicker(g.groupCheckDuration) 135 go g.processTicks() 136 return nil 137 } 138 139 // OnStop implements Service by stopping the goroutine described above. 140 // NOTE: g.Head must be closed separately using Close. 141 func (g *Group) OnStop() { 142 g.ticker.Stop() 143 g.Flush() // flush any uncommitted data 144 } 145 146 func (g *Group) Wait() { 147 // wait for processTicks routine to finish 148 <-g.doneProcessTicks 149 } 150 151 // Close closes the head file. The group must be stopped by this moment. 152 func (g *Group) Close() { 153 g.Flush() // flush any uncommitted data 154 155 g.mtx.Lock() 156 _ = g.Head.closeFile() 157 g.mtx.Unlock() 158 } 159 160 // HeadSizeLimit returns the current head size limit. 161 func (g *Group) HeadSizeLimit() int64 { 162 g.mtx.Lock() 163 defer g.mtx.Unlock() 164 return g.headSizeLimit 165 } 166 167 // TotalSizeLimit returns total size limit of the group. 168 func (g *Group) TotalSizeLimit() int64 { 169 g.mtx.Lock() 170 defer g.mtx.Unlock() 171 return g.totalSizeLimit 172 } 173 174 // MaxIndex returns index of the last file in the group. 175 func (g *Group) MaxIndex() int { 176 g.mtx.Lock() 177 defer g.mtx.Unlock() 178 return g.maxIndex 179 } 180 181 // MinIndex returns index of the first file in the group. 182 func (g *Group) MinIndex() int { 183 g.mtx.Lock() 184 defer g.mtx.Unlock() 185 return g.minIndex 186 } 187 188 func (g *Group) Write(p []byte) (nn int, err error) { 189 g.mtx.Lock() 190 defer g.mtx.Unlock() 191 return g.headBuf.Write(p) 192 } 193 194 func (g *Group) WriteLine(line string) error { 195 g.mtx.Lock() 196 defer g.mtx.Unlock() 197 _, err := g.headBuf.Write([]byte(line + "\n")) 198 return err 199 } 200 201 // Flush writes any buffered data to the underlying file and commits the 202 // current content of the file to stable storage. 203 func (g *Group) Flush() error { 204 g.mtx.Lock() 205 defer g.mtx.Unlock() 206 err := g.headBuf.Flush() 207 if err == nil { 208 err = g.Head.Sync() 209 } 210 return err 211 } 212 213 func (g *Group) processTicks() { 214 defer close(g.doneProcessTicks) 215 for { 216 select { 217 case <-g.ticker.C: 218 g.checkHeadSizeLimit() 219 g.checkTotalSizeLimit() 220 case <-g.Quit(): 221 return 222 } 223 } 224 } 225 226 // NOTE: this function is called manually in tests. 227 func (g *Group) checkHeadSizeLimit() { 228 limit := g.HeadSizeLimit() 229 if limit == 0 { 230 return 231 } 232 size, err := g.Head.Size() 233 if err != nil { 234 g.Logger.Error("Group's head may grow without bound", "head", g.Head.Path, "err", err) 235 return 236 } 237 if size >= limit { 238 g.RotateFile() 239 } 240 } 241 242 func (g *Group) checkTotalSizeLimit() { 243 limit := g.TotalSizeLimit() 244 if limit == 0 { 245 return 246 } 247 248 gInfo := g.readGroupInfo() 249 totalSize := gInfo.TotalSize 250 for i := 0; i < maxFilesToRemove; i++ { 251 index := gInfo.MinIndex + i 252 if totalSize < limit { 253 return 254 } 255 if index == gInfo.MaxIndex { 256 // Special degenerate case, just do nothing. 257 g.Logger.Error("Group's head may grow without bound", "head", g.Head.Path) 258 return 259 } 260 pathToRemove := filePathForIndex(g.Head.Path, index, gInfo.MaxIndex) 261 fInfo, err := os.Stat(pathToRemove) 262 if err != nil { 263 g.Logger.Error("Failed to fetch info for file", "file", pathToRemove) 264 continue 265 } 266 err = os.Remove(pathToRemove) 267 if err != nil { 268 g.Logger.Error("Failed to remove path", "path", pathToRemove) 269 return 270 } 271 totalSize -= fInfo.Size() 272 } 273 } 274 275 // RotateFile causes group to close the current head and assign it some index. 276 // Note it does not create a new head. 277 func (g *Group) RotateFile() { 278 g.mtx.Lock() 279 defer g.mtx.Unlock() 280 281 headPath := g.Head.Path 282 283 if err := g.headBuf.Flush(); err != nil { 284 panic(err) 285 } 286 287 if err := g.Head.Sync(); err != nil { 288 panic(err) 289 } 290 291 if err := g.Head.closeFile(); err != nil { 292 panic(err) 293 } 294 295 indexPath := filePathForIndex(headPath, g.maxIndex, g.maxIndex+1) 296 if err := os.Rename(headPath, indexPath); err != nil { 297 panic(err) 298 } 299 300 g.maxIndex++ 301 } 302 303 // NewReader returns a new group reader. 304 // CONTRACT: Caller must close the returned GroupReader. 305 func (g *Group) NewReader(index int) (*GroupReader, error) { 306 r := newGroupReader(g) 307 err := r.SetIndex(index) 308 if err != nil { 309 return nil, err 310 } 311 return r, nil 312 } 313 314 // Returns -1 if line comes after, 0 if found, 1 if line comes before. 315 type SearchFunc func(line string) (int, error) 316 317 // Searches for the right file in Group, then returns a GroupReader to start 318 // streaming lines. 319 // Returns true if an exact match was found, otherwise returns the next greater 320 // line that starts with prefix. 321 // CONTRACT: Caller must close the returned GroupReader 322 func (g *Group) Search(prefix string, cmp SearchFunc) (*GroupReader, bool, error) { 323 g.mtx.Lock() 324 minIndex, maxIndex := g.minIndex, g.maxIndex 325 g.mtx.Unlock() 326 // Now minIndex/maxIndex may change meanwhile, 327 // but it shouldn't be a big deal 328 // (maybe we'll want to limit scanUntil though) 329 330 for { 331 curIndex := (minIndex + maxIndex + 1) / 2 332 333 // Base case, when there's only 1 choice left. 334 if minIndex == maxIndex { 335 r, err := g.NewReader(maxIndex) 336 if err != nil { 337 return nil, false, err 338 } 339 match, err := scanUntil(r, prefix, cmp) 340 if err != nil { 341 r.Close() 342 return nil, false, err 343 } 344 return r, match, err 345 } 346 347 // Read starting roughly at the middle file, 348 // until we find line that has prefix. 349 r, err := g.NewReader(curIndex) 350 if err != nil { 351 return nil, false, err 352 } 353 foundIndex, line, err := scanNext(r, prefix) 354 r.Close() 355 if err != nil { 356 return nil, false, err 357 } 358 359 // Compare this line to our search query. 360 val, err := cmp(line) 361 if err != nil { 362 return nil, false, err 363 } 364 if val < 0 { 365 // Line will come later 366 minIndex = foundIndex 367 } else if val == 0 { 368 // Stroke of luck, found the line 369 r, err := g.NewReader(foundIndex) 370 if err != nil { 371 return nil, false, err 372 } 373 match, err := scanUntil(r, prefix, cmp) 374 if !match { 375 panic("Expected match to be true") 376 } 377 if err != nil { 378 r.Close() 379 return nil, false, err 380 } 381 return r, true, err 382 } else { 383 // We passed it 384 maxIndex = curIndex - 1 385 } 386 } 387 388 } 389 390 // Scans and returns the first line that starts with 'prefix' 391 // Consumes line and returns it. 392 func scanNext(r *GroupReader, prefix string) (int, string, error) { 393 for { 394 line, err := r.ReadLine() 395 if err != nil { 396 return 0, "", err 397 } 398 if !strings.HasPrefix(line, prefix) { 399 continue 400 } 401 index := r.CurIndex() 402 return index, line, nil 403 } 404 } 405 406 // Returns true iff an exact match was found. 407 // Pushes line, does not consume it. 408 func scanUntil(r *GroupReader, prefix string, cmp SearchFunc) (bool, error) { 409 for { 410 line, err := r.ReadLine() 411 if err != nil { 412 return false, err 413 } 414 if !strings.HasPrefix(line, prefix) { 415 continue 416 } 417 val, err := cmp(line) 418 if err != nil { 419 return false, err 420 } 421 if val < 0 { 422 continue 423 } else if val == 0 { 424 r.PushLine(line) 425 return true, nil 426 } else { 427 r.PushLine(line) 428 return false, nil 429 } 430 } 431 } 432 433 // Searches backwards for the last line in Group with prefix. 434 // Scans each file forward until the end to find the last match. 435 func (g *Group) FindLast(prefix string) (match string, found bool, err error) { 436 g.mtx.Lock() 437 minIndex, maxIndex := g.minIndex, g.maxIndex 438 g.mtx.Unlock() 439 440 r, err := g.NewReader(maxIndex) 441 if err != nil { 442 return "", false, err 443 } 444 defer r.Close() 445 446 // Open files from the back and read 447 GROUP_LOOP: 448 for i := maxIndex; i >= minIndex; i-- { 449 err := r.SetIndex(i) 450 if err != nil { 451 return "", false, err 452 } 453 // Scan each line and test whether line matches 454 for { 455 line, err := r.ReadLine() 456 if err == io.EOF { 457 if found { 458 return match, found, nil 459 } 460 continue GROUP_LOOP 461 } else if err != nil { 462 return "", false, err 463 } 464 if strings.HasPrefix(line, prefix) { 465 match = line 466 found = true 467 } 468 if r.CurIndex() > i { 469 if found { 470 return match, found, nil 471 } 472 continue GROUP_LOOP 473 } 474 } 475 } 476 477 return 478 } 479 480 // GroupInfo holds information about the group. 481 type GroupInfo struct { 482 MinIndex int // index of the first file in the group, including head 483 MaxIndex int // index of the last file in the group, including head 484 TotalSize int64 // total size of the group 485 HeadSize int64 // size of the head 486 } 487 488 // Returns info after scanning all files in g.Head's dir. 489 func (g *Group) ReadGroupInfo() GroupInfo { 490 g.mtx.Lock() 491 defer g.mtx.Unlock() 492 return g.readGroupInfo() 493 } 494 495 // Index includes the head. 496 // CONTRACT: caller should have called g.mtx.Lock 497 func (g *Group) readGroupInfo() GroupInfo { 498 groupDir := filepath.Dir(g.Head.Path) 499 headBase := filepath.Base(g.Head.Path) 500 var minIndex, maxIndex int = -1, -1 501 var totalSize, headSize int64 = 0, 0 502 503 dir, err := os.Open(groupDir) 504 if err != nil { 505 panic(err) 506 } 507 defer dir.Close() 508 fiz, err := dir.Readdir(0) 509 if err != nil { 510 panic(err) 511 } 512 513 // For each file in the directory, filter by pattern 514 for _, fileInfo := range fiz { 515 if fileInfo.Name() == headBase { 516 fileSize := fileInfo.Size() 517 totalSize += fileSize 518 headSize = fileSize 519 continue 520 } else if strings.HasPrefix(fileInfo.Name(), headBase) { 521 fileSize := fileInfo.Size() 522 totalSize += fileSize 523 indexedFilePattern := regexp.MustCompile(`^.+\.([0-9]{3,})$`) 524 submatch := indexedFilePattern.FindSubmatch([]byte(fileInfo.Name())) 525 if len(submatch) != 0 { 526 // Matches 527 fileIndex, err := strconv.Atoi(string(submatch[1])) 528 if err != nil { 529 panic(err) 530 } 531 if maxIndex < fileIndex { 532 maxIndex = fileIndex 533 } 534 if minIndex == -1 || fileIndex < minIndex { 535 minIndex = fileIndex 536 } 537 } 538 } 539 } 540 541 // Now account for the head. 542 if minIndex == -1 { 543 // If there were no numbered files, 544 // then the head is index 0. 545 minIndex, maxIndex = 0, 0 546 } else { 547 // Otherwise, the head file is 1 greater 548 maxIndex++ 549 } 550 return GroupInfo{minIndex, maxIndex, totalSize, headSize} 551 } 552 553 func filePathForIndex(headPath string, index int, maxIndex int) string { 554 if index == maxIndex { 555 return headPath 556 } 557 return fmt.Sprintf("%v.%03d", headPath, index) 558 } 559 560 //-------------------------------------------------------------------------------- 561 562 // GroupReader provides an interface for reading from a Group. 563 type GroupReader struct { 564 *Group 565 mtx sync.Mutex 566 curIndex int 567 curFile *os.File 568 curReader *bufio.Reader 569 curLine []byte 570 } 571 572 func newGroupReader(g *Group) *GroupReader { 573 return &GroupReader{ 574 Group: g, 575 curIndex: 0, 576 curFile: nil, 577 curReader: nil, 578 curLine: nil, 579 } 580 } 581 582 // Close closes the GroupReader by closing the cursor file. 583 func (gr *GroupReader) Close() error { 584 gr.mtx.Lock() 585 defer gr.mtx.Unlock() 586 587 if gr.curReader != nil { 588 err := gr.curFile.Close() 589 gr.curIndex = 0 590 gr.curReader = nil 591 gr.curFile = nil 592 gr.curLine = nil 593 return err 594 } 595 return nil 596 } 597 598 // Read implements io.Reader, reading bytes from the current Reader 599 // incrementing index until enough bytes are read. 600 func (gr *GroupReader) Read(p []byte) (n int, err error) { 601 lenP := len(p) 602 if lenP == 0 { 603 return 0, errors.New("given empty slice") 604 } 605 606 gr.mtx.Lock() 607 defer gr.mtx.Unlock() 608 609 // Open file if not open yet 610 if gr.curReader == nil { 611 if err = gr.openFile(gr.curIndex); err != nil { 612 return 0, err 613 } 614 } 615 616 // Iterate over files until enough bytes are read 617 var nn int 618 for { 619 nn, err = gr.curReader.Read(p[n:]) 620 n += nn 621 if err == io.EOF { 622 if n >= lenP { 623 return n, nil 624 } 625 // Open the next file 626 if err1 := gr.openFile(gr.curIndex + 1); err1 != nil { 627 return n, err1 628 } 629 } else if err != nil { 630 return n, err 631 } else if nn == 0 { // empty file 632 return n, err 633 } 634 } 635 } 636 637 // ReadLine reads a line (without delimiter). 638 // just return io.EOF if no new lines found. 639 func (gr *GroupReader) ReadLine() (string, error) { 640 gr.mtx.Lock() 641 defer gr.mtx.Unlock() 642 643 // From PushLine 644 if gr.curLine != nil { 645 line := string(gr.curLine) 646 gr.curLine = nil 647 return line, nil 648 } 649 650 // Open file if not open yet 651 if gr.curReader == nil { 652 err := gr.openFile(gr.curIndex) 653 if err != nil { 654 return "", err 655 } 656 } 657 658 // Iterate over files until line is found 659 var linePrefix string 660 for { 661 bytesRead, err := gr.curReader.ReadBytes('\n') 662 if err == io.EOF { 663 // Open the next file 664 if err1 := gr.openFile(gr.curIndex + 1); err1 != nil { 665 return "", err1 666 } 667 if len(bytesRead) > 0 && bytesRead[len(bytesRead)-1] == byte('\n') { 668 return linePrefix + string(bytesRead[:len(bytesRead)-1]), nil 669 } 670 linePrefix += string(bytesRead) 671 continue 672 } else if err != nil { 673 return "", err 674 } 675 return linePrefix + string(bytesRead[:len(bytesRead)-1]), nil 676 } 677 } 678 679 // IF index > gr.Group.maxIndex, returns io.EOF 680 // CONTRACT: caller should hold gr.mtx 681 func (gr *GroupReader) openFile(index int) error { 682 // Lock on Group to ensure that head doesn't move in the meanwhile. 683 gr.Group.mtx.Lock() 684 defer gr.Group.mtx.Unlock() 685 686 if index > gr.Group.maxIndex { 687 return io.EOF 688 } 689 690 curFilePath := filePathForIndex(gr.Head.Path, index, gr.Group.maxIndex) 691 curFile, err := os.OpenFile(curFilePath, os.O_RDONLY|os.O_CREATE, autoFilePerms) 692 if err != nil { 693 return err 694 } 695 curReader := bufio.NewReader(curFile) 696 697 // Update gr.cur* 698 if gr.curFile != nil { 699 gr.curFile.Close() 700 } 701 gr.curIndex = index 702 gr.curFile = curFile 703 gr.curReader = curReader 704 gr.curLine = nil 705 return nil 706 } 707 708 // PushLine makes the given line the current one, so the next time somebody 709 // calls ReadLine, this line will be returned. 710 // panics if called twice without calling ReadLine. 711 func (gr *GroupReader) PushLine(line string) { 712 gr.mtx.Lock() 713 defer gr.mtx.Unlock() 714 715 if gr.curLine == nil { 716 gr.curLine = []byte(line) 717 } else { 718 panic("PushLine failed, already have line") 719 } 720 } 721 722 // CurIndex returns cursor's file index. 723 func (gr *GroupReader) CurIndex() int { 724 gr.mtx.Lock() 725 defer gr.mtx.Unlock() 726 return gr.curIndex 727 } 728 729 // SetIndex sets the cursor's file index to index by opening a file at this 730 // position. 731 func (gr *GroupReader) SetIndex(index int) error { 732 gr.mtx.Lock() 733 defer gr.mtx.Unlock() 734 return gr.openFile(index) 735 } 736 737 //-------------------------------------------------------------------------------- 738 739 // A simple SearchFunc that assumes that the marker is of form 740 // <prefix><number>. 741 // For example, if prefix is '#HEIGHT:', the markers of expected to be of the form: 742 // 743 // #HEIGHT:1 744 // ... 745 // #HEIGHT:2 746 // ... 747 func MakeSimpleSearchFunc(prefix string, target int) SearchFunc { 748 return func(line string) (int, error) { 749 if !strings.HasPrefix(line, prefix) { 750 return -1, fmt.Errorf("Marker line did not have prefix: %v", prefix) 751 } 752 i, err := strconv.Atoi(line[len(prefix):]) 753 if err != nil { 754 return -1, fmt.Errorf("Failed to parse marker line: %v", err.Error()) 755 } 756 if target < i { 757 return 1, nil 758 } else if target == i { 759 return 0, nil 760 } else { 761 return -1, nil 762 } 763 } 764 }