github.com/cryptotooltop/go-ethereum@v0.0.0-20231103184714-151d1922f3e5/core/rawdb/freezer_table.go (about) 1 // Copyright 2019 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package rawdb 18 19 import ( 20 "bytes" 21 "encoding/binary" 22 "errors" 23 "fmt" 24 "io" 25 "os" 26 "path/filepath" 27 "sync" 28 "sync/atomic" 29 30 "github.com/golang/snappy" 31 32 "github.com/scroll-tech/go-ethereum/common" 33 "github.com/scroll-tech/go-ethereum/log" 34 "github.com/scroll-tech/go-ethereum/metrics" 35 ) 36 37 var ( 38 // errClosed is returned if an operation attempts to read from or write to the 39 // freezer table after it has already been closed. 40 errClosed = errors.New("closed") 41 42 // errOutOfBounds is returned if the item requested is not contained within the 43 // freezer table. 44 errOutOfBounds = errors.New("out of bounds") 45 46 // errNotSupported is returned if the database doesn't support the required operation. 47 errNotSupported = errors.New("this operation is not supported") 48 ) 49 50 // indexEntry contains the number/id of the file that the data resides in, aswell as the 51 // offset within the file to the end of the data 52 // In serialized form, the filenum is stored as uint16. 53 type indexEntry struct { 54 filenum uint32 // stored as uint16 ( 2 bytes) 55 offset uint32 // stored as uint32 ( 4 bytes) 56 } 57 58 const indexEntrySize = 6 59 60 // unmarshalBinary deserializes binary b into the rawIndex entry. 61 func (i *indexEntry) unmarshalBinary(b []byte) error { 62 i.filenum = uint32(binary.BigEndian.Uint16(b[:2])) 63 i.offset = binary.BigEndian.Uint32(b[2:6]) 64 return nil 65 } 66 67 // append adds the encoded entry to the end of b. 68 func (i *indexEntry) append(b []byte) []byte { 69 offset := len(b) 70 out := append(b, make([]byte, indexEntrySize)...) 71 binary.BigEndian.PutUint16(out[offset:], uint16(i.filenum)) 72 binary.BigEndian.PutUint32(out[offset+2:], i.offset) 73 return out 74 } 75 76 // bounds returns the start- and end- offsets, and the file number of where to 77 // read there data item marked by the two index entries. The two entries are 78 // assumed to be sequential. 79 func (start *indexEntry) bounds(end *indexEntry) (startOffset, endOffset, fileId uint32) { 80 if start.filenum != end.filenum { 81 // If a piece of data 'crosses' a data-file, 82 // it's actually in one piece on the second data-file. 83 // We return a zero-indexEntry for the second file as start 84 return 0, end.offset, end.filenum 85 } 86 return start.offset, end.offset, end.filenum 87 } 88 89 // freezerTable represents a single chained data table within the freezer (e.g. blocks). 90 // It consists of a data file (snappy encoded arbitrary data blobs) and an indexEntry 91 // file (uncompressed 64 bit indices into the data file). 92 type freezerTable struct { 93 // WARNING: The `items` field is accessed atomically. On 32 bit platforms, only 94 // 64-bit aligned fields can be atomic. The struct is guaranteed to be so aligned, 95 // so take advantage of that (https://golang.org/pkg/sync/atomic/#pkg-note-BUG). 96 items uint64 // Number of items stored in the table (including items removed from tail) 97 98 noCompression bool // if true, disables snappy compression. Note: does not work retroactively 99 maxFileSize uint32 // Max file size for data-files 100 name string 101 path string 102 103 head *os.File // File descriptor for the data head of the table 104 files map[uint32]*os.File // open files 105 headId uint32 // number of the currently active head file 106 tailId uint32 // number of the earliest file 107 index *os.File // File descriptor for the indexEntry file of the table 108 109 // In the case that old items are deleted (from the tail), we use itemOffset 110 // to count how many historic items have gone missing. 111 itemOffset uint32 // Offset (number of discarded items) 112 113 headBytes int64 // Number of bytes written to the head file 114 readMeter metrics.Meter // Meter for measuring the effective amount of data read 115 writeMeter metrics.Meter // Meter for measuring the effective amount of data written 116 sizeGauge metrics.Gauge // Gauge for tracking the combined size of all freezer tables 117 118 logger log.Logger // Logger with database path and table name ambedded 119 lock sync.RWMutex // Mutex protecting the data file descriptors 120 } 121 122 // NewFreezerTable opens the given path as a freezer table. 123 func NewFreezerTable(path, name string, disableSnappy bool) (*freezerTable, error) { 124 return newTable(path, name, metrics.NilMeter{}, metrics.NilMeter{}, metrics.NilGauge{}, freezerTableSize, disableSnappy) 125 } 126 127 // openFreezerFileForAppend opens a freezer table file and seeks to the end 128 func openFreezerFileForAppend(filename string) (*os.File, error) { 129 // Open the file without the O_APPEND flag 130 // because it has differing behaviour during Truncate operations 131 // on different OS's 132 file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0644) 133 if err != nil { 134 return nil, err 135 } 136 // Seek to end for append 137 if _, err = file.Seek(0, io.SeekEnd); err != nil { 138 return nil, err 139 } 140 return file, nil 141 } 142 143 // openFreezerFileForReadOnly opens a freezer table file for read only access 144 func openFreezerFileForReadOnly(filename string) (*os.File, error) { 145 return os.OpenFile(filename, os.O_RDONLY, 0644) 146 } 147 148 // openFreezerFileTruncated opens a freezer table making sure it is truncated 149 func openFreezerFileTruncated(filename string) (*os.File, error) { 150 return os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) 151 } 152 153 // truncateFreezerFile resizes a freezer table file and seeks to the end 154 func truncateFreezerFile(file *os.File, size int64) error { 155 if err := file.Truncate(size); err != nil { 156 return err 157 } 158 // Seek to end for append 159 if _, err := file.Seek(0, io.SeekEnd); err != nil { 160 return err 161 } 162 return nil 163 } 164 165 // newTable opens a freezer table, creating the data and index files if they are 166 // non existent. Both files are truncated to the shortest common length to ensure 167 // they don't go out of sync. 168 func newTable(path string, name string, readMeter metrics.Meter, writeMeter metrics.Meter, sizeGauge metrics.Gauge, maxFilesize uint32, noCompression bool) (*freezerTable, error) { 169 // Ensure the containing directory exists and open the indexEntry file 170 if err := os.MkdirAll(path, 0755); err != nil { 171 return nil, err 172 } 173 var idxName string 174 if noCompression { 175 // Raw idx 176 idxName = fmt.Sprintf("%s.ridx", name) 177 } else { 178 // Compressed idx 179 idxName = fmt.Sprintf("%s.cidx", name) 180 } 181 offsets, err := openFreezerFileForAppend(filepath.Join(path, idxName)) 182 if err != nil { 183 return nil, err 184 } 185 // Create the table and repair any past inconsistency 186 tab := &freezerTable{ 187 index: offsets, 188 files: make(map[uint32]*os.File), 189 readMeter: readMeter, 190 writeMeter: writeMeter, 191 sizeGauge: sizeGauge, 192 name: name, 193 path: path, 194 logger: log.New("database", path, "table", name), 195 noCompression: noCompression, 196 maxFileSize: maxFilesize, 197 } 198 if err := tab.repair(); err != nil { 199 tab.Close() 200 return nil, err 201 } 202 // Initialize the starting size counter 203 size, err := tab.sizeNolock() 204 if err != nil { 205 tab.Close() 206 return nil, err 207 } 208 tab.sizeGauge.Inc(int64(size)) 209 210 return tab, nil 211 } 212 213 // repair cross checks the head and the index file and truncates them to 214 // be in sync with each other after a potential crash / data loss. 215 func (t *freezerTable) repair() error { 216 // Create a temporary offset buffer to init files with and read indexEntry into 217 buffer := make([]byte, indexEntrySize) 218 219 // If we've just created the files, initialize the index with the 0 indexEntry 220 stat, err := t.index.Stat() 221 if err != nil { 222 return err 223 } 224 if stat.Size() == 0 { 225 if _, err := t.index.Write(buffer); err != nil { 226 return err 227 } 228 } 229 // Ensure the index is a multiple of indexEntrySize bytes 230 if overflow := stat.Size() % indexEntrySize; overflow != 0 { 231 truncateFreezerFile(t.index, stat.Size()-overflow) // New file can't trigger this path 232 } 233 // Retrieve the file sizes and prepare for truncation 234 if stat, err = t.index.Stat(); err != nil { 235 return err 236 } 237 offsetsSize := stat.Size() 238 239 // Open the head file 240 var ( 241 firstIndex indexEntry 242 lastIndex indexEntry 243 contentSize int64 244 contentExp int64 245 ) 246 // Read index zero, determine what file is the earliest 247 // and what item offset to use 248 t.index.ReadAt(buffer, 0) 249 firstIndex.unmarshalBinary(buffer) 250 251 t.tailId = firstIndex.filenum 252 t.itemOffset = firstIndex.offset 253 254 t.index.ReadAt(buffer, offsetsSize-indexEntrySize) 255 lastIndex.unmarshalBinary(buffer) 256 t.head, err = t.openFile(lastIndex.filenum, openFreezerFileForAppend) 257 if err != nil { 258 return err 259 } 260 if stat, err = t.head.Stat(); err != nil { 261 return err 262 } 263 contentSize = stat.Size() 264 265 // Keep truncating both files until they come in sync 266 contentExp = int64(lastIndex.offset) 267 268 for contentExp != contentSize { 269 // Truncate the head file to the last offset pointer 270 if contentExp < contentSize { 271 t.logger.Warn("Truncating dangling head", "indexed", common.StorageSize(contentExp), "stored", common.StorageSize(contentSize)) 272 if err := truncateFreezerFile(t.head, contentExp); err != nil { 273 return err 274 } 275 contentSize = contentExp 276 } 277 // Truncate the index to point within the head file 278 if contentExp > contentSize { 279 t.logger.Warn("Truncating dangling indexes", "indexed", common.StorageSize(contentExp), "stored", common.StorageSize(contentSize)) 280 if err := truncateFreezerFile(t.index, offsetsSize-indexEntrySize); err != nil { 281 return err 282 } 283 offsetsSize -= indexEntrySize 284 t.index.ReadAt(buffer, offsetsSize-indexEntrySize) 285 var newLastIndex indexEntry 286 newLastIndex.unmarshalBinary(buffer) 287 // We might have slipped back into an earlier head-file here 288 if newLastIndex.filenum != lastIndex.filenum { 289 // Release earlier opened file 290 t.releaseFile(lastIndex.filenum) 291 if t.head, err = t.openFile(newLastIndex.filenum, openFreezerFileForAppend); err != nil { 292 return err 293 } 294 if stat, err = t.head.Stat(); err != nil { 295 // TODO, anything more we can do here? 296 // A data file has gone missing... 297 return err 298 } 299 contentSize = stat.Size() 300 } 301 lastIndex = newLastIndex 302 contentExp = int64(lastIndex.offset) 303 } 304 } 305 // Ensure all reparation changes have been written to disk 306 if err := t.index.Sync(); err != nil { 307 return err 308 } 309 if err := t.head.Sync(); err != nil { 310 return err 311 } 312 // Update the item and byte counters and return 313 t.items = uint64(t.itemOffset) + uint64(offsetsSize/indexEntrySize-1) // last indexEntry points to the end of the data file 314 t.headBytes = contentSize 315 t.headId = lastIndex.filenum 316 317 // Close opened files and preopen all files 318 if err := t.preopen(); err != nil { 319 return err 320 } 321 t.logger.Debug("Chain freezer table opened", "items", t.items, "size", common.StorageSize(t.headBytes)) 322 return nil 323 } 324 325 // preopen opens all files that the freezer will need. This method should be called from an init-context, 326 // since it assumes that it doesn't have to bother with locking 327 // The rationale for doing preopen is to not have to do it from within Retrieve, thus not needing to ever 328 // obtain a write-lock within Retrieve. 329 func (t *freezerTable) preopen() (err error) { 330 // The repair might have already opened (some) files 331 t.releaseFilesAfter(0, false) 332 // Open all except head in RDONLY 333 for i := t.tailId; i < t.headId; i++ { 334 if _, err = t.openFile(i, openFreezerFileForReadOnly); err != nil { 335 return err 336 } 337 } 338 // Open head in read/write 339 t.head, err = t.openFile(t.headId, openFreezerFileForAppend) 340 return err 341 } 342 343 // truncate discards any recent data above the provided threshold number. 344 func (t *freezerTable) truncate(items uint64) error { 345 t.lock.Lock() 346 defer t.lock.Unlock() 347 348 // If our item count is correct, don't do anything 349 existing := atomic.LoadUint64(&t.items) 350 if existing <= items { 351 return nil 352 } 353 // We need to truncate, save the old size for metrics tracking 354 oldSize, err := t.sizeNolock() 355 if err != nil { 356 return err 357 } 358 // Something's out of sync, truncate the table's offset index 359 log := t.logger.Debug 360 if existing > items+1 { 361 log = t.logger.Warn // Only loud warn if we delete multiple items 362 } 363 log("Truncating freezer table", "items", existing, "limit", items) 364 if err := truncateFreezerFile(t.index, int64(items+1)*indexEntrySize); err != nil { 365 return err 366 } 367 // Calculate the new expected size of the data file and truncate it 368 buffer := make([]byte, indexEntrySize) 369 if _, err := t.index.ReadAt(buffer, int64(items*indexEntrySize)); err != nil { 370 return err 371 } 372 var expected indexEntry 373 expected.unmarshalBinary(buffer) 374 375 // We might need to truncate back to older files 376 if expected.filenum != t.headId { 377 // If already open for reading, force-reopen for writing 378 t.releaseFile(expected.filenum) 379 newHead, err := t.openFile(expected.filenum, openFreezerFileForAppend) 380 if err != nil { 381 return err 382 } 383 // Release any files _after the current head -- both the previous head 384 // and any files which may have been opened for reading 385 t.releaseFilesAfter(expected.filenum, true) 386 // Set back the historic head 387 t.head = newHead 388 t.headId = expected.filenum 389 } 390 if err := truncateFreezerFile(t.head, int64(expected.offset)); err != nil { 391 return err 392 } 393 // All data files truncated, set internal counters and return 394 t.headBytes = int64(expected.offset) 395 atomic.StoreUint64(&t.items, items) 396 397 // Retrieve the new size and update the total size counter 398 newSize, err := t.sizeNolock() 399 if err != nil { 400 return err 401 } 402 t.sizeGauge.Dec(int64(oldSize - newSize)) 403 404 return nil 405 } 406 407 // Close closes all opened files. 408 func (t *freezerTable) Close() error { 409 t.lock.Lock() 410 defer t.lock.Unlock() 411 412 var errs []error 413 if err := t.index.Close(); err != nil { 414 errs = append(errs, err) 415 } 416 t.index = nil 417 418 for _, f := range t.files { 419 if err := f.Close(); err != nil { 420 errs = append(errs, err) 421 } 422 } 423 t.head = nil 424 425 if errs != nil { 426 return fmt.Errorf("%v", errs) 427 } 428 return nil 429 } 430 431 // openFile assumes that the write-lock is held by the caller 432 func (t *freezerTable) openFile(num uint32, opener func(string) (*os.File, error)) (f *os.File, err error) { 433 var exist bool 434 if f, exist = t.files[num]; !exist { 435 var name string 436 if t.noCompression { 437 name = fmt.Sprintf("%s.%04d.rdat", t.name, num) 438 } else { 439 name = fmt.Sprintf("%s.%04d.cdat", t.name, num) 440 } 441 f, err = opener(filepath.Join(t.path, name)) 442 if err != nil { 443 return nil, err 444 } 445 t.files[num] = f 446 } 447 return f, err 448 } 449 450 // releaseFile closes a file, and removes it from the open file cache. 451 // Assumes that the caller holds the write lock 452 func (t *freezerTable) releaseFile(num uint32) { 453 if f, exist := t.files[num]; exist { 454 delete(t.files, num) 455 f.Close() 456 } 457 } 458 459 // releaseFilesAfter closes all open files with a higher number, and optionally also deletes the files 460 func (t *freezerTable) releaseFilesAfter(num uint32, remove bool) { 461 for fnum, f := range t.files { 462 if fnum > num { 463 delete(t.files, fnum) 464 f.Close() 465 if remove { 466 os.Remove(f.Name()) 467 } 468 } 469 } 470 } 471 472 // getIndices returns the index entries for the given from-item, covering 'count' items. 473 // N.B: The actual number of returned indices for N items will always be N+1 (unless an 474 // error is returned). 475 // OBS: This method assumes that the caller has already verified (and/or trimmed) the range 476 // so that the items are within bounds. If this method is used to read out of bounds, 477 // it will return error. 478 func (t *freezerTable) getIndices(from, count uint64) ([]*indexEntry, error) { 479 // Apply the table-offset 480 from = from - uint64(t.itemOffset) 481 // For reading N items, we need N+1 indices. 482 buffer := make([]byte, (count+1)*indexEntrySize) 483 if _, err := t.index.ReadAt(buffer, int64(from*indexEntrySize)); err != nil { 484 return nil, err 485 } 486 var ( 487 indices []*indexEntry 488 offset int 489 ) 490 for i := from; i <= from+count; i++ { 491 index := new(indexEntry) 492 index.unmarshalBinary(buffer[offset:]) 493 offset += indexEntrySize 494 indices = append(indices, index) 495 } 496 if from == 0 { 497 // Special case if we're reading the first item in the freezer. We assume that 498 // the first item always start from zero(regarding the deletion, we 499 // only support deletion by files, so that the assumption is held). 500 // This means we can use the first item metadata to carry information about 501 // the 'global' offset, for the deletion-case 502 indices[0].offset = 0 503 indices[0].filenum = indices[1].filenum 504 } 505 return indices, nil 506 } 507 508 // Retrieve looks up the data offset of an item with the given number and retrieves 509 // the raw binary blob from the data file. 510 func (t *freezerTable) Retrieve(item uint64) ([]byte, error) { 511 items, err := t.RetrieveItems(item, 1, 0) 512 if err != nil { 513 return nil, err 514 } 515 return items[0], nil 516 } 517 518 // RetrieveItems returns multiple items in sequence, starting from the index 'start'. 519 // It will return at most 'max' items, but will abort earlier to respect the 520 // 'maxBytes' argument. However, if the 'maxBytes' is smaller than the size of one 521 // item, it _will_ return one element and possibly overflow the maxBytes. 522 func (t *freezerTable) RetrieveItems(start, count, maxBytes uint64) ([][]byte, error) { 523 // First we read the 'raw' data, which might be compressed. 524 diskData, sizes, err := t.retrieveItems(start, count, maxBytes) 525 if err != nil { 526 return nil, err 527 } 528 var ( 529 output = make([][]byte, 0, count) 530 offset int // offset for reading 531 outputSize int // size of uncompressed data 532 ) 533 // Now slice up the data and decompress. 534 for i, diskSize := range sizes { 535 item := diskData[offset : offset+diskSize] 536 offset += diskSize 537 decompressedSize := diskSize 538 if !t.noCompression { 539 decompressedSize, _ = snappy.DecodedLen(item) 540 } 541 if i > 0 && uint64(outputSize+decompressedSize) > maxBytes { 542 break 543 } 544 if !t.noCompression { 545 data, err := snappy.Decode(nil, item) 546 if err != nil { 547 return nil, err 548 } 549 output = append(output, data) 550 } else { 551 output = append(output, item) 552 } 553 outputSize += decompressedSize 554 } 555 return output, nil 556 } 557 558 // retrieveItems reads up to 'count' items from the table. It reads at least 559 // one item, but otherwise avoids reading more than maxBytes bytes. 560 // It returns the (potentially compressed) data, and the sizes. 561 func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []int, error) { 562 t.lock.RLock() 563 defer t.lock.RUnlock() 564 565 // Ensure the table and the item is accessible 566 if t.index == nil || t.head == nil { 567 return nil, nil, errClosed 568 } 569 itemCount := atomic.LoadUint64(&t.items) // max number 570 // Ensure the start is written, not deleted from the tail, and that the 571 // caller actually wants something 572 if itemCount <= start || uint64(t.itemOffset) > start || count == 0 { 573 return nil, nil, errOutOfBounds 574 } 575 if start+count > itemCount { 576 count = itemCount - start 577 } 578 var ( 579 output = make([]byte, maxBytes) // Buffer to read data into 580 outputSize int // Used size of that buffer 581 ) 582 // readData is a helper method to read a single data item from disk. 583 readData := func(fileId, start uint32, length int) error { 584 // In case a small limit is used, and the elements are large, may need to 585 // realloc the read-buffer when reading the first (and only) item. 586 if len(output) < length { 587 output = make([]byte, length) 588 } 589 dataFile, exist := t.files[fileId] 590 if !exist { 591 return fmt.Errorf("missing data file %d", fileId) 592 } 593 if _, err := dataFile.ReadAt(output[outputSize:outputSize+length], int64(start)); err != nil { 594 return err 595 } 596 outputSize += length 597 return nil 598 } 599 // Read all the indexes in one go 600 indices, err := t.getIndices(start, count) 601 if err != nil { 602 return nil, nil, err 603 } 604 var ( 605 sizes []int // The sizes for each element 606 totalSize = 0 // The total size of all data read so far 607 readStart = indices[0].offset // Where, in the file, to start reading 608 unreadSize = 0 // The size of the as-yet-unread data 609 ) 610 611 for i, firstIndex := range indices[:len(indices)-1] { 612 secondIndex := indices[i+1] 613 // Determine the size of the item. 614 offset1, offset2, _ := firstIndex.bounds(secondIndex) 615 size := int(offset2 - offset1) 616 // Crossing a file boundary? 617 if secondIndex.filenum != firstIndex.filenum { 618 // If we have unread data in the first file, we need to do that read now. 619 if unreadSize > 0 { 620 if err := readData(firstIndex.filenum, readStart, unreadSize); err != nil { 621 return nil, nil, err 622 } 623 unreadSize = 0 624 } 625 readStart = 0 626 } 627 if i > 0 && uint64(totalSize+size) > maxBytes { 628 // About to break out due to byte limit being exceeded. We don't 629 // read this last item, but we need to do the deferred reads now. 630 if unreadSize > 0 { 631 if err := readData(secondIndex.filenum, readStart, unreadSize); err != nil { 632 return nil, nil, err 633 } 634 } 635 break 636 } 637 // Defer the read for later 638 unreadSize += size 639 totalSize += size 640 sizes = append(sizes, size) 641 if i == len(indices)-2 || uint64(totalSize) > maxBytes { 642 // Last item, need to do the read now 643 if err := readData(secondIndex.filenum, readStart, unreadSize); err != nil { 644 return nil, nil, err 645 } 646 break 647 } 648 } 649 return output[:outputSize], sizes, nil 650 } 651 652 // has returns an indicator whether the specified number data 653 // exists in the freezer table. 654 func (t *freezerTable) has(number uint64) bool { 655 return atomic.LoadUint64(&t.items) > number 656 } 657 658 // size returns the total data size in the freezer table. 659 func (t *freezerTable) size() (uint64, error) { 660 t.lock.RLock() 661 defer t.lock.RUnlock() 662 663 return t.sizeNolock() 664 } 665 666 // sizeNolock returns the total data size in the freezer table without obtaining 667 // the mutex first. 668 func (t *freezerTable) sizeNolock() (uint64, error) { 669 stat, err := t.index.Stat() 670 if err != nil { 671 return 0, err 672 } 673 total := uint64(t.maxFileSize)*uint64(t.headId-t.tailId) + uint64(t.headBytes) + uint64(stat.Size()) 674 return total, nil 675 } 676 677 // advanceHead should be called when the current head file would outgrow the file limits, 678 // and a new file must be opened. The caller of this method must hold the write-lock 679 // before calling this method. 680 func (t *freezerTable) advanceHead() error { 681 t.lock.Lock() 682 defer t.lock.Unlock() 683 684 // We open the next file in truncated mode -- if this file already 685 // exists, we need to start over from scratch on it. 686 nextID := t.headId + 1 687 newHead, err := t.openFile(nextID, openFreezerFileTruncated) 688 if err != nil { 689 return err 690 } 691 692 // Close old file, and reopen in RDONLY mode. 693 t.releaseFile(t.headId) 694 t.openFile(t.headId, openFreezerFileForReadOnly) 695 696 // Swap out the current head. 697 t.head = newHead 698 t.headBytes = 0 699 t.headId = nextID 700 return nil 701 } 702 703 // Sync pushes any pending data from memory out to disk. This is an expensive 704 // operation, so use it with care. 705 func (t *freezerTable) Sync() error { 706 if err := t.index.Sync(); err != nil { 707 return err 708 } 709 return t.head.Sync() 710 } 711 712 // DumpIndex is a debug print utility function, mainly for testing. It can also 713 // be used to analyse a live freezer table index. 714 func (t *freezerTable) DumpIndex(start, stop int64) { 715 t.dumpIndex(os.Stdout, start, stop) 716 } 717 718 func (t *freezerTable) dumpIndexString(start, stop int64) string { 719 var out bytes.Buffer 720 out.WriteString("\n") 721 t.dumpIndex(&out, start, stop) 722 return out.String() 723 } 724 725 func (t *freezerTable) dumpIndex(w io.Writer, start, stop int64) { 726 buf := make([]byte, indexEntrySize) 727 728 fmt.Fprintf(w, "| number | fileno | offset |\n") 729 fmt.Fprintf(w, "|--------|--------|--------|\n") 730 731 for i := uint64(start); ; i++ { 732 if _, err := t.index.ReadAt(buf, int64(i*indexEntrySize)); err != nil { 733 break 734 } 735 var entry indexEntry 736 entry.unmarshalBinary(buf) 737 fmt.Fprintf(w, "| %03d | %03d | %03d | \n", i, entry.filenum, entry.offset) 738 if stop > 0 && i >= uint64(stop) { 739 break 740 } 741 } 742 fmt.Fprintf(w, "|--------------------------|\n") 743 }