github.com/0chain/gosdk@v1.17.11/zboxcore/sdk/repairworker.go (about) 1 package sdk 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "sync" 8 9 "github.com/0chain/gosdk/constants" 10 "github.com/0chain/gosdk/core/sys" 11 "github.com/0chain/gosdk/zboxcore/fileref" 12 l "github.com/0chain/gosdk/zboxcore/logger" 13 "github.com/0chain/gosdk/zboxcore/zboxutil" 14 "go.uber.org/zap" 15 ) 16 17 type RepairRequest struct { 18 listDir *ListResult 19 isRepairCanceled bool 20 localRootPath string 21 statusCB StatusCallback 22 completedCallback func() 23 filesRepaired int 24 wg *sync.WaitGroup 25 allocation *Allocation 26 } 27 28 type RepairStatusCB struct { 29 wg *sync.WaitGroup 30 success bool 31 err error 32 statusCB StatusCallback 33 } 34 35 var RepairBlocks = 100 36 37 func (cb *RepairStatusCB) Started(allocationId, filePath string, op int, totalBytes int) { 38 cb.statusCB.Started(allocationId, filePath, op, totalBytes) 39 } 40 41 func (cb *RepairStatusCB) InProgress(allocationId, filePath string, op int, completedBytes int, data []byte) { 42 cb.statusCB.InProgress(allocationId, filePath, op, completedBytes, data) 43 } 44 45 func (cb *RepairStatusCB) RepairCompleted(filesRepaired int) { 46 cb.statusCB.RepairCompleted(filesRepaired) 47 } 48 49 func (cb *RepairStatusCB) Completed(allocationId, filePath string, filename string, mimetype string, size int, op int) { 50 cb.statusCB.Completed(allocationId, filePath, filename, mimetype, size, op) 51 cb.success = true 52 cb.wg.Done() 53 } 54 55 func (cb *RepairStatusCB) Error(allocationID string, filePath string, op int, err error) { 56 cb.statusCB.Error(allocationID, filePath, op, err) 57 cb.success = false 58 cb.err = err 59 cb.wg.Done() 60 } 61 62 func (r *RepairRequest) processRepair(ctx context.Context, a *Allocation) { 63 if r.completedCallback != nil { 64 defer r.completedCallback() 65 } 66 67 if r.checkForCancel(a) { 68 return 69 } 70 SetNumBlockDownloads(RepairBlocks) 71 currBatchSize := BatchSize 72 BatchSize = BatchSize / 2 73 defer func() { 74 BatchSize = currBatchSize 75 }() 76 if !singleClientMode { 77 SetSingleClietnMode(true) 78 defer SetSingleClietnMode(false) 79 } 80 r.iterateDir(a, r.listDir) 81 if r.statusCB != nil { 82 r.statusCB.RepairCompleted(r.filesRepaired) 83 } 84 } 85 86 // holds result of repair size 87 type RepairSize struct { 88 // upload size in bytes 89 UploadSize uint64 `json:"upload_size"` 90 // download size in bytes 91 DownloadSize uint64 `json:"download_size"` 92 } 93 94 // gets size to repair for remote dir. 95 func (r *RepairRequest) Size(ctx context.Context, dir *ListResult) (RepairSize, error) { 96 var rs RepairSize 97 var err error 98 switch dir.Type { 99 case fileref.DIRECTORY: 100 if len(dir.Children) == 0 { 101 // fetch dir 102 dir, err = r.allocation.ListDir(dir.Path, WithListRequestForRepair(true), WithListRequestPageLimit(-1)) 103 if err != nil { 104 return rs, err 105 } 106 } 107 for _, subDir := range dir.Children { 108 subDirSz, err := r.Size(ctx, subDir) 109 if err != nil { 110 return rs, err 111 } 112 rs.UploadSize += subDirSz.UploadSize 113 rs.DownloadSize += subDirSz.DownloadSize 114 } 115 case fileref.FILE: 116 // this returns repair operations required 117 repairOps := r.repairFile(r.allocation, dir) 118 if repairOps == nil { 119 err = fmt.Errorf("fetch repairOps failed") 120 return rs, err 121 } 122 for _, repairOp := range repairOps { 123 if repairOp.OperationType == constants.FileOperationInsert { 124 rs.UploadSize += uint64(repairOp.Mask.CountOnes()) * uint64(getShardSize(repairOp.FileMeta.ActualSize, r.allocation.DataShards, repairOp.EncryptedKey != "")) 125 rs.DownloadSize += uint64(repairOp.FileMeta.ActualSize) 126 } 127 } 128 } 129 return rs, err 130 } 131 132 func (r *RepairRequest) iterateDir(a *Allocation, dir *ListResult) []OperationRequest { 133 ops := make([]OperationRequest, 0) 134 switch dir.Type { 135 case fileref.DIRECTORY: 136 if len(dir.Children) == 0 { 137 var err error 138 dir, err = a.ListDir(dir.Path, WithListRequestForRepair(true), WithListRequestPageLimit(-1)) 139 if err != nil { 140 l.Logger.Error("Failed to get listDir for path ", zap.Any("path", dir.Path), zap.Error(err)) 141 return nil 142 } 143 } 144 if len(dir.Children) == 0 { 145 if dir.deleteMask.CountOnes() > 0 { 146 l.Logger.Info("Deleting minority shards for the path :", zap.Any("path", dir.Path)) 147 consensus := dir.deleteMask.CountOnes() 148 if consensus < a.DataShards { 149 150 err := a.deleteFile(dir.Path, 0, consensus, dir.deleteMask) 151 if err != nil { 152 l.Logger.Error("repair_file_failed", zap.Error(err)) 153 if r.statusCB != nil { 154 r.statusCB.Error(a.ID, dir.Path, OpRepair, err) 155 } 156 return nil 157 } 158 r.filesRepaired++ 159 } else if consensus < len(a.Blobbers) { 160 createMask := dir.deleteMask.Not().And(zboxutil.NewUint128(1).Lsh(uint64(len(a.Blobbers))).Sub64(1)) 161 err := a.createDir(dir.Path, 0, createMask.CountOnes(), createMask) 162 if err != nil { 163 l.Logger.Error("repair_file_failed", zap.Error(err)) 164 if r.statusCB != nil { 165 r.statusCB.Error(a.ID, dir.Path, OpRepair, err) 166 } 167 return nil 168 } 169 r.filesRepaired++ 170 } 171 } 172 } 173 for _, childDir := range dir.Children { 174 if r.checkForCancel(a) { 175 return nil 176 } 177 ops = append(ops, r.iterateDir(a, childDir)...) 178 if len(ops) >= RepairBatchSize/2 { 179 r.repairOperation(a, ops) 180 ops = nil 181 } 182 } 183 if len(ops) > 0 { 184 r.repairOperation(a, ops) 185 ops = nil 186 } 187 case fileref.FILE: 188 // this returns op object and mask 189 repairOps := r.repairFile(a, dir) 190 if repairOps != nil { 191 ops = append(ops, repairOps...) 192 } 193 194 default: 195 l.Logger.Info("Invalid directory type", zap.Any("type", dir.Type)) 196 } 197 return ops 198 } 199 200 func (r *RepairRequest) repairFile(a *Allocation, file *ListResult) []OperationRequest { 201 ops := make([]OperationRequest, 0) 202 if r.checkForCancel(a) { 203 return nil 204 } 205 l.Logger.Info("Checking file for the path :", zap.Any("path", file.Path)) 206 found, deleteMask, repairRequired, ref, err := a.RepairRequired(file.Path) 207 if err != nil { 208 l.Logger.Error("repair_required_failed", zap.Error(err)) 209 return nil 210 } 211 if repairRequired { 212 l.Logger.Info("Repair required for the path :", zap.Any("path", file.Path)) 213 if found.CountOnes() >= a.DataShards { 214 l.Logger.Info("Repair by upload", zap.Any("path", file.Path)) 215 var wg sync.WaitGroup 216 statusCB := &RepairStatusCB{ 217 wg: &wg, 218 statusCB: r.statusCB, 219 } 220 221 if deleteMask.CountOnes() > 0 { 222 l.Logger.Info("Deleting minority shards for the path :", zap.Any("path", file.Path)) 223 op := OperationRequest{ 224 OperationType: constants.FileOperationDelete, 225 RemotePath: file.Path, 226 Mask: &deleteMask, 227 } 228 ops = append(ops, op) 229 } 230 wg.Add(1) 231 localPath := r.getLocalPath(file) 232 var op *OperationRequest 233 if !checkFileExists(localPath) { 234 if r.checkForCancel(a) { 235 return nil 236 } 237 memFile := &sys.MemChanFile{ 238 Buffer: make(chan []byte, 100), 239 ChunkWriteSize: int(a.GetChunkReadSize(ref.EncryptedKey != "")), 240 } 241 op = a.RepairFile(memFile, file.Path, statusCB, found, ref) 242 if op.FileMeta.ActualSize > 0 { 243 op.DownloadFile = true 244 } 245 } else { 246 f, err := sys.Files.Open(localPath) 247 if err != nil { 248 l.Logger.Error("repair_file_failed", zap.Error(err)) 249 return nil 250 } 251 op = a.RepairFile(f, file.Path, statusCB, found, ref) 252 } 253 ops = append(ops, *op) 254 if r.checkForCancel(a) { 255 return nil 256 } 257 } else { 258 l.Logger.Info("Repair by delete", zap.Any("path", file.Path)) 259 op := OperationRequest{ 260 OperationType: constants.FileOperationDelete, 261 RemotePath: file.Path, 262 Mask: &found, 263 } 264 ops = append(ops, op) 265 } 266 } else if deleteMask.CountOnes() > 0 { 267 l.Logger.Info("Deleting minority shards for the path :", zap.Any("path", file.Path)) 268 op := OperationRequest{ 269 OperationType: constants.FileOperationDelete, 270 RemotePath: file.Path, 271 Mask: &deleteMask, 272 } 273 ops = append(ops, op) 274 } 275 return ops 276 } 277 278 func (r *RepairRequest) repairOperation(a *Allocation, ops []OperationRequest) { 279 err := a.DoMultiOperation(ops, WithRepair()) 280 if err != nil { 281 l.Logger.Error("repair_file_failed", zap.Error(err)) 282 status := r.statusCB != nil 283 for _, op := range ops { 284 if op.DownloadFile { 285 _ = a.CancelDownload(op.RemotePath) 286 } 287 if status { 288 r.statusCB.Error(a.ID, op.RemotePath, OpRepair, err) 289 } 290 } 291 } else { 292 r.filesRepaired += len(ops) 293 } 294 for _, op := range ops { 295 if op.FileReader != nil && !op.DownloadFile { 296 if f, ok := op.FileReader.(io.Closer); ok { 297 f.Close() 298 } 299 } 300 } 301 } 302 303 func (r *RepairRequest) getLocalPath(file *ListResult) string { 304 return r.localRootPath + file.Path 305 } 306 307 func checkFileExists(localPath string) bool { 308 if IsWasm { 309 return false 310 } 311 info, err := sys.Files.Stat(localPath) 312 if err != nil { 313 return false 314 } 315 return !info.IsDir() 316 } 317 318 func (r *RepairRequest) checkForCancel(a *Allocation) bool { 319 if r.isRepairCanceled { 320 l.Logger.Info("Repair Cancelled by the user") 321 if r.statusCB != nil { 322 r.statusCB.RepairCompleted(r.filesRepaired) 323 } 324 return true 325 } 326 return false 327 }