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 }