github.com/0chain/gosdk@v1.17.11/zboxcore/sdk/multi_operation_worker.go (about) 1 package sdk 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io/ioutil" 8 "mime/multipart" 9 "net/http" 10 "sync" 11 "time" 12 13 "github.com/0chain/errors" 14 "github.com/remeh/sizedwaitgroup" 15 16 "github.com/0chain/gosdk/core/common" 17 "github.com/0chain/gosdk/core/util" 18 "github.com/0chain/gosdk/zboxcore/allocationchange" 19 "github.com/0chain/gosdk/zboxcore/client" 20 "github.com/0chain/gosdk/zboxcore/fileref" 21 "github.com/0chain/gosdk/zboxcore/logger" 22 l "github.com/0chain/gosdk/zboxcore/logger" 23 24 "github.com/0chain/gosdk/zboxcore/zboxutil" 25 "github.com/google/uuid" 26 ) 27 28 const ( 29 DefaultCreateConnectionTimeOut = 45 * time.Second 30 ) 31 32 var BatchSize = 6 33 34 type MultiOperationOption func(mo *MultiOperation) 35 36 func WithRepair() MultiOperationOption { 37 return func(mo *MultiOperation) { 38 mo.Consensus.consensusThresh = 0 39 mo.isRepair = true 40 } 41 } 42 43 type Operationer interface { 44 Process(allocObj *Allocation, connectionID string) ([]fileref.RefEntity, zboxutil.Uint128, error) 45 buildChange(refs []fileref.RefEntity, uid uuid.UUID) []allocationchange.AllocationChange 46 Verify(allocObj *Allocation) error 47 Completed(allocObj *Allocation) 48 Error(allocObj *Allocation, consensus int, err error) 49 } 50 51 type MultiOperation struct { 52 connectionID string 53 operations []Operationer 54 allocationObj *Allocation 55 ctx context.Context 56 ctxCncl context.CancelCauseFunc 57 operationMask zboxutil.Uint128 58 maskMU *sync.Mutex 59 Consensus 60 changes [][]allocationchange.AllocationChange 61 isRepair bool 62 } 63 64 func (mo *MultiOperation) createConnectionObj(blobberIdx int) (err error) { 65 66 defer func() { 67 if err == nil { 68 mo.maskMU.Lock() 69 mo.operationMask = mo.operationMask.Or(zboxutil.NewUint128(1).Lsh(uint64(blobberIdx))) 70 mo.maskMU.Unlock() 71 } 72 }() 73 74 var ( 75 resp *http.Response 76 shouldContinue bool 77 latestRespMsg string 78 79 latestStatusCode int 80 ) 81 blobber := mo.allocationObj.Blobbers[blobberIdx] 82 83 for i := 0; i < 3; i++ { 84 err, shouldContinue = func() (err error, shouldContinue bool) { 85 body := new(bytes.Buffer) 86 formWriter := multipart.NewWriter(body) 87 88 err = formWriter.WriteField("connection_id", mo.connectionID) 89 if err != nil { 90 return err, false 91 } 92 formWriter.Close() 93 94 var httpreq *http.Request 95 httpreq, err = zboxutil.NewConnectionRequest(blobber.Baseurl, mo.allocationObj.ID, mo.allocationObj.Tx, mo.allocationObj.sig, body) 96 if err != nil { 97 l.Logger.Error(blobber.Baseurl, "Error creating new connection request", err) 98 return 99 } 100 101 httpreq.Header.Add("Content-Type", formWriter.FormDataContentType()) 102 ctx, cncl := context.WithTimeout(mo.ctx, DefaultCreateConnectionTimeOut) 103 defer cncl() 104 err = zboxutil.HttpDo(ctx, cncl, httpreq, func(r *http.Response, err error) error { 105 resp = r 106 return err 107 }) 108 if err != nil { 109 logger.Logger.Error("Create Connection: ", err) 110 return 111 } 112 113 if resp.Body != nil { 114 defer resp.Body.Close() 115 } 116 var respBody []byte 117 respBody, err = ioutil.ReadAll(resp.Body) 118 if err != nil { 119 logger.Logger.Error("Error: Resp ", err) 120 return 121 } 122 123 latestRespMsg = string(respBody) 124 latestStatusCode = resp.StatusCode 125 if resp.StatusCode == http.StatusOK { 126 l.Logger.Debug(blobber.Baseurl, " connection obj created.") 127 return 128 } 129 130 if resp.StatusCode == http.StatusTooManyRequests { 131 logger.Logger.Error("Got too many request error") 132 var r int 133 r, err = zboxutil.GetRateLimitValue(resp) 134 if err != nil { 135 logger.Logger.Error(err) 136 return 137 } 138 time.Sleep(time.Duration(r) * time.Second) 139 shouldContinue = true 140 return 141 } 142 l.Logger.Error(blobber.Baseurl, "Response: ", string(respBody)) 143 err = errors.New("response_error", string(respBody)) 144 return 145 }() 146 147 if err != nil { 148 return 149 } 150 if shouldContinue { 151 continue 152 } 153 return 154 } 155 156 err = errors.New("unknown_issue", 157 fmt.Sprintf("last status code: %d, last response message: %s", latestStatusCode, latestRespMsg)) 158 return 159 } 160 161 func (mo *MultiOperation) Process() error { 162 l.Logger.Debug("MultiOperation Process start") 163 wg := &sync.WaitGroup{} 164 mo.changes = make([][]allocationchange.AllocationChange, len(mo.operations)) 165 ctx := mo.ctx 166 ctxCncl := mo.ctxCncl 167 defer ctxCncl(nil) 168 swg := sizedwaitgroup.New(BatchSize) 169 errsSlice := make([]error, len(mo.operations)) 170 mo.operationMask = zboxutil.NewUint128(0) 171 for idx, op := range mo.operations { 172 uid := util.GetNewUUID() 173 swg.Add() 174 go func(op Operationer, idx int) { 175 defer swg.Done() 176 177 // Check for other goroutines signal 178 select { 179 case <-ctx.Done(): 180 return 181 default: 182 } 183 184 refs, mask, err := op.Process(mo.allocationObj, mo.connectionID) // Process with each blobber 185 if err != nil { 186 l.Logger.Error(err) 187 errsSlice[idx] = errors.New("", err.Error()) 188 ctxCncl(err) 189 return 190 } 191 mo.maskMU.Lock() 192 mo.operationMask = mo.operationMask.Or(mask) 193 mo.maskMU.Unlock() 194 changes := op.buildChange(refs, uid) 195 mo.changes[idx] = changes 196 }(op, idx) 197 } 198 swg.Wait() 199 200 if ctx.Err() != nil { 201 err := context.Cause(ctx) 202 return err 203 } 204 205 // Check consensus 206 if mo.operationMask.CountOnes() < mo.consensusThresh { 207 majorErr := zboxutil.MajorError(errsSlice) 208 if majorErr != nil { 209 return errors.New("consensus_not_met", 210 fmt.Sprintf("Multioperation failed. Required consensus %d got %d. Major error: %s", 211 mo.consensusThresh, mo.operationMask.CountOnes(), majorErr.Error())) 212 } 213 return nil 214 } 215 216 // Take transpose of mo.change because it will be easier to iterate mo if it contains blobber changes 217 // in row instead of column. Currently mo.change[0] contains allocationChange for operation 1 and so on. 218 // But we want mo.changes[0] to have allocationChange for blobber 1 and mo.changes[1] to have allocationChange for 219 // blobber 2 and so on. 220 start := time.Now() 221 mo.changes = zboxutil.Transpose(mo.changes) 222 223 writeMarkerMutex, err := CreateWriteMarkerMutex(client.GetClient(), mo.allocationObj) 224 if err != nil { 225 return fmt.Errorf("Operation failed: %s", err.Error()) 226 } 227 228 l.Logger.Debug("Trying to lock write marker.....") 229 if singleClientMode { 230 mo.allocationObj.commitMutex.Lock() 231 } else { 232 err = writeMarkerMutex.Lock(mo.ctx, &mo.operationMask, mo.maskMU, 233 mo.allocationObj.Blobbers, &mo.Consensus, 0, time.Minute, mo.connectionID) 234 if err != nil { 235 return fmt.Errorf("Operation failed: %s", err.Error()) 236 } 237 } 238 logger.Logger.Debug("[writemarkerLocked]", time.Since(start).Milliseconds()) 239 start = time.Now() 240 status := Commit 241 if !mo.isRepair && !mo.allocationObj.checkStatus { 242 status, _, err = mo.allocationObj.CheckAllocStatus() 243 if err != nil { 244 logger.Logger.Error("Error checking allocation status", err) 245 if singleClientMode { 246 mo.allocationObj.commitMutex.Unlock() 247 } else { 248 writeMarkerMutex.Unlock(mo.ctx, mo.operationMask, mo.allocationObj.Blobbers, time.Minute, mo.connectionID) //nolint: errcheck 249 } 250 return fmt.Errorf("Check allocation status failed: %s", err.Error()) 251 } 252 if status == Repair { 253 if singleClientMode { 254 mo.allocationObj.commitMutex.Unlock() 255 } else { 256 writeMarkerMutex.Unlock(mo.ctx, mo.operationMask, mo.allocationObj.Blobbers, time.Minute, mo.connectionID) //nolint: errcheck 257 } 258 for _, op := range mo.operations { 259 op.Error(mo.allocationObj, 0, ErrRepairRequired) 260 } 261 return ErrRepairRequired 262 } 263 } 264 if singleClientMode { 265 mo.allocationObj.checkStatus = true 266 defer mo.allocationObj.commitMutex.Unlock() 267 } else { 268 defer writeMarkerMutex.Unlock(mo.ctx, mo.operationMask, mo.allocationObj.Blobbers, time.Minute, mo.connectionID) //nolint: errcheck 269 } 270 if status != Commit { 271 for _, op := range mo.operations { 272 op.Error(mo.allocationObj, 0, ErrRetryOperation) 273 } 274 return ErrRetryOperation 275 } 276 logger.Logger.Debug("[checkAllocStatus]", time.Since(start).Milliseconds()) 277 mo.Consensus.Reset() 278 activeBlobbers := mo.operationMask.CountOnes() 279 commitReqs := make([]*CommitRequest, activeBlobbers) 280 start = time.Now() 281 wg.Add(activeBlobbers) 282 var pos uint64 283 var counter = 0 284 timestamp := int64(common.Now()) 285 for i := mo.operationMask; !i.Equals64(0); i = i.And(zboxutil.NewUint128(1).Lsh(pos).Not()) { 286 pos = uint64(i.TrailingZeros()) 287 commitReq := &CommitRequest{ 288 allocationID: mo.allocationObj.ID, 289 allocationTx: mo.allocationObj.Tx, 290 sig: mo.allocationObj.sig, 291 blobber: mo.allocationObj.Blobbers[pos], 292 connectionID: mo.connectionID, 293 wg: wg, 294 timestamp: timestamp, 295 blobberInd: pos, 296 } 297 298 commitReq.changes = append(commitReq.changes, mo.changes[pos]...) 299 commitReqs[counter] = commitReq 300 l.Logger.Debug("Commit request sending to blobber ", commitReq.blobber.Baseurl) 301 go AddCommitRequest(commitReq) 302 counter++ 303 } 304 wg.Wait() 305 logger.Logger.Debug("[commitRequests]", time.Since(start).Milliseconds()) 306 rollbackMask := zboxutil.NewUint128(0) 307 errSlice := make([]error, len(commitReqs)) 308 for idx, commitReq := range commitReqs { 309 if commitReq.result != nil { 310 if commitReq.result.Success { 311 l.Logger.Debug("Commit success", commitReq.blobber.Baseurl) 312 if !mo.isRepair { 313 rollbackMask = rollbackMask.Or(zboxutil.NewUint128(1).Lsh(commitReq.blobberInd)) 314 } 315 mo.consensus++ 316 } else { 317 errSlice[idx] = errors.New("commit_failed", commitReq.result.ErrorMessage) 318 l.Logger.Error("Commit failed", commitReq.blobber.Baseurl, commitReq.result.ErrorMessage) 319 } 320 } else { 321 l.Logger.Debug("Commit result not set", commitReq.blobber.Baseurl) 322 } 323 } 324 325 if !mo.isConsensusOk() { 326 err = zboxutil.MajorError(errSlice) 327 if mo.getConsensus() != 0 { 328 l.Logger.Info("Rolling back changes on minority blobbers") 329 mo.allocationObj.RollbackWithMask(rollbackMask) 330 } 331 for _, op := range mo.operations { 332 op.Error(mo.allocationObj, mo.getConsensus(), err) 333 } 334 return err 335 } else { 336 for _, op := range mo.operations { 337 op.Completed(mo.allocationObj) 338 } 339 } 340 341 return nil 342 343 }