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