gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/renter/directoryheap.go (about)

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