github.com/TIBCOSoftware/flogo-lib@v0.5.9/engine/runner/worker.go (about)

     1  package runner
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/TIBCOSoftware/flogo-lib/core/action"
     8  	"github.com/TIBCOSoftware/flogo-lib/core/data"
     9  	"github.com/TIBCOSoftware/flogo-lib/logger"
    10  )
    11  
    12  // Based off: http://nesv.github.io/golang/2014/02/25/worker-queues-in-go.html
    13  
    14  // RequestType is value that indicates the type of Request
    15  type RequestType int
    16  
    17  const (
    18  	// RtRun denotes a run action request
    19  	RtRun RequestType = 10
    20  )
    21  
    22  // ActionWorkRequest describes a Request that Worker should handle
    23  type ActionWorkRequest struct {
    24  	ReqType    RequestType
    25  	ID         string
    26  	actionData *ActionData
    27  }
    28  
    29  // ActionData action related data to pass along in a ActionWorkRequest
    30  type ActionData struct {
    31  	context context.Context
    32  	action  action.Action
    33  	inputs  map[string]*data.Attribute
    34  	arc     chan *ActionResult
    35  
    36  	options map[string]interface{}
    37  }
    38  
    39  // ActionResult is a simple struct to hold the results for an Action
    40  type ActionResult struct {
    41  	results map[string]*data.Attribute
    42  	err     error
    43  }
    44  
    45  // A ActionWorker handles WorkRequest, work requests consist of start, restart
    46  // and resume of FlowInstances
    47  type ActionWorker struct {
    48  	ID          int
    49  	runner      *DirectRunner
    50  	Work        chan ActionWorkRequest
    51  	WorkerQueue chan chan ActionWorkRequest
    52  	QuitChan    chan bool
    53  }
    54  
    55  // NewWorker creates, and returns a new Worker object. Its only argument
    56  // is a channel that the worker can add itself to whenever it is done its
    57  // work.
    58  func NewWorker(id int, runner *DirectRunner, workerQueue chan chan ActionWorkRequest) ActionWorker {
    59  	// Create, and return the worker.
    60  	worker := ActionWorker{
    61  		ID:          id,
    62  		runner:      runner,
    63  		Work:        make(chan ActionWorkRequest),
    64  		WorkerQueue: workerQueue,
    65  		QuitChan:    make(chan bool)}
    66  
    67  	return worker
    68  }
    69  
    70  // Start function "starts" the worker by starting a goroutine, that is
    71  // an infinite "for-select" loop.  This is where all the request are handled
    72  func (w ActionWorker) Start() {
    73  	go func() {
    74  		for {
    75  			// Add ourselves into the worker queue.
    76  			w.WorkerQueue <- w.Work
    77  
    78  			select {
    79  			case work := <-w.Work:
    80  				// Receive a work request.
    81  				logger.Debugf("Action-Worker-%d: Received Request", w.ID)
    82  
    83  				switch work.ReqType {
    84  				default:
    85  
    86  					err := fmt.Errorf("unsupported work request type: '%d'", work.ReqType)
    87  					actionData := work.actionData
    88  					actionData.arc <- &ActionResult{err: err}
    89  
    90  				case RtRun:
    91  
    92  					actionData := work.actionData
    93  
    94  					handler := &AsyncResultHandler{result: make(chan *ActionResult), done: make(chan bool, 1)}
    95  
    96  					md := action.GetMetadata(actionData.action)
    97  
    98  					if !md.Async {
    99  						syncAct := actionData.action.(action.SyncAction)
   100  						results, err := syncAct.Run(actionData.context, actionData.inputs)
   101  						logger.Debugf("Action-Worker-%d: Received result: %v", w.ID, results)
   102  						actionData.arc <- &ActionResult{results: results, err: err}
   103  					} else {
   104  						asyncAct := actionData.action.(action.AsyncAction)
   105  
   106  						err := asyncAct.Run(actionData.context, actionData.inputs, handler)
   107  
   108  						if err != nil {
   109  							logger.Debugf("Action-Worker-%d: Action Run error: %s", w.ID, err.Error())
   110  							// error so just return
   111  							actionData.arc <- &ActionResult{err: err}
   112  						} else {
   113  							done := false
   114  
   115  							replied := false
   116  
   117  							//wait for reply
   118  							for !done {
   119  								select {
   120  								case result := <-handler.result:
   121  									if !replied {
   122  										replied = true
   123  										logger.Debugf("Action-Worker-%d: Received result: %#v", w.ID, result)
   124  										actionData.arc <- result
   125  									}
   126  								case <-handler.done:
   127  									if !replied {
   128  										actionData.arc <- &ActionResult{}
   129  									}
   130  									done = true
   131  								}
   132  							}
   133  						}
   134  					}
   135  
   136  					logger.Debugf("Action-Worker-%d: Completed Request", w.ID)
   137  				}
   138  
   139  			case <-w.QuitChan:
   140  				// We have been asked to stop.
   141  				logger.Debugf("Action-Worker-%d: Stopping", w.ID)
   142  				return
   143  			}
   144  		}
   145  	}()
   146  }
   147  
   148  // Stop tells the worker to stop listening for work requests.
   149  //
   150  // Note that the worker will only stop *after* it has finished its work.
   151  func (w ActionWorker) Stop() {
   152  	go func() {
   153  		w.QuitChan <- true
   154  	}()
   155  }
   156  
   157  // AsyncResultHandler simple ResultHandler to use in the asynchronous case
   158  type AsyncResultHandler struct {
   159  	done   chan bool
   160  	result chan *ActionResult
   161  }
   162  
   163  // HandleResult implements action.ResultHandler.HandleResult
   164  func (rh *AsyncResultHandler) HandleResult(results map[string]*data.Attribute, err error) {
   165  	rh.result <- &ActionResult{results: results, err: err}
   166  }
   167  
   168  // Done implements action.ResultHandler.Done
   169  func (rh *AsyncResultHandler) Done() {
   170  	rh.done <- true
   171  }