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 }