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  }