gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/directoryheap.go (about)

     1  package renter
     2  
     3  import (
     4  	"container/heap"
     5  	"fmt"
     6  	"math"
     7  	"sync"
     8  
     9  	"gitlab.com/NebulousLabs/errors"
    10  	"gitlab.com/NebulousLabs/threadgroup"
    11  
    12  	"gitlab.com/SkynetLabs/skyd/build"
    13  	"gitlab.com/SkynetLabs/skyd/skymodules"
    14  	"gitlab.com/SkynetLabs/skyd/skymodules/renter/filesystem/siadir"
    15  )
    16  
    17  // directory is a helper struct that represents a siadir in the
    18  // repairDirectoryHeap
    19  type directory struct {
    20  	// Heap controlled fields
    21  	index int // The index of the item in the heap
    22  
    23  	staticSiaPath skymodules.SiaPath
    24  
    25  	// mu controlled fields
    26  	aggregateHealth       float64
    27  	aggregateRemoteHealth float64
    28  	explored              bool
    29  	health                float64
    30  	remoteHealth          float64
    31  
    32  	mu sync.Mutex
    33  }
    34  
    35  // managedHeapHealth returns the health that should be used to prioritize the
    36  // directory in the heap. It also returns a boolean indicating if that health is
    37  // from remote files.
    38  //
    39  // If a directory is explored then we should use the Health of the Directory. If
    40  // a directory is unexplored then we should use the AggregateHealth of the
    41  // Directory. This will ensure we are following the path of lowest health as
    42  // well as evaluating each directory on its own merit.
    43  //
    44  // If either the RemoteHealth or the AggregateRemoteHealth are above the
    45  // RepairThreshold we should use that health in order to prioritize remote files
    46  func (d *directory) managedHeapHealth() (float64, bool) {
    47  	d.mu.Lock()
    48  	defer d.mu.Unlock()
    49  
    50  	// Grab the directory level health values
    51  	remoteHealth := d.remoteHealth
    52  	health := d.health
    53  
    54  	// If the directory hasn't been explored yet, grab the aggregate health
    55  	// values
    56  	if !d.explored {
    57  		remoteHealth = d.aggregateRemoteHealth
    58  		health = d.aggregateHealth
    59  	}
    60  
    61  	// Use the remoteHealth if it is at or worse than the RepairThreshold
    62  	if skymodules.NeedsRepair(remoteHealth) {
    63  		return remoteHealth, true
    64  	}
    65  	return health, false
    66  }
    67  
    68  // directoryHeap contains a priority sorted heap of directories that are being
    69  // explored and repaired
    70  type directoryHeap struct {
    71  	heap repairDirectoryHeap
    72  
    73  	// heapDirectories is a map containing all the directories currently in the
    74  	// heap
    75  	heapDirectories map[skymodules.SiaPath]*directory
    76  
    77  	mu sync.Mutex
    78  }
    79  
    80  // repairDirectoryHeap is a heap of priority sorted directory elements that need
    81  // to be explored and repaired.
    82  type repairDirectoryHeap []*directory
    83  
    84  // Implementation of heap.Interface for repairDirectoryHeap.
    85  func (rdh repairDirectoryHeap) Len() int { return len(rdh) }
    86  func (rdh repairDirectoryHeap) Less(i, j int) bool {
    87  	// Get the health of each directory and whether or not they have remote
    88  	// files
    89  	iHealth, iRemote := rdh[i].managedHeapHealth()
    90  	jHealth, jRemote := rdh[j].managedHeapHealth()
    91  
    92  	// Prioritize based on Remote first
    93  	if iRemote && !jRemote {
    94  		return true
    95  	}
    96  	if !iRemote && jRemote {
    97  		return false
    98  	}
    99  
   100  	// Directories are prioritized based on their heapHealth
   101  	//
   102  	// Note: we are using the > operator and not >= which means that the element
   103  	// added to the heap first will be prioritized in the event that the healths
   104  	// are equal
   105  	return iHealth > jHealth
   106  }
   107  func (rdh repairDirectoryHeap) Swap(i, j int) {
   108  	rdh[i], rdh[j] = rdh[j], rdh[i]
   109  	rdh[i].index, rdh[j].index = i, j
   110  }
   111  func (rdh *repairDirectoryHeap) Push(x interface{}) {
   112  	n := len(*rdh)
   113  	d := x.(*directory)
   114  	d.index = n
   115  	*rdh = append(*rdh, d)
   116  }
   117  func (rdh *repairDirectoryHeap) Pop() interface{} {
   118  	old := *rdh
   119  	n := len(old)
   120  	d := old[n-1]
   121  	d.index = -1 // for safety
   122  	*rdh = old[0 : n-1]
   123  	return d
   124  }
   125  
   126  // managedLen returns the length of the heap
   127  func (dh *directoryHeap) managedLen() int {
   128  	dh.mu.Lock()
   129  	defer dh.mu.Unlock()
   130  	return dh.heap.Len()
   131  }
   132  
   133  // managedPeekHealth returns the current worst health of the directory heap. A
   134  // boolean is returned indicating whether or not the health is based on remote
   135  // health. If the file has poor remote health, this is considered more
   136  // significant than having even poorer local health.
   137  //
   138  // 'Remote' health indicates the health of all chunks that are not available
   139  // locally and therefore need to do remote repairs.
   140  func (dh *directoryHeap) managedPeekHealth() (float64, bool) {
   141  	dh.mu.Lock()
   142  	defer dh.mu.Unlock()
   143  
   144  	// If the heap is empty return 0 as that is the max health
   145  	if dh.heap.Len() == 0 {
   146  		return 0, false
   147  	}
   148  
   149  	// Pop off and then push back the top directory. We are not using the
   150  	// managed methods here as to avoid removing the directory from the map and
   151  	// having another thread push the directory onto the heap in between locks
   152  	d := heap.Pop(&dh.heap).(*directory)
   153  	defer heap.Push(&dh.heap, d)
   154  	return d.managedHeapHealth()
   155  }
   156  
   157  // managedPop will return the top directory from the heap
   158  func (dh *directoryHeap) managedPop() (d *directory) {
   159  	dh.mu.Lock()
   160  	defer dh.mu.Unlock()
   161  	if dh.heap.Len() > 0 {
   162  		d = heap.Pop(&dh.heap).(*directory)
   163  		delete(dh.heapDirectories, d.staticSiaPath)
   164  	}
   165  	return d
   166  }
   167  
   168  // managedPush will try to add a directory to the directory heap. If the
   169  // directory already exists, the existing directory will be updated to have the
   170  // worst healths of the pushed directory and the existing directory. And if
   171  // either the existing directory or the pushed directory is marked as
   172  // unexplored, the updated directory will be marked as unexplored.
   173  func (dh *directoryHeap) managedPush(d *directory) {
   174  	dh.mu.Lock()
   175  	defer dh.mu.Unlock()
   176  
   177  	// If the directory exists already in the heap, update that directory.
   178  	_, exists := dh.heapDirectories[d.staticSiaPath]
   179  	if exists {
   180  		if !dh.update(d) {
   181  			build.Critical("update should succeed because the directory is known to exist in the heap")
   182  		}
   183  		return
   184  	}
   185  
   186  	// If the directory does not exist in the heap, add it to the heap.
   187  	heap.Push(&dh.heap, d)
   188  	dh.heapDirectories[d.staticSiaPath] = d
   189  }
   190  
   191  // managedReset clears the directory heap by recreating the heap and
   192  // heapDirectories.
   193  func (dh *directoryHeap) managedReset() {
   194  	dh.mu.Lock()
   195  	defer dh.mu.Unlock()
   196  	dh.heapDirectories = make(map[skymodules.SiaPath]*directory)
   197  	dh.heap = repairDirectoryHeap{}
   198  }
   199  
   200  // update will update the directory that is currently in the heap based on the
   201  // directory pasted in.
   202  //
   203  // The worse health between the pushed dir and the existing dir will be kept to
   204  // ensure that the directory is looked at by the repair heap.
   205  //
   206  // Similarly, if either the new dir or the existing dir are marked as
   207  // unexplored, the new dir will be marked as unexplored to ensure that all
   208  // subdirs of the dir get added to the heap.
   209  func (dh *directoryHeap) update(d *directory) bool {
   210  	heapDir, exists := dh.heapDirectories[d.staticSiaPath]
   211  	if !exists {
   212  		return false
   213  	}
   214  	// Update the health fields of the directory in the heap.
   215  	heapDir.mu.Lock()
   216  	heapDir.aggregateHealth = math.Max(heapDir.aggregateHealth, d.aggregateHealth)
   217  	heapDir.aggregateRemoteHealth = math.Max(heapDir.aggregateRemoteHealth, d.aggregateRemoteHealth)
   218  	heapDir.health = math.Max(heapDir.health, d.health)
   219  	heapDir.remoteHealth = math.Max(heapDir.remoteHealth, d.remoteHealth)
   220  	if !heapDir.explored || !d.explored {
   221  		heapDir.explored = false
   222  	}
   223  	heapDir.mu.Unlock()
   224  	dh.heapDirectories[d.staticSiaPath] = heapDir
   225  	heap.Fix(&dh.heap, heapDir.index)
   226  	return true
   227  }
   228  
   229  // managedPushDirectory adds a directory to the directory heap
   230  func (dh *directoryHeap) managedPushDirectory(siaPath skymodules.SiaPath, metadata siadir.Metadata, explored bool) {
   231  	d := &directory{
   232  		aggregateHealth:       metadata.AggregateHealth,
   233  		aggregateRemoteHealth: metadata.AggregateRemoteHealth,
   234  		explored:              explored,
   235  		health:                metadata.Health,
   236  		remoteHealth:          metadata.RemoteHealth,
   237  		staticSiaPath:         siaPath,
   238  	}
   239  	dh.managedPush(d)
   240  }
   241  
   242  // managedNextExploredDirectory pops directories off of the heap until it
   243  // finds an explored directory. If an unexplored directory is found, any
   244  // subdirectories are added to the heap and the directory is marked as explored
   245  // and pushed back onto the heap.
   246  func (r *Renter) managedNextExploredDirectory() (*directory, error) {
   247  	// Loop until we pop off an explored directory
   248  	for {
   249  		select {
   250  		case <-r.tg.StopChan():
   251  			return nil, errors.AddContext(threadgroup.ErrStopped, "renter shutdown before directory could be returned")
   252  		default:
   253  		}
   254  
   255  		// Pop directory
   256  		d := r.staticDirectoryHeap.managedPop()
   257  
   258  		// Sanity check that we are still popping off directories
   259  		if d == nil {
   260  			return nil, nil
   261  		}
   262  
   263  		// Check if explored and mark as explored if unexplored
   264  		d.mu.Lock()
   265  		explored := d.explored
   266  		if !explored {
   267  			d.explored = true
   268  		}
   269  		d.mu.Unlock()
   270  		if explored {
   271  			return d, nil
   272  		}
   273  
   274  		// Add Sub directories
   275  		err := r.managedPushSubDirectories(d)
   276  		if err != nil {
   277  			contextStr := fmt.Sprintf("unable to push subdirectories for `%v`", d.staticSiaPath)
   278  			return nil, errors.AddContext(err, contextStr)
   279  		}
   280  
   281  		// Add popped directory back to heap with explored now set to true.
   282  		r.staticDirectoryHeap.managedPush(d)
   283  	}
   284  }
   285  
   286  // managedPushSubDirectories adds unexplored directory elements to the heap for
   287  // all of the directory's sub directories
   288  func (r *Renter) managedPushSubDirectories(d *directory) error {
   289  	subDirs, err := r.managedSubDirectories(d.staticSiaPath)
   290  	if err != nil {
   291  		contextStr := fmt.Sprintf("unable to get subdirectories for `%v`", d.staticSiaPath)
   292  		return errors.AddContext(err, contextStr)
   293  	}
   294  	for _, subDir := range subDirs {
   295  		err = r.managedPushUnexploredDirectory(subDir)
   296  		if err != nil {
   297  			contextStr := fmt.Sprintf("unable to push unexplored directory `%v`", subDir)
   298  			return errors.AddContext(err, contextStr)
   299  		}
   300  	}
   301  	return nil
   302  }
   303  
   304  // managedPushUnexploredDirectory reads the health from the siadir metadata and
   305  // pushes an unexplored directory element onto the heap
   306  func (r *Renter) managedPushUnexploredDirectory(siaPath skymodules.SiaPath) (err error) {
   307  	// Grab the siadir metadata. If it doesn't exist, create it.
   308  	siaDir, err := r.staticFileSystem.OpenSiaDirCustom(siaPath, true)
   309  	if err != nil {
   310  		return err
   311  	}
   312  	defer func() {
   313  		err = errors.Compose(err, siaDir.Close())
   314  	}()
   315  	metadata, err := siaDir.Metadata()
   316  	if err != nil {
   317  		return err
   318  	}
   319  
   320  	// Push unexplored directory onto heap.
   321  	r.staticDirectoryHeap.managedPushDirectory(siaPath, metadata, false)
   322  	return nil
   323  }