mirror of
https://github.com/XTXMarkets/ternfs.git
synced 2025-12-19 09:40:05 -06:00
312 lines
8.7 KiB
Go
312 lines
8.7 KiB
Go
package lib
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
"xtx/eggsfs/msgs"
|
|
)
|
|
|
|
type ScrubState struct {
|
|
Migrate MigrateStats
|
|
CheckedBlocks uint64
|
|
CheckedBytes uint64
|
|
WorkersQueuesSize [256]uint64
|
|
CheckQueuesSize [256]uint64
|
|
DecommissionedBlocks uint64
|
|
Cursors [256]msgs.InodeId
|
|
Cycles [256]uint32
|
|
}
|
|
|
|
type ScrubOptions struct {
|
|
NumWorkersPerShard int // how many goroutienes should be sending check request to the block services
|
|
WorkersQueueSize int
|
|
}
|
|
|
|
func badBlockError(err error) bool {
|
|
return err == msgs.BAD_BLOCK_CRC || err == msgs.BLOCK_NOT_FOUND || err == msgs.BLOCK_IO_ERROR_FILE
|
|
}
|
|
|
|
func scrubFileInternal(
|
|
log *Logger,
|
|
client *Client,
|
|
bufPool *BufPool,
|
|
stats *ScrubState,
|
|
timeStats *timeStats,
|
|
progressReportAlert *XmonNCAlert,
|
|
scratchFile *scratchFile,
|
|
scrubbingMu *sync.Mutex,
|
|
file msgs.InodeId,
|
|
) error {
|
|
log.Debug("starting to scrub file %v", file)
|
|
scrubbingMu.Lock() // scrubbings don't work in parallel
|
|
defer scrubbingMu.Unlock()
|
|
|
|
badBlock := func(blockService *msgs.BlockService, blockSize uint32, block *msgs.FetchedBlock) (bool, error) {
|
|
err := client.CheckBlock(log, blockService, block.BlockId, blockSize, block.Crc)
|
|
if badBlockError(err) {
|
|
log.RaiseAlert("found bad block, block service %v, block %v: %v", blockService.Id, block.BlockId, err)
|
|
return true, nil
|
|
}
|
|
return false, err
|
|
}
|
|
return migrateBlocksInFileGeneric(log, client, bufPool, &stats.Migrate, timeStats, progressReportAlert, "scrubbed", badBlock, scratchFile, file)
|
|
}
|
|
|
|
type scrubRequest struct {
|
|
file msgs.InodeId
|
|
blockService msgs.BlockService
|
|
block msgs.BlockId
|
|
size uint32
|
|
crc msgs.Crc
|
|
}
|
|
|
|
func scrubWorker(
|
|
log *Logger,
|
|
client *Client,
|
|
opts *ScrubOptions,
|
|
stats *ScrubState,
|
|
rateLimit *RateLimit,
|
|
shid msgs.ShardId,
|
|
workerChan chan *scrubRequest,
|
|
terminateChan chan any,
|
|
scratchFile *scratchFile,
|
|
scrubbingMu *sync.Mutex,
|
|
) {
|
|
bufPool := NewBufPool()
|
|
blockNotFoundAlert := log.NewNCAlert(0)
|
|
defer log.ClearNC(blockNotFoundAlert)
|
|
for {
|
|
req := <-workerChan
|
|
if req == nil {
|
|
log.Debug("worker for shard %v terminating", shid)
|
|
workerChan <- nil // terminate the other workers, too
|
|
log.Debug("worker for shard %v terminated", shid)
|
|
return
|
|
}
|
|
if rateLimit != nil {
|
|
rateLimit.Acquire()
|
|
}
|
|
atomic.StoreUint64(&stats.WorkersQueuesSize[shid], uint64(len(workerChan)))
|
|
canIgnoreError := req.blockService.Flags.HasAny(msgs.EGGSFS_BLOCK_SERVICE_STALE | msgs.EGGSFS_BLOCK_SERVICE_DECOMMISSIONED | msgs.EGGSFS_BLOCK_SERVICE_NO_WRITE)
|
|
if req.blockService.Flags.HasAny(msgs.EGGSFS_BLOCK_SERVICE_DECOMMISSIONED) {
|
|
atomic.AddUint64(&stats.DecommissionedBlocks, 1)
|
|
}
|
|
err := client.CheckBlock(log, &req.blockService, req.block, req.size, req.crc)
|
|
if badBlockError(err) {
|
|
atomic.AddUint64(&stats.CheckedBlocks, 1)
|
|
if err == msgs.BAD_BLOCK_CRC {
|
|
atomic.AddUint64(&stats.CheckedBytes, uint64(req.size))
|
|
}
|
|
for attempts := 1; ; attempts++ {
|
|
if err := scrubFileInternal(log, client, bufPool, stats, nil, nil, scratchFile, scrubbingMu, req.file); err != nil {
|
|
if err == msgs.BLOCK_NOT_FOUND {
|
|
log.RaiseNC(blockNotFoundAlert, "could not migrate blocks in file %v after %v attempts because a block was not found in it. this is probably due to conflicts with other migrations or scrubbing. will retry in one second.", req.file, attempts)
|
|
time.Sleep(time.Second)
|
|
} else {
|
|
log.Info("could not scrub file %v, will terminate: %v", req.file, err)
|
|
select {
|
|
case terminateChan <- err:
|
|
default:
|
|
}
|
|
return
|
|
}
|
|
} else {
|
|
log.ClearNC(blockNotFoundAlert)
|
|
break
|
|
}
|
|
}
|
|
} else if err == msgs.BLOCK_IO_ERROR_DEVICE {
|
|
// This is almost certainly a broken server. We want migration to take
|
|
// care of this -- otherwise the scrubber will be stuck on tons of
|
|
// these errors.
|
|
log.Info("got IO error for file %v (block %v, block service %v), ignoring: %v", req.file, req.block, req.blockService.Id, err)
|
|
} else if err != nil {
|
|
if canIgnoreError {
|
|
log.Debug("could not check block %v in file %v block service %v, but can ignore error (block service is probably decommissioned): %v", req.block, req.file, req.blockService.Id, err)
|
|
} else {
|
|
log.Info("could not check block %v for file %v, will terminate: %v", req.block, req.file, err)
|
|
select {
|
|
case terminateChan <- err:
|
|
default:
|
|
}
|
|
return
|
|
}
|
|
} else {
|
|
atomic.AddUint64(&stats.CheckedBlocks, 1)
|
|
atomic.AddUint64(&stats.CheckedBytes, uint64(req.size))
|
|
}
|
|
}
|
|
}
|
|
|
|
func scrubScraper(
|
|
log *Logger,
|
|
client *Client,
|
|
stats *ScrubState,
|
|
shid msgs.ShardId,
|
|
terminateChan chan any,
|
|
workerChan chan *scrubRequest,
|
|
) {
|
|
fileReq := &msgs.VisitFilesReq{BeginId: stats.Cursors[shid]}
|
|
fileResp := &msgs.VisitFilesResp{}
|
|
for {
|
|
if err := client.ShardRequest(log, msgs.ShardId(shid), fileReq, fileResp); err != nil {
|
|
log.Info("could not get files: %v", err)
|
|
select {
|
|
case terminateChan <- err:
|
|
default:
|
|
}
|
|
return
|
|
}
|
|
log.Debug("will migrate %d files", len(fileResp.Ids))
|
|
for _, file := range fileResp.Ids {
|
|
spansReq := msgs.FileSpansReq{FileId: file}
|
|
spansResp := msgs.FileSpansResp{}
|
|
for {
|
|
if err := client.ShardRequest(log, file.Shard(), &spansReq, &spansResp); err != nil {
|
|
log.Info("could not get spans: %v", err)
|
|
select {
|
|
case terminateChan <- err:
|
|
default:
|
|
}
|
|
return
|
|
}
|
|
for _, span := range spansResp.Spans {
|
|
if span.Header.StorageClass == msgs.INLINE_STORAGE {
|
|
continue
|
|
}
|
|
body := span.Body.(*msgs.FetchedBlocksSpan)
|
|
for _, block := range body.Blocks {
|
|
size := body.CellSize * uint32(body.Stripes)
|
|
blockService := spansResp.BlockServices[block.BlockServiceIx]
|
|
workerChan <- &scrubRequest{
|
|
file: file,
|
|
blockService: blockService,
|
|
block: block.BlockId,
|
|
size: size,
|
|
crc: block.Crc,
|
|
}
|
|
}
|
|
}
|
|
spansReq.ByteOffset = spansResp.NextOffset
|
|
if spansReq.ByteOffset == 0 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
stats.Cursors[shid] = fileResp.NextId
|
|
fileReq.BeginId = fileResp.NextId
|
|
if fileReq.BeginId == 0 {
|
|
// this will terminate all the workers
|
|
log.Debug("file scraping done for shard %v, terminating workers", shid)
|
|
atomic.AddUint32(&stats.Cycles[shid], 1)
|
|
workerChan <- nil
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func ScrubFile(
|
|
log *Logger,
|
|
client *Client,
|
|
stats *ScrubState,
|
|
file msgs.InodeId,
|
|
) error {
|
|
bufPool := NewBufPool()
|
|
scratchFile := scratchFile{}
|
|
keepAlive := startToKeepScratchFileAlive(log, client, &scratchFile)
|
|
defer keepAlive.stop()
|
|
var scrubbingMu sync.Mutex
|
|
return scrubFileInternal(log, client, bufPool, stats, nil, nil, &scratchFile, &scrubbingMu, file)
|
|
}
|
|
|
|
func ScrubFiles(
|
|
log *Logger,
|
|
client *Client,
|
|
opts *ScrubOptions,
|
|
rateLimit *RateLimit,
|
|
stats *ScrubState,
|
|
shid msgs.ShardId,
|
|
) error {
|
|
if opts.NumWorkersPerShard <= 0 {
|
|
panic(fmt.Errorf("the number of senders should be positive, got %v", opts.NumWorkersPerShard))
|
|
}
|
|
log.Info("starting to scrub files for shard %v", shid)
|
|
terminateChan := make(chan any, 1)
|
|
sendChan := make(chan *scrubRequest, opts.WorkersQueueSize)
|
|
scratchFile := &scratchFile{}
|
|
keepAlive := startToKeepScratchFileAlive(log, client, scratchFile)
|
|
defer func() {
|
|
keepAlive.stop()
|
|
}()
|
|
|
|
go func() {
|
|
defer func() { HandleRecoverChan(log, terminateChan, recover()) }()
|
|
scrubScraper(log, client, stats, shid, terminateChan, sendChan)
|
|
}()
|
|
|
|
var workersWg sync.WaitGroup
|
|
workersWg.Add(opts.NumWorkersPerShard)
|
|
var scrubbingMu sync.Mutex
|
|
for i := 0; i < opts.NumWorkersPerShard; i++ {
|
|
go func() {
|
|
defer func() { HandleRecoverChan(log, terminateChan, recover()) }()
|
|
scrubWorker(log, client, opts, stats, rateLimit, shid, sendChan, terminateChan, scratchFile, &scrubbingMu)
|
|
workersWg.Done()
|
|
}()
|
|
}
|
|
go func() {
|
|
workersWg.Wait()
|
|
log.Info("all workers terminated for shard %v, we're done", shid)
|
|
select {
|
|
case terminateChan <- nil:
|
|
default:
|
|
}
|
|
}()
|
|
|
|
err := <-terminateChan
|
|
if err == nil {
|
|
return nil
|
|
} else {
|
|
log.Info("could not scrub files: %v", err)
|
|
return err.(error)
|
|
}
|
|
}
|
|
|
|
func ScrubFilesInAllShards(
|
|
log *Logger,
|
|
client *Client,
|
|
opts *ScrubOptions,
|
|
rateLimit *RateLimit,
|
|
state *ScrubState,
|
|
) error {
|
|
terminateChan := make(chan any, 1)
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(256)
|
|
for i := 0; i < 256; i++ {
|
|
shid := msgs.ShardId(i)
|
|
go func() {
|
|
defer func() { HandleRecoverChan(log, terminateChan, recover()) }()
|
|
if err := ScrubFiles(log, client, opts, rateLimit, state, shid); err != nil {
|
|
panic(err)
|
|
}
|
|
wg.Done()
|
|
}()
|
|
}
|
|
go func() {
|
|
wg.Wait()
|
|
log.Info("scrubbing terminated in all shards")
|
|
terminateChan <- nil
|
|
}()
|
|
|
|
err := <-terminateChan
|
|
if err == nil {
|
|
return nil
|
|
} else {
|
|
log.Info("could not scrub files: %v", err)
|
|
return err.(error)
|
|
}
|
|
}
|