github.com/balzaczyy/golucene@v0.0.0-20151210033525-d0be9ee89713/core/index/segmentInfos.go (about) 1 package index 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "github.com/balzaczyy/golucene/core/codec" 8 . "github.com/balzaczyy/golucene/core/codec/spi" 9 . "github.com/balzaczyy/golucene/core/index/model" 10 "github.com/balzaczyy/golucene/core/store" 11 "github.com/balzaczyy/golucene/core/util" 12 "strconv" 13 "strings" 14 ) 15 16 /* Prints the given message to the infoStream. */ 17 func message(format string, args ...interface{}) { 18 fmt.Printf("SIS: %v\n", fmt.Sprintf(format, args...)) 19 } 20 21 type FindSegmentsFile struct { 22 directory store.Directory 23 doBody func(segmentFileName string) (interface{}, error) 24 defaultGenLookaheadCount int 25 } 26 27 func NewFindSegmentsFile(directory store.Directory, 28 doBody func(segmentFileName string) (interface{}, error)) *FindSegmentsFile { 29 return &FindSegmentsFile{directory, doBody, 10} 30 } 31 32 // TODO support IndexCommit 33 func (fsf *FindSegmentsFile) run(commit IndexCommit) (interface{}, error) { 34 // fmt.Println("Finding segments file...") 35 if commit != nil { 36 if fsf.directory != commit.Directory() { 37 return nil, errors.New("the specified commit does not match the specified Directory") 38 } 39 return fsf.doBody(commit.SegmentsFileName()) 40 } 41 42 lastGen := int64(-1) 43 gen := int64(0) 44 genLookaheadCount := 0 45 var exc error 46 retryCount := 0 47 48 useFirstMethod := true 49 50 // Loop until we succeed in calling doBody() without 51 // hitting an IOException. An IOException most likely 52 // means a commit was in process and has finished, in 53 // the time it took us to load the now-old infos files 54 // (and segments files). It's also possible it's a 55 // true error (corrupt index). To distinguish these, 56 // on each retry we must see "forward progress" on 57 // which generation we are trying to load. If we 58 // don't, then the original error is real and we throw 59 // it. 60 61 // We have three methods for determining the current 62 // generation. We try the first two in parallel (when 63 // useFirstMethod is true), and fall back to the third 64 // when necessary. 65 66 for { 67 // fmt.Println("Trying...") 68 if useFirstMethod { 69 // fmt.Println("Trying first method...") 70 // List the directory and use the highest 71 // segments_N file. This method works well as long 72 // as there is no stale caching on the directory 73 // contents (NOTE: NFS clients often have such stale 74 // caching): 75 genA := int64(-1) 76 77 files, err := fsf.directory.ListAll() 78 if err != nil { 79 return nil, err 80 } 81 if files != nil { 82 genA = LastCommitGeneration(files) 83 } 84 85 // message("directory listing genA=%v", genA) 86 87 // Also open segments.gen and read its 88 // contents. Then we take the larger of the two 89 // gens. This way, if either approach is hitting 90 // a stale cache (NFS) we have a better chance of 91 // getting the right generation. 92 genB := int64(-1) 93 genInput, err := fsf.directory.OpenChecksumInput(INDEX_FILENAME_SEGMENTS_GEN, store.IO_CONTEXT_READ) 94 if err != nil { 95 message("segments.gen open: %v", err) 96 } else { 97 defer genInput.Close() 98 // fmt.Println("Reading segments info...") 99 100 var version int32 101 if version, err = genInput.ReadInt(); err != nil { 102 return nil, err 103 } 104 // fmt.Printf("Version: %v\n", version) 105 if version == FORMAT_SEGMENTS_GEN_47 || version == FORMAT_SEGMENTS_GEN_CURRENT { 106 // fmt.Println("Version is current.") 107 var gen0, gen1 int64 108 if gen0, err = genInput.ReadLong(); err != nil { 109 return nil, err 110 } 111 if gen1, err = genInput.ReadLong(); err != nil { 112 return nil, err 113 } 114 message("fallback check: %v; %v", gen0, gen1) 115 if version == FORMAT_SEGMENTS_GEN_CHECKSUM { 116 if _, err = codec.CheckFooter(genInput); err != nil { 117 return nil, err 118 } 119 } else { 120 if err = codec.CheckEOF(genInput); err != nil { 121 return nil, err 122 } 123 } 124 if gen0 == gen1 { 125 // The file is consistent. 126 genB = gen0 127 } 128 } else { 129 return nil, codec.NewIndexFormatTooNewError(genInput, version, 130 FORMAT_SEGMENTS_GEN_CURRENT, FORMAT_SEGMENTS_GEN_CURRENT) 131 } 132 } 133 134 message("%v check: genB=%v", INDEX_FILENAME_SEGMENTS_GEN, genB) 135 136 // Pick the larger of the two gen's: 137 gen = genA 138 if genB > gen { 139 gen = genB 140 } 141 142 if gen == -1 { 143 // Neither approach found a generation 144 return nil, errors.New(fmt.Sprintf("no segments* file found in %v: files: %#v", fsf.directory, files)) 145 } 146 } 147 148 if useFirstMethod && lastGen == gen && retryCount >= 2 { 149 // Give up on first method -- this is 3rd cycle on 150 // listing directory and checking gen file to 151 // attempt to locate the segments file. 152 useFirstMethod = false 153 } 154 155 // Second method: since both directory cache and 156 // file contents cache seem to be stale, just 157 // advance the generation. 158 if !useFirstMethod { 159 if genLookaheadCount < fsf.defaultGenLookaheadCount { 160 gen++ 161 genLookaheadCount++ 162 message("look ahead increment gen to %v", gen) 163 } else { 164 // All attempts have failed -- throw first exc: 165 return nil, exc 166 } 167 } else if lastGen == gen { 168 // This means we're about to try the same 169 // segments_N last tried. 170 retryCount++ 171 } else { 172 // Segment file has advanced since our last loop 173 // (we made "progress"), so reset retryCount: 174 retryCount = 0 175 } 176 177 lastGen = gen 178 segmentFileName := util.FileNameFromGeneration(INDEX_FILENAME_SEGMENTS, "", gen) 179 // fmt.Printf("SegmentFileName: %v\n", segmentFileName) 180 181 var v interface{} 182 var err error 183 if v, err = fsf.doBody(segmentFileName); err == nil { 184 message("success on %v", segmentFileName) 185 return v, nil 186 } 187 // Save the original root cause: 188 if exc == nil { 189 exc = err 190 } 191 192 message("primary Exception on '%v': %v; will retry: retryCount = %v; gen = %v", 193 segmentFileName, err, retryCount, gen) 194 195 if gen > 1 && useFirstMethod && retryCount == 1 { 196 // This is our second time trying this same segments 197 // file (because retryCount is 1), and, there is 198 // possibly a segments_(N-1) (because gen > 1). 199 // So, check if the segments_(N-1) exists and 200 // try it if so: 201 prevSegmentFileName := util.FileNameFromGeneration(INDEX_FILENAME_SEGMENTS, "", gen-1) 202 203 if prevExists := fsf.directory.FileExists(prevSegmentFileName); prevExists { 204 message("fallback to prior segment file '%v'", prevSegmentFileName) 205 if v, err = fsf.doBody(prevSegmentFileName); err != nil { 206 message("secondary Exception on '%v': %v; will retry", prevSegmentFileName, err) 207 } else { 208 message("success on fallback %v", prevSegmentFileName) 209 return v, nil 210 } 211 } 212 } 213 } 214 } 215 216 // index/SegmentInfos.java 217 218 const ( 219 VERSION_40 = 0 220 VERSION_46 = 1 221 VERSION_48 = 2 222 VERSION_49 = 3 223 224 // Used for the segments.gen file only! 225 // Whenver you add a new format, make it 1 smaller (negative version logic)! 226 FORMAT_SEGMENTS_GEN_47 = -2 227 FORMAT_SEGMENTS_GEN_CHECKSUM = -3 228 FORMAT_SEGMENTS_GEN_START = FORMAT_SEGMENTS_GEN_47 229 // Current format of segments.gen 230 FORMAT_SEGMENTS_GEN_CURRENT = FORMAT_SEGMENTS_GEN_CHECKSUM 231 ) 232 233 /* 234 A collection of segmentInfo objects with methods for operating on 235 those segments in relation to the file system. 236 237 The active segments in the index are stored in the segment into file, 238 segments_N. There may be one or more segments_N files in the index; 239 however, hte one with the largest generation is the activbe one (when 240 older segments_N files are present it's because they temporarily 241 cannot be deleted, or, a writer is in the process of committing, or a 242 custom IndexDeletionPolicy is in use). This file lists each segment 243 by name and has details about the codec and generation of deletes. 244 245 There is also a file segments.gen. This file contains the current 246 generation (the _N in segments_N) of the index. This is used only as 247 a fallback in case the current generation cannot be accurately 248 determined by directory listing alone (as is the case for some NFS 249 clients with time-based directory cache expiration). This file simply 250 contains an Int32 version header (FORMAT_SEGMENTS_GEN_CURRENT), 251 followed by the generation recorded as int64, written twice. 252 253 Files: 254 255 - segments.gen: GenHeader, Generation, Generation, Footer 256 - segments_N: Header, Version, NameCounter, SegCount, 257 <SegName, SegCodec, DelGen, DeletionCount, FieldInfosGen, 258 DocValuesGen, UpdatesFiles>^SegCount, CommitUserData, Footer 259 260 Data types: 261 262 - Header --> CodecHeader 263 - Genheader, NameCounter, SegCount, DeletionCount --> int32 264 - Generation, Version, DelGen, Checksum --> int64 265 - SegName, SegCodec --> string 266 - CommitUserData --> map[string]string 267 - UpdatesFiles --> map[int32]map[string]bool> 268 - Footer --> CodecFooter 269 270 Field Descriptions: 271 272 - Version counts how often the index has been changed by adding or 273 deleting docments. 274 - NameCounter is used to generate names for new segment files. 275 - SegName is the name of the segment, and is used as the file name 276 prefix for all of the files that compose the segment's index. 277 - DelGen is the generation count of the deletes file. If this is -1, 278 there are no deletes. Anything above zero means there are deletes 279 stored by LiveDocsFormat. 280 - DeletionCount records the number of deleted documents in this segment. 281 - SegCodec is the nme of the Codec that encoded this segment. 282 - CommitUserData stores an optional user-spplied opaue 283 map[string]string that was passed to SetCommitData(). 284 - FieldInfosGen is the generation count of the fieldInfos file. If 285 this is -1, there are no updates to the fieldInfos in that segment. 286 Anything above zero means there are updates to the fieldInfos 287 stored by FieldInfosFormat. 288 - DocValuesGen is the generation count of the updatable DocValues. If 289 this is -1, there are no udpates to DocValues in that segment. 290 Anything above zero means there are updates to DocValues stored by 291 DocvaluesFormat. 292 - UpdatesFiles stores the set of files that were updated in that 293 segment per file. 294 */ 295 type SegmentInfos struct { 296 counter int 297 version int64 298 generation int64 299 lastGeneration int64 300 userData map[string]string 301 Segments []*SegmentCommitInfo 302 303 // Only non-nil after prepareCommit has been called and before 304 // finishCommit is called 305 pendingSegnOutput store.IndexOutput 306 } 307 308 func LastCommitGeneration(files []string) int64 { 309 if files == nil { 310 return int64(-1) 311 } 312 max := int64(-1) 313 for _, file := range files { 314 if strings.HasPrefix(file, INDEX_FILENAME_SEGMENTS) && file != INDEX_FILENAME_SEGMENTS_GEN { 315 gen := GenerationFromSegmentsFileName(file) 316 if gen > max { 317 max = gen 318 } 319 } 320 } 321 return max 322 } 323 324 func (sis *SegmentInfos) SegmentsFileName() string { 325 return util.FileNameFromGeneration(util.SEGMENTS, "", sis.lastGeneration) 326 } 327 328 func GenerationFromSegmentsFileName(fileName string) int64 { 329 switch { 330 case fileName == INDEX_FILENAME_SEGMENTS: 331 return int64(0) 332 case strings.HasPrefix(fileName, INDEX_FILENAME_SEGMENTS): 333 d, err := strconv.ParseInt(fileName[1+len(INDEX_FILENAME_SEGMENTS):], 36, 64) 334 if err != nil { 335 panic(err) 336 } 337 return d 338 default: 339 panic(fmt.Sprintf("filename %v is not a segments file", fileName)) 340 } 341 } 342 343 /* 344 A utility for writing the SEGMENTS_GEN file to a Directory. 345 346 NOTE: this is an internal utility which is kept public so that it's 347 accessible by code from other packages. You should avoid calling this 348 method unless you're absolutely sure what you're doing! 349 */ 350 func writeSegmentsGen(dir store.Directory, generation int64) { 351 if err := func() (err error) { 352 var genOutput store.IndexOutput 353 genOutput, err = dir.CreateOutput(INDEX_FILENAME_SEGMENTS_GEN, store.IO_CONTEXT_READONCE) 354 if err != nil { 355 return err 356 } 357 358 defer func() { 359 err = mergeError(err, genOutput.Close()) 360 err = mergeError(err, dir.Sync([]string{INDEX_FILENAME_SEGMENTS_GEN})) 361 }() 362 363 if err = genOutput.WriteInt(FORMAT_SEGMENTS_GEN_CURRENT); err == nil { 364 if err = genOutput.WriteLong(generation); err == nil { 365 if err = genOutput.WriteLong(generation); err == nil { 366 err = codec.WriteFooter(genOutput) 367 } 368 } 369 } 370 return err 371 }(); err != nil { 372 // It's OK if we fail to write this file since it's used only as 373 // one of the retry fallbacks. 374 dir.DeleteFile(INDEX_FILENAME_SEGMENTS_GEN) 375 // Ignore error; this file is only used in a retry fallback on init 376 } 377 } 378 379 /* Get the next segments_N filename that will be written. */ 380 func (sis *SegmentInfos) nextSegmentFilename() string { 381 var nextGeneration int64 382 if sis.generation == -1 { 383 nextGeneration = 1 384 } else { 385 nextGeneration = sis.generation + 1 386 } 387 return util.FileNameFromGeneration(util.SEGMENTS, "", nextGeneration) 388 } 389 390 /* 391 Read a particular segmentFileName. Note that this may return IO error 392 if a commit is in process. 393 */ 394 func (sis *SegmentInfos) Read(directory store.Directory, segmentFileName string) (err error) { 395 // fmt.Printf("Reading segment info from %v...\n", segmentFileName) 396 397 // Clear any previous segments: 398 sis.Clear() 399 400 sis.generation = GenerationFromSegmentsFileName(segmentFileName) 401 sis.lastGeneration = sis.generation 402 403 var input store.ChecksumIndexInput 404 if input, err = directory.OpenChecksumInput(segmentFileName, store.IO_CONTEXT_READ); err != nil { 405 return 406 } 407 408 var success = false 409 defer func() { 410 if !success { 411 // Clear any segment infos we had loaded so we 412 // have a clean slate on retry: 413 sis.Clear() 414 util.CloseWhileSuppressingError(input) 415 } else { 416 err = input.Close() 417 } 418 }() 419 420 var format int 421 if format, err = asInt(input.ReadInt()); err != nil { 422 return 423 } 424 425 var actualFormat int 426 if format == codec.CODEC_MAGIC { 427 // 4.0+ 428 if actualFormat, err = asInt(codec.CheckHeaderNoMagic(input, "segments", VERSION_40, VERSION_49)); err != nil { 429 return 430 } 431 if sis.version, err = input.ReadLong(); err != nil { 432 return 433 } 434 if sis.counter, err = asInt(input.ReadInt()); err != nil { 435 return 436 } 437 var numSegments int 438 if numSegments, err = asInt(input.ReadInt()); err != nil { 439 return 440 } else if numSegments < 0 { 441 return errors.New(fmt.Sprintf("invalid segment count: %v (resource: %v)", numSegments, input)) 442 } 443 var segName, codecName string 444 var fCodec Codec 445 var delGen, fieldInfosGen, dvGen int64 446 var delCount int 447 for seg := 0; seg < numSegments; seg++ { 448 if segName, err = input.ReadString(); err != nil { 449 return 450 } 451 if codecName, err = input.ReadString(); err != nil { 452 return 453 } 454 fCodec = LoadCodec(codecName) 455 assert2(fCodec != nil, "Invalid codec name: %v", codecName) 456 // fmt.Printf("SIS.read seg=%v codec=%v\n", seg, fCodec) 457 var info *SegmentInfo 458 if info, err = fCodec.SegmentInfoFormat().SegmentInfoReader().Read(directory, segName, store.IO_CONTEXT_READ); err != nil { 459 return 460 } 461 info.SetCodec(fCodec) 462 if delGen, err = input.ReadLong(); err != nil { 463 return 464 } 465 if delCount, err = asInt(input.ReadInt()); err != nil { 466 return 467 } else if delCount < 0 || delCount > info.DocCount() { 468 return errors.New(fmt.Sprintf( 469 "invalid deletion count: %v vs docCount=%v (resource: %v)", 470 delCount, info.DocCount(), input)) 471 } 472 fieldInfosGen = -1 473 if actualFormat >= VERSION_46 { 474 if fieldInfosGen, err = input.ReadLong(); err != nil { 475 return 476 } 477 } 478 dvGen = -1 479 if actualFormat >= VERSION_49 { 480 if dvGen, err = input.ReadLong(); err != nil { 481 return 482 } 483 } else { 484 dvGen = fieldInfosGen 485 } 486 siPerCommit := NewSegmentCommitInfo(info, delCount, delGen, fieldInfosGen, dvGen) 487 if actualFormat >= VERSION_46 { 488 if actualFormat < VERSION_49 { 489 panic("not implemented yet") 490 } else { 491 var ss map[string]bool 492 if ss, err = input.ReadStringSet(); err != nil { 493 return err 494 } 495 siPerCommit.SetFieldInfosFiles(ss) 496 var dvUpdatesFiles map[int]map[string]bool 497 var numDVFields int 498 if numDVFields, err = asInt(input.ReadInt()); err != nil { 499 return err 500 } 501 if numDVFields == 0 { 502 dvUpdatesFiles = make(map[int]map[string]bool) 503 } else { 504 panic("not implemented yet") 505 } 506 siPerCommit.SetDocValuesUpdatesFiles(dvUpdatesFiles) 507 } 508 } 509 sis.Segments = append(sis.Segments, siPerCommit) 510 } 511 if sis.userData, err = input.ReadStringStringMap(); err != nil { 512 return err 513 } 514 } else { 515 // TODO support <4.0 index 516 panic("Index format pre-4.0 not supported yet") 517 } 518 519 if actualFormat >= VERSION_48 { 520 if _, err = codec.CheckFooter(input); err != nil { 521 return 522 } 523 } else { 524 var checksumNow = int64(input.Checksum()) 525 var checksumThen int64 526 if checksumThen, err = input.ReadLong(); err != nil { 527 return 528 } 529 if checksumNow != checksumThen { 530 return errors.New(fmt.Sprintf( 531 "checksum mismatch in segments file: %v vs %v (resource: %v)", 532 checksumNow, checksumThen, input)) 533 } 534 if err = codec.CheckEOF(input); err != nil { 535 return 536 } 537 } 538 539 success = true 540 return nil 541 } 542 543 func asInt(n int32, err error) (int, error) { 544 if err != nil { 545 return 0, err 546 } 547 return int(n), nil 548 } 549 550 func (sis *SegmentInfos) ReadAll(directory store.Directory) error { 551 sis.generation, sis.lastGeneration = -1, -1 552 _, err := NewFindSegmentsFile(directory, func(segmentFileName string) (obj interface{}, err error) { 553 err = sis.Read(directory, segmentFileName) 554 return nil, err 555 }).run(nil) 556 return err 557 } 558 559 func (sis *SegmentInfos) write(directory store.Directory) (err error) { 560 segmentsFilename := sis.nextSegmentFilename() 561 562 // Always advance the generation on write: 563 if sis.generation == -1 { 564 sis.generation = 1 565 } else { 566 sis.generation++ 567 } 568 569 var segnOutput store.IndexOutput 570 var success = false 571 // var upgradedSIFiles = make(map[string]bool) 572 573 defer func() { 574 if !success { 575 // We hit an error above; try to close the file but suppress 576 // any errors 577 util.CloseWhileSuppressingError(segnOutput) 578 579 // for filename, _ := range upgradedSIFiles { 580 // directory.DeleteFile(filename) // ignore error 581 // } 582 583 // Try not to leave a truncated segments_N fle in the index: 584 directory.DeleteFile(segmentsFilename) // ignore error 585 } 586 }() 587 588 if segnOutput, err = directory.CreateOutput(segmentsFilename, store.IO_CONTEXT_DEFAULT); err != nil { 589 return 590 } 591 if err = codec.WriteHeader(segnOutput, "segments", VERSION_49); err != nil { 592 return 593 } 594 if err = segnOutput.WriteLong(sis.version); err == nil { 595 if err = segnOutput.WriteInt(int32(sis.counter)); err == nil { 596 err = segnOutput.WriteInt(int32(len(sis.Segments))) 597 } 598 } 599 if err != nil { 600 return 601 } 602 for _, siPerCommit := range sis.Segments { 603 si := siPerCommit.Info 604 if err = segnOutput.WriteString(si.Name); err == nil { 605 if err = segnOutput.WriteString(si.Codec().(Codec).Name()); err == nil { 606 if err = segnOutput.WriteLong(siPerCommit.DelGen()); err == nil { 607 assert2(siPerCommit.DelCount() >= 0 && siPerCommit.DelCount() <= si.DocCount(), 608 "cannot write segment: invalid docCount segment=%v docCount=%v delCount=%v", 609 si.Name, si.DocCount(), siPerCommit.DelCount()) 610 if err = segnOutput.WriteInt(int32(siPerCommit.DelCount())); err == nil { 611 if err = segnOutput.WriteLong(siPerCommit.FieldInfosGen()); err == nil { 612 if err = segnOutput.WriteLong(siPerCommit.DocValuesGen()); err == nil { 613 if err = segnOutput.WriteStringSet(siPerCommit.FieldInfosFiles()); err == nil { 614 dvUpdatesFiles := siPerCommit.DocValuesUpdatesFiles() 615 if err = segnOutput.WriteInt(int32(len(dvUpdatesFiles))); err == nil { 616 for k, v := range dvUpdatesFiles { 617 if err = segnOutput.WriteInt(int32(k)); err != nil { 618 break 619 } 620 if err = segnOutput.WriteStringSet(v); err != nil { 621 break 622 } 623 } 624 } 625 } 626 } 627 } 628 } 629 } 630 } 631 } 632 if err != nil { 633 return 634 } 635 assert(si.Dir == directory) 636 637 // If this segment is pre-4.x, perform a one-time "upgrade" to 638 // write the .si file for it: 639 if version := si.Version(); len(version) == 0 || !version.OnOrAfter(util.VERSION_4_0) { 640 panic("not implemented yet") 641 } 642 } 643 if err = segnOutput.WriteStringStringMap(sis.userData); err != nil { 644 return 645 } 646 sis.pendingSegnOutput = segnOutput 647 success = true 648 return nil 649 } 650 651 // func versionLess(a, b string) bool { 652 // parts1 := strings.Split(a, ".") 653 // parts2 := strings.Split(b, ".") 654 // for i, v := range parts1 { 655 // n1, _ := strconv.Atoi(v) 656 // if i < len(parts2) { 657 // if n2, _ := strconv.Atoi(parts2[i]); n1 != n2 { 658 // return n1 < n2 659 // } 660 // } else if n1 != 0 { 661 // // a has some extra trailing tokens. 662 // // if these are all zeroes, that's ok. 663 // return false 664 // } 665 // } 666 667 // // b has some extra trailing tokens. 668 // // if these are all zeroes, that's ok. 669 // for i := len(parts1); i < len(parts2); i++ { 670 // if n, _ := strconv.Atoi(parts2[i]); n != 0 { 671 // return true 672 // } 673 // } 674 675 // return false 676 // } 677 678 /* 679 Returns a copy of this instance, also copying each SegmentInfo. 680 */ 681 func (sis *SegmentInfos) Clone() *SegmentInfos { 682 return sis.clone(false) 683 } 684 685 func (sis *SegmentInfos) clone(cloneSegmentInfo bool) *SegmentInfos { 686 clone := &SegmentInfos{ 687 counter: sis.counter, 688 version: sis.version, 689 generation: sis.generation, 690 lastGeneration: sis.lastGeneration, 691 userData: make(map[string]string), 692 Segments: nil, 693 } 694 for _, info := range sis.Segments { 695 assert(info.Info.Codec() != nil) 696 clone.Segments = append(clone.Segments, info.CloneDeep(cloneSegmentInfo)) 697 } 698 for k, v := range sis.userData { 699 clone.userData[k] = v 700 } 701 return clone 702 } 703 704 // L873 705 /* Carry over generation numbers from another SegmentInfos */ 706 func (sis *SegmentInfos) updateGeneration(other *SegmentInfos) { 707 sis.lastGeneration = other.lastGeneration 708 sis.generation = other.generation 709 } 710 711 func (sis *SegmentInfos) rollbackCommit(dir store.Directory) { 712 if sis.pendingSegnOutput != nil { 713 // Suppress so we keep throwing the original error in our caller 714 util.CloseWhileSuppressingError(sis.pendingSegnOutput) 715 sis.pendingSegnOutput = nil 716 717 // Must carefully compute filename from "generation" since 718 // lastGeneration isn't incremented: 719 segmentFilename := util.FileNameFromGeneration(INDEX_FILENAME_SEGMENTS, "", sis.generation) 720 721 // Suppress so we keep throwing the original error in our caller 722 util.DeleteFilesIgnoringErrors(dir, segmentFilename) 723 } 724 } 725 726 func (sis *SegmentInfos) toString(dir store.Directory) string { 727 var buffer bytes.Buffer 728 buffer.WriteString(sis.SegmentsFileName()) 729 buffer.WriteString(":") 730 for _, info := range sis.Segments { 731 buffer.WriteString(info.StringOf(dir, 0)) 732 } 733 return buffer.String() 734 } 735 736 /* 737 Call this to start a commit. This writes the new segments file, but 738 writes an invalid checksum at the end, so that it is not visible to 739 readers. Once this is called you must call finishCommit() to complete 740 the commit or rollbackCommit() to abort it. 741 742 Note: changed() should be called prior to this method if changes have 743 been made to this SegmentInfos instance. 744 */ 745 func (sis *SegmentInfos) prepareCommit(dir store.Directory) error { 746 assert2(sis.pendingSegnOutput == nil, "prepareCommit was already called") 747 return sis.write(dir) 748 } 749 750 /* 751 Returns all file names referenced by SegmentInfo instances matching 752 the provided Directory (ie files associated with any "external" 753 segments are skipped). The returned collection is recomputed on each 754 invocation. 755 */ 756 func (sis *SegmentInfos) files(dir store.Directory, includeSegmentsFile bool) []string { 757 files := make(map[string]bool) 758 if includeSegmentsFile { 759 if segmentFileName := sis.SegmentsFileName(); segmentFileName != "" { 760 files[segmentFileName] = true 761 } 762 } 763 for _, info := range sis.Segments { 764 assert(info.Info.Dir == dir) 765 // if info.Info.dir == dir { 766 for _, file := range info.Files() { 767 files[file] = true 768 } 769 // } 770 } 771 var res = make([]string, 0, len(files)) 772 for file, _ := range files { 773 res = append(res, file) 774 } 775 return res 776 } 777 778 func (sis *SegmentInfos) finishCommit(dir store.Directory) (fileName string, err error) { 779 assert(dir != nil) 780 assert2(sis.pendingSegnOutput != nil, "prepareCommit was not called") 781 if err = func() error { 782 var success = false 783 defer func() { 784 if !success { 785 // Closes pendingSegnOutput & delets partial segments_N: 786 sis.rollbackCommit(dir) 787 } else { 788 err := func() error { 789 var success = false 790 defer func() { 791 if !success { 792 // Closes pendingSegnOutput & delets partial segments_N: 793 sis.rollbackCommit(dir) 794 } else { 795 sis.pendingSegnOutput = nil 796 } 797 }() 798 799 err := sis.pendingSegnOutput.Close() 800 success = err == nil 801 return err 802 }() 803 assertn(err == nil, "%v", err) // no shadow 804 } 805 }() 806 807 if err := codec.WriteFooter(sis.pendingSegnOutput); err != nil { 808 return err 809 } 810 success = true 811 return nil 812 }(); err != nil { 813 return 814 } 815 816 // NOTE: if we crash here, we have left a segments_N file in the 817 // directory in a possibly corrupt state (if some bytes made it to 818 // stable storage and others didn't). But, the segments_N file 819 // includes checksum at the end, which should catch this case. So 820 // when a reader tries to read it, it will return an index corrupt 821 // error, which should cause the retry logic in SegmentInfos to 822 // kick in and load the last good (previous) segments_N-1 file. 823 824 fileName = util.FileNameFromGeneration(INDEX_FILENAME_SEGMENTS, "", sis.generation) 825 if err = func() error { 826 var success = false 827 defer func() { 828 if !success { 829 dir.DeleteFile(fileName) 830 // suppress error so we keep returning the original error 831 } 832 }() 833 834 err := dir.Sync([]string{fileName}) 835 success = err == nil 836 return err 837 }(); err != nil { 838 return 839 } 840 841 sis.lastGeneration = sis.generation 842 writeSegmentsGen(dir, sis.generation) 843 return 844 } 845 846 // L1041 847 /* 848 Replaces all segments in this instance in this instance, but keeps 849 generation, version, counter so that future commits remain write once. 850 */ 851 func (sis *SegmentInfos) replace(other *SegmentInfos) { 852 sis.rollbackSegmentInfos(other.Segments) 853 sis.lastGeneration = other.lastGeneration 854 } 855 856 func (sis *SegmentInfos) changed() { 857 sis.version++ 858 } 859 860 func (sis *SegmentInfos) createBackupSegmentInfos() []*SegmentCommitInfo { 861 ans := make([]*SegmentCommitInfo, len(sis.Segments)) 862 for i, info := range sis.Segments { 863 assert(info.Info.Codec() != nil) 864 ans[i] = info.Clone() 865 } 866 return ans 867 } 868 869 // L1104 870 func (sis *SegmentInfos) rollbackSegmentInfos(infos []*SegmentCommitInfo) { 871 if cap(sis.Segments) < len(infos) { 872 sis.Segments = make([]*SegmentCommitInfo, len(infos)) 873 copy(sis.Segments, infos) 874 } else { 875 n := len(sis.Segments) 876 copy(sis.Segments, infos) 877 for i, limit := len(infos), n; i < limit; i++ { 878 sis.Segments[i] = nil 879 } 880 sis.Segments = sis.Segments[0:len(infos)] 881 } 882 } 883 884 func (sis *SegmentInfos) Clear() { 885 for i, _ := range sis.Segments { 886 sis.Segments[i] = nil 887 } 888 sis.Segments = sis.Segments[:0] // reuse existing space 889 } 890 891 /* 892 Remove the provided SegmentCommitInfo. 893 894 WARNING: O(N) cost 895 */ 896 func (sis *SegmentInfos) remove(si *SegmentCommitInfo) { 897 panic("not implemented yet") 898 }