github.com/igggame/nebulas-go@v2.1.0+incompatible/common/dag/dispatcher.go (about)

     1  // it under the terms of the GNU General Public License as published by
     2  // the Free Software Foundation, either version 3 of the License, or
     3  // (at your option) any later version.
     4  //
     5  // the go-nebulas library is distributed in the hope that it will be useful,
     6  // but WITHOUT ANY WARRANTY; without even the implied warranty of
     7  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     8  // GNU General Public License for more details.
     9  //
    10  // You should have received a copy of the GNU General Public License
    11  // along with the go-nebulas library.  If not, see <http://www.gnu.org/licenses/>.
    12  //
    13  
    14  package dag
    15  
    16  import (
    17  	"errors"
    18  	"sync"
    19  	"time"
    20  
    21  	"github.com/nebulasio/go-nebulas/util/logging"
    22  	"github.com/sirupsen/logrus"
    23  )
    24  
    25  //type value interface{}
    26  
    27  // Callback func node
    28  type Callback func(*Node, interface{}) error
    29  
    30  // Task struct
    31  type Task struct {
    32  	dependence int
    33  	node       *Node
    34  }
    35  
    36  // Errors
    37  var (
    38  	ErrDagHasCirclular = errors.New("dag hava circlular")
    39  	ErrTimeout         = errors.New("dispatcher execute timeout")
    40  )
    41  
    42  // Dispatcher struct a message dispatcher dag.
    43  type Dispatcher struct {
    44  	concurrency      int
    45  	cb               Callback
    46  	muTask           sync.Mutex
    47  	dag              *Dag
    48  	elapseInMs       int64
    49  	quitCh           chan bool
    50  	queueCh          chan *Node
    51  	tasks            map[interface{}]*Task
    52  	queueCounter     int
    53  	completedCounter int
    54  	isFinsih         bool
    55  	finishCH         chan bool
    56  	context          interface{}
    57  }
    58  
    59  // NewDispatcher create Dag Dispatcher instance.
    60  func NewDispatcher(dag *Dag, concurrency int, elapseInMs int64, context interface{}, cb Callback) *Dispatcher {
    61  	dp := &Dispatcher{
    62  		concurrency:      concurrency,
    63  		elapseInMs:       elapseInMs,
    64  		dag:              dag,
    65  		cb:               cb,
    66  		tasks:            make(map[interface{}]*Task),
    67  		queueCounter:     0,
    68  		quitCh:           make(chan bool, concurrency),
    69  		queueCh:          make(chan *Node, dag.Len()),
    70  		completedCounter: 0,
    71  		finishCH:         make(chan bool, 1),
    72  		isFinsih:         false,
    73  		context:          context,
    74  	}
    75  	return dp
    76  }
    77  
    78  // Run dag dispatch goroutine.
    79  func (dp *Dispatcher) Run() error {
    80  	logging.VLog().Debug("Starting Dag Dispatcher...")
    81  
    82  	vertices := dp.dag.GetNodes()
    83  
    84  	rootCounter := 0
    85  	for _, node := range vertices {
    86  		task := &Task{
    87  			dependence: node.parentCounter,
    88  			node:       node,
    89  		}
    90  		task.dependence = node.parentCounter
    91  		dp.tasks[node.key] = task
    92  
    93  		if task.dependence == 0 {
    94  			rootCounter++
    95  			dp.push(node)
    96  		}
    97  	}
    98  
    99  	if rootCounter == 0 && len(vertices) > 0 {
   100  		return ErrDagHasCirclular
   101  	}
   102  
   103  	return dp.execute()
   104  }
   105  
   106  // execute callback
   107  func (dp *Dispatcher) execute() error {
   108  	logging.VLog().Debug("loop Dag Dispatcher.")
   109  
   110  	//timerChan := time.NewTicker(time.Second).C
   111  
   112  	if dp.dag.Len() < dp.concurrency {
   113  		dp.concurrency = dp.dag.Len()
   114  	}
   115  	if dp.concurrency == 0 {
   116  		return nil
   117  	}
   118  
   119  	var err error
   120  	go func() {
   121  		for i := 0; i < dp.concurrency; i++ {
   122  			go func() {
   123  				for {
   124  					select {
   125  					case <-dp.quitCh:
   126  						logging.VLog().Debug("Stoped Dag Dispatcher.")
   127  						return
   128  					case msg := <-dp.queueCh:
   129  						err = dp.cb(msg, dp.context)
   130  
   131  						if err != nil {
   132  							dp.Stop()
   133  						} else {
   134  							isFinish, err := dp.onCompleteParentTask(msg)
   135  							if err != nil {
   136  								logging.VLog().WithFields(logrus.Fields{
   137  									"err": err,
   138  								}).Debug("Stoped Dag Dispatcher.")
   139  								dp.Stop()
   140  							}
   141  							if isFinish {
   142  								dp.Stop()
   143  							}
   144  						}
   145  					}
   146  				}
   147  			}()
   148  		}
   149  
   150  		if dp.elapseInMs > 0 {
   151  			deadlineTimer := time.NewTimer(time.Duration(dp.elapseInMs) * time.Millisecond)
   152  			<-deadlineTimer.C
   153  			err = ErrTimeout
   154  			dp.Stop()
   155  		}
   156  	}()
   157  
   158  	<-dp.finishCH
   159  	return err
   160  }
   161  
   162  // Stop stop goroutine.
   163  func (dp *Dispatcher) Stop() {
   164  	logging.VLog().Debug("Stopping dag Dispatcher...")
   165  	dp.muTask.Lock()
   166  	defer dp.muTask.Unlock()
   167  	if dp.isFinsih {
   168  		return
   169  	}
   170  	dp.isFinsih = true
   171  
   172  	for i := 0; i < dp.concurrency; i++ {
   173  		select {
   174  		case dp.quitCh <- true:
   175  		default:
   176  		}
   177  	}
   178  	dp.finishCH <- true
   179  }
   180  
   181  // push queue channel
   182  func (dp *Dispatcher) push(vertx *Node) {
   183  	dp.queueCounter++
   184  	dp.queueCh <- vertx
   185  }
   186  
   187  // CompleteParentTask completed parent tasks
   188  func (dp *Dispatcher) onCompleteParentTask(node *Node) (bool, error) {
   189  	dp.muTask.Lock()
   190  	defer dp.muTask.Unlock()
   191  
   192  	key := node.key
   193  
   194  	vertices := dp.dag.GetChildrenNodes(key)
   195  	for _, node := range vertices {
   196  		err := dp.updateDependenceTask(node.key)
   197  		if err != nil {
   198  			return false, err
   199  		}
   200  	}
   201  
   202  	dp.completedCounter++
   203  
   204  	if dp.completedCounter == dp.queueCounter {
   205  		if dp.queueCounter < dp.dag.Len() {
   206  			return false, ErrDagHasCirclular
   207  		}
   208  		return true, nil
   209  	}
   210  
   211  	return false, nil
   212  }
   213  
   214  // updateDependenceTask task counter
   215  func (dp *Dispatcher) updateDependenceTask(key interface{}) error {
   216  	if _, ok := dp.tasks[key]; ok {
   217  		dp.tasks[key].dependence--
   218  		if dp.tasks[key].dependence == 0 {
   219  			dp.push(dp.tasks[key].node)
   220  		}
   221  		if dp.tasks[key].dependence < 0 {
   222  			return ErrDagHasCirclular
   223  		}
   224  	}
   225  	return nil
   226  }