gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/proto/refcounter.go (about) 1 package proto 2 3 import ( 4 "encoding/binary" 5 "fmt" 6 "math" 7 "os" 8 "sync" 9 10 siasync "go.sia.tech/siad/sync" 11 12 "gitlab.com/SkynetLabs/skyd/skymodules" 13 14 "gitlab.com/NebulousLabs/writeaheadlog" 15 "go.sia.tech/siad/modules" 16 17 "gitlab.com/NebulousLabs/errors" 18 ) 19 20 var ( 21 // ErrInvalidHeaderData is returned when we try to deserialize the header from 22 // a []byte with incorrect data 23 ErrInvalidHeaderData = errors.New("invalid header data") 24 25 // ErrInvalidSectorNumber is returned when the requested sector doesnt' exist 26 ErrInvalidSectorNumber = errors.New("invalid sector given - it does not exist") 27 28 // ErrInvalidVersion is returned when the version of the file we are trying to 29 // read does not match the current refCounterHeaderSize 30 ErrInvalidVersion = errors.New("invalid file version") 31 32 // ErrInvalidUpdateInstruction is returned when trying to parse a WAL update 33 // instruction that is too short to possibly contain all the required data. 34 ErrInvalidUpdateInstruction = errors.New("instructions slice is too short to contain the required data") 35 36 // ErrRefCounterNotExist is returned when there is no refcounter file with 37 // the given path 38 ErrRefCounterNotExist = errors.New("refcounter does not exist") 39 40 // ErrUpdateWithoutUpdateSession is returned when an update operation is 41 // called without an open update session 42 ErrUpdateWithoutUpdateSession = errors.New("an update operation was called without an open update session") 43 44 // ErrUpdateAfterDelete is returned when an update operation is attempted to 45 // be created after a delete 46 ErrUpdateAfterDelete = errors.New("updates cannot be created after a deletion") 47 48 // refCounterVersion defines the latest version of the refCounter 49 refCounterVersion = [8]byte{1} 50 51 // updateNameRCDelete is the name of an idempotent update that deletes a file 52 // from the disk. 53 updateNameRCDelete = "RC_DELETE" 54 55 // updateNameRCTruncate is the name of an idempotent update that truncates a 56 // refcounter file by a number of sectors. 57 updateNameRCTruncate = "RC_TRUNCATE" 58 59 // updateNameRCWriteAt is the name of an idempotent update that writes a 60 // value to a position in the file. 61 updateNameRCWriteAt = "RC_WRITE_AT" 62 ) 63 64 const ( 65 // refCounterHeaderSize is the size of the header in bytes 66 refCounterHeaderSize = 8 67 ) 68 69 type ( 70 // refCounter keeps track of how many references to each sector exist. 71 // 72 // Once the number of references drops to zero we consider the sector as 73 // garbage. We move the sector to end of the data and set the 74 // GarbageCollectionOffset to point to it. We can either reuse it to store 75 // new data or drop it from the contract at the end of the current period 76 // and before the contract renewal. 77 refCounter struct { 78 refCounterHeader 79 80 filepath string // where the refcounter is persisted on disk 81 numSectors uint64 // used for sanity checks before we attempt mutation operations 82 staticWal *writeaheadlog.WAL 83 mu sync.Mutex 84 85 // utility fields 86 staticDeps modules.Dependencies 87 88 refCounterUpdateControl 89 } 90 91 // refCounterHeader contains metadata about the reference counter file 92 refCounterHeader struct { 93 Version [8]byte 94 } 95 96 // refCounterUpdateControl is a helper struct that holds fields pertaining 97 // to the process of updating the refcounter 98 refCounterUpdateControl struct { 99 // isDeleted marks when a refcounter has been deleted and therefore 100 // cannot accept further updates 101 isDeleted bool 102 // isUpdateInProgress marks when an update session is open and updates 103 // are allowed to be created and applied 104 isUpdateInProgress bool 105 // newSectorCounts holds the new values of sector counters during an 106 // update session, so we can use them even before they are stored on 107 // disk 108 newSectorCounts map[uint64]uint16 109 110 // muUpdate serializes updates to the refcounter. It is acquired by 111 // callStartUpdate and released by callUpdateApplied. 112 muUpdate siasync.TryMutex 113 } 114 115 // u16 is a utility type for ser/des of uint16 values 116 u16 [2]byte 117 ) 118 119 // loadRefCounter loads a refcounter from disk 120 func loadRefCounter(path string, wal *writeaheadlog.WAL) (_ *refCounter, err error) { 121 // Open the file and start loading the data. 122 f, err := os.Open(path) 123 if err != nil { 124 return nil, ErrRefCounterNotExist 125 } 126 defer func() { 127 err = errors.Compose(err, f.Close()) 128 }() 129 130 var header refCounterHeader 131 headerBytes := make([]byte, refCounterHeaderSize) 132 if _, err = f.ReadAt(headerBytes, 0); err != nil { 133 return nil, errors.AddContext(err, "unable to read from file") 134 } 135 if err = deserializeHeader(headerBytes, &header); err != nil { 136 return nil, errors.AddContext(err, "unable to load refcounter header") 137 } 138 if header.Version != refCounterVersion { 139 return nil, errors.AddContext(ErrInvalidVersion, fmt.Sprintf("expected version %d, got version %d", refCounterVersion, header.Version)) 140 } 141 fi, err := os.Stat(path) 142 if err != nil { 143 return nil, errors.AddContext(err, "failed to read file stats") 144 } 145 numSectors := uint64((fi.Size() - refCounterHeaderSize) / 2) 146 return &refCounter{ 147 refCounterHeader: header, 148 filepath: path, 149 numSectors: numSectors, 150 staticWal: wal, 151 staticDeps: modules.ProdDependencies, 152 refCounterUpdateControl: refCounterUpdateControl{ 153 newSectorCounts: make(map[uint64]uint16), 154 }, 155 }, nil 156 } 157 158 // newCustomRefCounter creates a new sector reference counter file to accompany 159 // a contract file and allows setting custom dependencies 160 func newCustomRefCounter(path string, numSec uint64, wal *writeaheadlog.WAL, deps modules.Dependencies) (*refCounter, error) { 161 h := refCounterHeader{ 162 Version: refCounterVersion, 163 } 164 updateHeader := writeaheadlog.WriteAtUpdate(path, 0, serializeHeader(h)) 165 166 b := make([]byte, numSec*2) 167 for i := uint64(0); i < numSec; i++ { 168 binary.LittleEndian.PutUint16(b[i*2:i*2+2], 1) 169 } 170 updateCounters := writeaheadlog.WriteAtUpdate(path, refCounterHeaderSize, b) 171 172 err := wal.CreateAndApplyTransaction(writeaheadlog.ApplyUpdates, updateHeader, updateCounters) 173 return &refCounter{ 174 refCounterHeader: h, 175 filepath: path, 176 numSectors: numSec, 177 staticWal: wal, 178 staticDeps: deps, 179 refCounterUpdateControl: refCounterUpdateControl{ 180 newSectorCounts: make(map[uint64]uint16), 181 }, 182 }, err 183 } 184 185 // newRefCounter creates a new sector reference counter file to accompany 186 // a contract file 187 func newRefCounter(path string, numSec uint64, wal *writeaheadlog.WAL) (*refCounter, error) { 188 return newCustomRefCounter(path, numSec, wal, modules.ProdDependencies) 189 } 190 191 // callAppend appends one counter to the end of the refcounter file and 192 // initializes it with `1` 193 func (rc *refCounter) callAppend() (writeaheadlog.Update, error) { 194 rc.mu.Lock() 195 defer rc.mu.Unlock() 196 if !rc.isUpdateInProgress { 197 return writeaheadlog.Update{}, ErrUpdateWithoutUpdateSession 198 } 199 if rc.isDeleted { 200 return writeaheadlog.Update{}, ErrUpdateAfterDelete 201 } 202 rc.numSectors++ 203 rc.newSectorCounts[rc.numSectors-1] = 1 204 return createWriteAtUpdate(rc.filepath, rc.numSectors-1, 1), nil 205 } 206 207 // callCount returns the number of references to the given sector 208 func (rc *refCounter) callCount(secIdx uint64) (uint16, error) { 209 rc.mu.Lock() 210 defer rc.mu.Unlock() 211 return rc.readCount(secIdx) 212 } 213 214 // callCreateAndApplyTransaction is a helper method that creates a writeaheadlog 215 // transaction and applies it. 216 func (rc *refCounter) callCreateAndApplyTransaction(updates ...writeaheadlog.Update) error { 217 rc.mu.Lock() 218 defer rc.mu.Unlock() 219 // We allow the creation of the file here because of the case where we got 220 // interrupted during the creation of the refcounter after writing the 221 // header update to the Wal but before applying it. 222 f, err := rc.staticDeps.OpenFile(rc.filepath, os.O_CREATE|os.O_RDWR, skymodules.DefaultFilePerm) 223 if err != nil { 224 return errors.AddContext(err, "failed to open refcounter file in order to apply updates") 225 } 226 defer func() { 227 err = errors.Compose(err, f.Close()) 228 }() 229 if !rc.isUpdateInProgress { 230 return ErrUpdateWithoutUpdateSession 231 } 232 // Create the writeaheadlog transaction. 233 txn, err := rc.staticWal.NewTransaction(updates) 234 if err != nil { 235 return errors.AddContext(err, "failed to create wal txn") 236 } 237 // No extra setup is required. Signal that it is done. 238 if err := <-txn.SignalSetupComplete(); err != nil { 239 return errors.AddContext(err, "failed to signal setup completion") 240 } 241 // Starting at this point, the changes to be made are written to the disk. 242 // This means that we need to panic in case applying the updates fails in 243 // order to avoid data corruption. 244 defer func() { 245 if err != nil { 246 panic(err) 247 } 248 }() 249 // Apply the updates. 250 if err = applyUpdates(f, updates...); err != nil { 251 return errors.AddContext(err, "failed to apply updates") 252 } 253 // Updates are applied. Let the writeaheadlog know. 254 if err = txn.SignalUpdatesApplied(); err != nil { 255 return errors.AddContext(err, "failed to signal that updates are applied") 256 } 257 // If the refcounter got deleted then we're done. 258 if rc.isDeleted { 259 return nil 260 } 261 // Update the in-memory helper fields. 262 fi, err := os.Stat(rc.filepath) 263 if err != nil { 264 return errors.AddContext(err, "failed to read from disk after updates") 265 } 266 rc.numSectors = uint64((fi.Size() - refCounterHeaderSize) / 2) 267 return nil 268 } 269 270 // callDecrement decrements the reference counter of a given sector. The sector 271 // is specified by its sequential number (secIdx). 272 // Returns the updated number of references or an error. 273 func (rc *refCounter) callDecrement(secIdx uint64) (writeaheadlog.Update, error) { 274 rc.mu.Lock() 275 defer rc.mu.Unlock() 276 if !rc.isUpdateInProgress { 277 return writeaheadlog.Update{}, ErrUpdateWithoutUpdateSession 278 } 279 if rc.isDeleted { 280 return writeaheadlog.Update{}, ErrUpdateAfterDelete 281 } 282 if secIdx >= rc.numSectors { 283 return writeaheadlog.Update{}, errors.AddContext(ErrInvalidSectorNumber, "failed to decrement") 284 } 285 count, err := rc.readCount(secIdx) 286 if err != nil { 287 return writeaheadlog.Update{}, errors.AddContext(err, "failed to read count from decrement") 288 } 289 if count == 0 { 290 return writeaheadlog.Update{}, errors.New("sector count underflow") 291 } 292 count-- 293 rc.newSectorCounts[secIdx] = count 294 return createWriteAtUpdate(rc.filepath, secIdx, count), nil 295 } 296 297 // callDeleteRefCounter deletes the counter's file from disk 298 func (rc *refCounter) callDeleteRefCounter() (writeaheadlog.Update, error) { 299 rc.mu.Lock() 300 defer rc.mu.Unlock() 301 if !rc.isUpdateInProgress { 302 return writeaheadlog.Update{}, ErrUpdateWithoutUpdateSession 303 } 304 if rc.isDeleted { 305 return writeaheadlog.Update{}, ErrUpdateAfterDelete 306 } 307 // mark the refcounter as deleted and don't allow any further updates to be created 308 rc.isDeleted = true 309 return createDeleteUpdate(rc.filepath), nil 310 } 311 312 // callDropSectors removes the last numSec sector counts from the refcounter file 313 func (rc *refCounter) callDropSectors(numSec uint64) (writeaheadlog.Update, error) { 314 rc.mu.Lock() 315 defer rc.mu.Unlock() 316 if !rc.isUpdateInProgress { 317 return writeaheadlog.Update{}, ErrUpdateWithoutUpdateSession 318 } 319 if rc.isDeleted { 320 return writeaheadlog.Update{}, ErrUpdateAfterDelete 321 } 322 if numSec > rc.numSectors { 323 return writeaheadlog.Update{}, errors.AddContext(ErrInvalidSectorNumber, "failed to drop sectors") 324 } 325 rc.numSectors -= numSec 326 return createTruncateUpdate(rc.filepath, rc.numSectors), nil 327 } 328 329 // callIncrement increments the reference counter of a given sector. The sector 330 // is specified by its sequential number (secIdx). 331 // Returns the updated number of references or an error. 332 func (rc *refCounter) callIncrement(secIdx uint64) (writeaheadlog.Update, error) { 333 rc.mu.Lock() 334 defer rc.mu.Unlock() 335 if !rc.isUpdateInProgress { 336 return writeaheadlog.Update{}, ErrUpdateWithoutUpdateSession 337 } 338 if rc.isDeleted { 339 return writeaheadlog.Update{}, ErrUpdateAfterDelete 340 } 341 if secIdx >= rc.numSectors { 342 return writeaheadlog.Update{}, errors.AddContext(ErrInvalidSectorNumber, "failed to increment") 343 } 344 count, err := rc.readCount(secIdx) 345 if err != nil { 346 return writeaheadlog.Update{}, errors.AddContext(err, "failed to read count from increment") 347 } 348 if count == math.MaxUint16 { 349 return writeaheadlog.Update{}, errors.New("sector count overflow") 350 } 351 count++ 352 rc.newSectorCounts[secIdx] = count 353 return createWriteAtUpdate(rc.filepath, secIdx, count), nil 354 } 355 356 // callSetCount sets the value of the reference counter of a given sector. The 357 // sector is specified by its sequential number (secIdx). 358 func (rc *refCounter) callSetCount(secIdx uint64, c uint16) (writeaheadlog.Update, error) { 359 rc.mu.Lock() 360 defer rc.mu.Unlock() 361 if !rc.isUpdateInProgress { 362 return writeaheadlog.Update{}, ErrUpdateWithoutUpdateSession 363 } 364 if rc.isDeleted { 365 return writeaheadlog.Update{}, ErrUpdateAfterDelete 366 } 367 // this allows the client to set multiple new counts in random order 368 if secIdx >= rc.numSectors { 369 rc.numSectors = secIdx + 1 370 } 371 rc.newSectorCounts[secIdx] = c 372 return createWriteAtUpdate(rc.filepath, secIdx, c), nil 373 } 374 375 // callStartUpdate acquires a lock, ensuring the caller is the only one currently 376 // allowed to perform updates on this refcounter file. This lock is released by 377 // calling callUpdateApplied after calling callCreateAndApplyTransaction in 378 // order to apply the updates. 379 func (rc *refCounter) callStartUpdate() error { 380 rc.muUpdate.Lock() 381 return rc.managedStartUpdate() 382 } 383 384 // callSwap swaps the two sectors at the given indices 385 func (rc *refCounter) callSwap(firstIdx, secondIdx uint64) ([]writeaheadlog.Update, error) { 386 rc.mu.Lock() 387 defer rc.mu.Unlock() 388 if !rc.isUpdateInProgress { 389 return []writeaheadlog.Update{}, ErrUpdateWithoutUpdateSession 390 } 391 if rc.isDeleted { 392 return []writeaheadlog.Update{}, ErrUpdateAfterDelete 393 } 394 if firstIdx >= rc.numSectors || secondIdx >= rc.numSectors { 395 return []writeaheadlog.Update{}, errors.AddContext(ErrInvalidSectorNumber, "failed to swap sectors") 396 } 397 firstVal, err := rc.readCount(firstIdx) 398 if err != nil { 399 return []writeaheadlog.Update{}, errors.AddContext(err, "failed to read count from swap") 400 } 401 secondVal, err := rc.readCount(secondIdx) 402 if err != nil { 403 return []writeaheadlog.Update{}, errors.AddContext(err, "failed to read count from swap") 404 } 405 rc.newSectorCounts[firstIdx] = secondVal 406 rc.newSectorCounts[secondIdx] = firstVal 407 return []writeaheadlog.Update{ 408 createWriteAtUpdate(rc.filepath, firstIdx, secondVal), 409 createWriteAtUpdate(rc.filepath, secondIdx, firstVal), 410 }, nil 411 } 412 413 // callUpdateApplied cleans up temporary data and releases the update lock, thus 414 // allowing other actors to acquire it in order to update the refcounter. 415 func (rc *refCounter) callUpdateApplied() error { 416 rc.mu.Lock() 417 defer rc.mu.Unlock() 418 419 // this method cannot be called if there is no active update session 420 if !rc.isUpdateInProgress { 421 return ErrUpdateWithoutUpdateSession 422 } 423 424 // clean up the temp counts 425 rc.newSectorCounts = make(map[uint64]uint16) 426 // close the update session 427 rc.isUpdateInProgress = false 428 // release the update lock 429 rc.muUpdate.Unlock() 430 return nil 431 } 432 433 // managedStartUpdate does everything callStartUpdate needs, aside from acquiring a 434 // lock 435 func (rc *refCounter) managedStartUpdate() error { 436 rc.mu.Lock() 437 defer rc.mu.Unlock() 438 if rc.isDeleted { 439 return ErrUpdateAfterDelete 440 } 441 // open an update session 442 rc.isUpdateInProgress = true 443 return nil 444 } 445 446 // readCount reads the given sector count either from disk (if there are no 447 // pending updates) or from the in-memory cache (if there are). 448 func (rc *refCounter) readCount(secIdx uint64) (_ uint16, err error) { 449 // check if the secIdx is a valid sector index based on the number of 450 // sectors in the file 451 if secIdx >= rc.numSectors { 452 return 0, errors.AddContext(ErrInvalidSectorNumber, "failed to read count") 453 } 454 // check if the value is being changed by a pending update 455 if count, ok := rc.newSectorCounts[secIdx]; ok { 456 return count, nil 457 } 458 // read the value from disk 459 f, err := rc.staticDeps.Open(rc.filepath) 460 if err != nil { 461 return 0, errors.AddContext(err, "failed to open the refcounter file") 462 } 463 defer func() { 464 err = errors.Compose(err, f.Close()) 465 }() 466 467 var b u16 468 if _, err = f.ReadAt(b[:], int64(offset(secIdx))); err != nil { 469 return 0, errors.AddContext(err, "failed to read from refcounter file") 470 } 471 return binary.LittleEndian.Uint16(b[:]), nil 472 } 473 474 // applyUpdates takes a list of WAL updates and applies them. 475 func applyUpdates(f modules.File, updates ...writeaheadlog.Update) (err error) { 476 for _, update := range updates { 477 switch update.Name { 478 case updateNameRCDelete: 479 err = applyDeleteUpdate(update) 480 case updateNameRCTruncate: 481 err = applyTruncateUpdate(f, update) 482 case updateNameRCWriteAt: 483 err = applyWriteAtUpdate(f, update) 484 default: 485 err = fmt.Errorf("unknown update type: %v", update.Name) 486 } 487 if err != nil { 488 return err 489 } 490 } 491 return f.Sync() 492 } 493 494 // createDeleteUpdate is a helper function which creates a writeaheadlog update 495 // for deleting a given refcounter file. 496 func createDeleteUpdate(path string) writeaheadlog.Update { 497 return writeaheadlog.Update{ 498 Name: updateNameRCDelete, 499 Instructions: []byte(path), 500 } 501 } 502 503 // applyDeleteUpdate parses and applies a Delete update. 504 func applyDeleteUpdate(update writeaheadlog.Update) error { 505 if update.Name != updateNameRCDelete { 506 return fmt.Errorf("applyDeleteUpdate called on update of type %v", update.Name) 507 } 508 // Remove the file and ignore the NotExist error 509 if err := os.Remove(string(update.Instructions)); !os.IsNotExist(err) { 510 return err 511 } 512 return nil 513 } 514 515 // createTruncateUpdate is a helper function which creates a writeaheadlog 516 // update for truncating a number of sectors from the end of the file. 517 func createTruncateUpdate(path string, newNumSec uint64) writeaheadlog.Update { 518 b := make([]byte, 8+len(path)) 519 binary.LittleEndian.PutUint64(b[:8], newNumSec) 520 copy(b[8:8+len(path)], path) 521 return writeaheadlog.Update{ 522 Name: updateNameRCTruncate, 523 Instructions: b, 524 } 525 } 526 527 // applyTruncateUpdate parses and applies a Truncate update. 528 func applyTruncateUpdate(f modules.File, u writeaheadlog.Update) error { 529 if u.Name != updateNameRCTruncate { 530 return fmt.Errorf("applyAppendTruncate called on update of type %v", u.Name) 531 } 532 // Decode update. 533 _, newNumSec, err := readTruncateUpdate(u) 534 if err != nil { 535 return err 536 } 537 // Truncate the file to the needed size. 538 return f.Truncate(refCounterHeaderSize + int64(newNumSec)*2) 539 } 540 541 // createWriteAtUpdate is a helper function which creates a writeaheadlog 542 // update for swapping the values of two positions in the file. 543 func createWriteAtUpdate(path string, secIdx uint64, value uint16) writeaheadlog.Update { 544 b := make([]byte, 8+2+len(path)) 545 binary.LittleEndian.PutUint64(b[:8], secIdx) 546 binary.LittleEndian.PutUint16(b[8:10], value) 547 copy(b[10:10+len(path)], path) 548 return writeaheadlog.Update{ 549 Name: updateNameRCWriteAt, 550 Instructions: b, 551 } 552 } 553 554 // applyWriteAtUpdate parses and applies a WriteAt update. 555 func applyWriteAtUpdate(f modules.File, u writeaheadlog.Update) error { 556 if u.Name != updateNameRCWriteAt { 557 return fmt.Errorf("applyAppendWriteAt called on update of type %v", u.Name) 558 } 559 // Decode update. 560 _, secIdx, value, err := readWriteAtUpdate(u) 561 if err != nil { 562 return err 563 } 564 565 // Write the value to disk. 566 var b u16 567 binary.LittleEndian.PutUint16(b[:], value) 568 _, err = f.WriteAt(b[:], int64(offset(secIdx))) 569 return err 570 } 571 572 // deserializeHeader deserializes a header from []byte 573 func deserializeHeader(b []byte, h *refCounterHeader) error { 574 if uint64(len(b)) < refCounterHeaderSize { 575 return ErrInvalidHeaderData 576 } 577 copy(h.Version[:], b[:8]) 578 return nil 579 } 580 581 // offset calculates the byte offset of the sector counter in the file on disk 582 func offset(secIdx uint64) uint64 { 583 return refCounterHeaderSize + secIdx*2 584 } 585 586 // readTruncateUpdate decodes a Truncate update 587 func readTruncateUpdate(u writeaheadlog.Update) (path string, newNumSec uint64, err error) { 588 if len(u.Instructions) < 8 { 589 err = ErrInvalidUpdateInstruction 590 return 591 } 592 newNumSec = binary.LittleEndian.Uint64(u.Instructions[:8]) 593 path = string(u.Instructions[8:]) 594 return 595 } 596 597 // readWriteAtUpdate decodes a WriteAt update 598 func readWriteAtUpdate(u writeaheadlog.Update) (path string, secIdx uint64, value uint16, err error) { 599 if len(u.Instructions) < 10 { 600 err = ErrInvalidUpdateInstruction 601 return 602 } 603 secIdx = binary.LittleEndian.Uint64(u.Instructions[:8]) 604 value = binary.LittleEndian.Uint16(u.Instructions[8:10]) 605 path = string(u.Instructions[10:]) 606 return 607 } 608 609 // serializeHeader serializes a header to []byte 610 func serializeHeader(h refCounterHeader) []byte { 611 b := make([]byte, refCounterHeaderSize) 612 copy(b[:8], h.Version[:]) 613 return b 614 }