github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/contract/trace_scheduler.go (about)

     1  package contract
     2  
     3  import (
     4  	"errors"
     5  	"math"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/bytom/bytom/protocol/bc"
    10  	log "github.com/sirupsen/logrus"
    11  	"golang.org/x/sync/semaphore"
    12  )
    13  
    14  var errInstQueueOverflow = errors.New("instance queue is overflow")
    15  
    16  type traceScheduler struct {
    17  	weighted      *semaphore.Weighted
    18  	instances     *sync.Map
    19  	tracerService *TraceService
    20  	infra         *Infrastructure
    21  
    22  	tracer        *tracer
    23  	currentHeight uint64
    24  	currentHash   bc.Hash
    25  }
    26  
    27  func newTraceScheduler(infra *Infrastructure) *traceScheduler {
    28  	scheduler := &traceScheduler{
    29  		weighted:  semaphore.NewWeighted(1000),
    30  		instances: new(sync.Map),
    31  		infra:     infra,
    32  	}
    33  	return scheduler
    34  }
    35  
    36  func (t *traceScheduler) start(service *TraceService) {
    37  	t.tracerService = service
    38  	go t.processLoop()
    39  }
    40  
    41  func (t *traceScheduler) addNewJob(instance *Instance) error {
    42  	if !t.weighted.TryAcquire(1) {
    43  		return errInstQueueOverflow
    44  	}
    45  
    46  	t.instances.Store(instance.TraceID, instance)
    47  	return nil
    48  }
    49  
    50  func (t *traceScheduler) processLoop() {
    51  	ticker := time.NewTicker(6 * time.Second)
    52  	defer ticker.Stop()
    53  
    54  	for range ticker.C {
    55  		jobs, beginHeight, beginHash := t.prepareJobs()
    56  		if beginHeight > t.tracerService.BestHeight() {
    57  			continue
    58  		}
    59  		t.tracer = newTracer(jobs[beginHash])
    60  
    61  		for t.currentHeight, t.currentHash = beginHeight, beginHash;; {
    62  			if t.currentHeight == t.tracerService.BestHeight() {
    63  				if err := t.finishJobs(jobs); err != nil {
    64  					log.WithFields(log.Fields{"module": logModule, "err": err}).Error("finish jobs")
    65  				} else {
    66  					break
    67  				}
    68  			}
    69  
    70  			if ok, err := t.tryAttach(jobs); err != nil {
    71  				log.WithFields(log.Fields{"module": logModule, "err": err}).Error("try attach on trace scheduler")
    72  			} else if !ok {
    73  				if err := t.detach(jobs); err != nil {
    74  					log.WithFields(log.Fields{"module": logModule, "err": err}).Error("detach on trace scheduler")
    75  				}
    76  			}
    77  		}
    78  	}
    79  }
    80  
    81  func (t *traceScheduler) prepareJobs() (map[bc.Hash][]*Instance, uint64, bc.Hash) {
    82  	beginHeight, beginHash := uint64(math.MaxUint64), bc.Hash{}
    83  	hashToJobs := make(map[bc.Hash][]*Instance)
    84  	t.instances.Range(func(_, value interface{}) bool {
    85  		inst := value.(*Instance)
    86  		hashToJobs[inst.ScannedHash] = append(hashToJobs[inst.ScannedHash], inst)
    87  		if inst.ScannedHeight < beginHeight {
    88  			beginHeight = inst.ScannedHeight
    89  			beginHash = inst.ScannedHash
    90  		}
    91  		return true
    92  	})
    93  	return hashToJobs, beginHeight, beginHash
    94  }
    95  
    96  func (t *traceScheduler) tryAttach(jobs map[bc.Hash][]*Instance) (bool, error) {
    97  	if t.currentHash == t.tracerService.BestHash() {
    98  		return true, nil
    99  	}
   100  
   101  	block, err := t.infra.Chain.GetBlockByHeight(t.currentHeight+1)
   102  	if err != nil {
   103  		return false, err
   104  	}
   105  
   106  	if block.PreviousBlockHash != t.currentHash {
   107  		return false, nil
   108  	}
   109  
   110  	t.tracer.applyBlock(block)
   111  	t.currentHeight++
   112  	t.currentHash = block.Hash()
   113  
   114  	if instances, ok := jobs[block.Hash()]; ok {
   115  		t.tracer.addInstances(instances)
   116  	}
   117  	return true, nil
   118  }
   119  
   120  func (t *traceScheduler) detach(jobs map[bc.Hash][]*Instance) error {
   121  	block, err := t.infra.Chain.GetBlockByHash(&t.currentHash)
   122  	if err != nil {
   123  		return err
   124  	}
   125  
   126  	if instances, ok := jobs[block.Hash()]; ok {
   127  		for _, inst := range instances {
   128  			t.tracer.removeInstance(inst.TraceID)
   129  		}
   130  	}
   131  
   132  	t.tracer.detachBlock(block)
   133  	t.currentHeight--
   134  	t.currentHash = block.PreviousBlockHash
   135  	return nil
   136  }
   137  
   138  func (t *traceScheduler) finishJobs(jobs map[bc.Hash][]*Instance) error {
   139  	inSyncInstances := t.tracer.allInstances()
   140  	inSyncMap := make(map[string]bool)
   141  	for _, inst := range inSyncInstances {
   142  		inSyncMap[inst.TraceID] = true
   143  	}
   144  
   145  	var offChainInstances []*Instance
   146  	for _, instances := range jobs {
   147  		for _, inst := range instances {
   148  			if _, ok := inSyncMap[inst.TraceID]; !ok {
   149  				inst.Status = OffChain
   150  				offChainInstances = append(offChainInstances, inst)
   151  			}
   152  		}
   153  	}
   154  
   155  	if err := t.infra.Repository.SaveInstances(offChainInstances); err != nil {
   156  		return err
   157  	}
   158  
   159  	t.releaseInstances(offChainInstances)
   160  
   161  	if len(inSyncInstances) != 0 {
   162  		if ok := t.tracerService.takeOverInstances(inSyncInstances, t.currentHash); ok {
   163  			t.releaseInstances(inSyncInstances)
   164  		}
   165  	}
   166  	return nil
   167  }
   168  
   169  func (t *traceScheduler) releaseInstances(instances []*Instance) {
   170  	t.weighted.Release(int64(len(instances)))
   171  	for _, inst := range instances {
   172  		t.instances.Delete(inst.TraceID)
   173  	}
   174  }