github.com/aergoio/aergo@v1.3.1/pkg/component/component.go (about)

     1  /**
     2   *  @file
     3   *  @copyright defined in aergo/LICENSE.txt
     4   */
     5  
     6  package component
     7  
     8  import (
     9  	"sync/atomic"
    10  	"time"
    11  
    12  	"github.com/opentracing/opentracing-go"
    13  
    14  	"github.com/aergoio/aergo-actor/actor"
    15  	"github.com/aergoio/aergo-lib/log"
    16  )
    17  
    18  var _ IComponent = (*BaseComponent)(nil)
    19  
    20  type ActorSpan struct {
    21  	span opentracing.Span
    22  }
    23  
    24  // BaseComponent provides a basic implementations for IComponent interface
    25  type BaseComponent struct {
    26  	*log.Logger
    27  	IActor
    28  	name            string
    29  	pid             *actor.PID
    30  	status          Status
    31  	hub             *ComponentHub
    32  	accProcessedMsg uint64
    33  	inbounds        []actor.InboundMiddleware
    34  	outbounds       []actor.OutboundMiddleware
    35  }
    36  
    37  // NewBaseComponent is a helper to create BaseComponent
    38  // This func requires this component's name, implementation of IActor, and
    39  // logger to record internal log msg
    40  // Setting a logger with a same name with the component is recommended
    41  func NewBaseComponent(name string, actor IActor, logger *log.Logger) *BaseComponent {
    42  	return &BaseComponent{
    43  		Logger:          logger,
    44  		IActor:          actor,
    45  		name:            name,
    46  		pid:             nil,
    47  		status:          StoppedStatus,
    48  		hub:             nil,
    49  		accProcessedMsg: 0,
    50  	}
    51  }
    52  
    53  // GetName returns a name of this component
    54  func (base *BaseComponent) GetName() string {
    55  	return base.name
    56  }
    57  
    58  // resumeDecider advices a behavior when panic is occured during receving a msg
    59  // A component, which its strategy is this, will throw away a current failing msg
    60  // and just keep going to process a next msg
    61  func resumeDecider(_ interface{}) actor.Directive {
    62  	return actor.ResumeDirective
    63  }
    64  
    65  // Start inits internal modules and spawns actor process
    66  // let this component
    67  func (base *BaseComponent) Start() {
    68  	// call a init func, defined at an actor's implementation
    69  	base.IActor.BeforeStart()
    70  
    71  	skipResumeStrategy := actor.NewOneForOneStrategy(0, 0, resumeDecider)
    72  
    73  	inbound := func(next actor.ActorFunc) actor.ActorFunc {
    74  		fn := func(c actor.Context) {
    75  			parentSpanId := c.MessageHeader().Get("opentracing-span")
    76  			parentSpan := base.hub.RestoreSpan(parentSpanId)
    77  			var span opentracing.Span
    78  
    79  			if nil == parentSpan {
    80  				span = opentracing.StartSpan(base.name)
    81  			} else {
    82  				span = opentracing.StartSpan(
    83  					base.name,
    84  					opentracing.ChildOf((*parentSpan).Context()))
    85  			}
    86  			spanId := base.hub.SaveSpan(span)
    87  			defer base.hub.DestroySpan(spanId)
    88  
    89  			next(c)
    90  		}
    91  		return fn
    92  	}
    93  	outbound := func(next actor.SenderFunc) actor.SenderFunc {
    94  		fn := func(c actor.Context, target *actor.PID, envelope *actor.MessageEnvelope) {
    95  			if nil == envelope.Header {
    96  				envelope.Header = make(map[string]string)
    97  			}
    98  			parentSpanId := c.MessageHeader().Get("opentracing-span")
    99  			parentSpan := base.hub.RestoreSpan(parentSpanId)
   100  			var span opentracing.Span
   101  
   102  			if nil == parentSpan {
   103  				span = opentracing.StartSpan(base.name)
   104  			} else {
   105  				span = opentracing.StartSpan(
   106  					base.name,
   107  					opentracing.ChildOf((*parentSpan).Context()))
   108  			}
   109  			spanId := base.hub.SaveSpan(span)
   110  			defer base.hub.DestroySpan(spanId)
   111  
   112  			envelope.Header.Set("opentracing-span", spanId)
   113  
   114  			next(c, target, envelope)
   115  		}
   116  		return fn
   117  	}
   118  
   119  	workerProps := actor.FromInstance(base).
   120  		WithGuardian(skipResumeStrategy).
   121  		WithMiddleware(inbound).
   122  		WithOutboundMiddleware(outbound)
   123  
   124  	var err error
   125  	// create and spawn an actor using the name as an unique id
   126  	base.pid, err = actor.SpawnNamed(workerProps, base.GetName())
   127  	// if a same name of pid already exists, retry by attaching a sequential id
   128  	// from actor.ProcessRegistry
   129  	for ; err != nil; base.pid, err = actor.SpawnPrefix(workerProps, base.GetName()) {
   130  		base.Warn().Err(err).Msg("actor name is duplicate")
   131  	}
   132  
   133  	// Wait for the messaging hub to be fully initialized. - Incomplete
   134  	// initialization leads to a crash.
   135  	hubInit.wait()
   136  
   137  	base.IActor.AfterStart()
   138  }
   139  
   140  // Stop lets this component stop and terminate
   141  func (base *BaseComponent) Stop() {
   142  	// call a cleanup func, defined at an actor's implementation
   143  	base.IActor.BeforeStop()
   144  
   145  	base.pid.Stop()
   146  	base.pid = nil
   147  }
   148  
   149  // Tell passes a given message to this component and forgets
   150  func (base *BaseComponent) Tell(message interface{}) {
   151  	if base.pid == nil {
   152  		logger.Warn().Msg("PID is empty")
   153  		return // do nothing
   154  	}
   155  	base.pid.Tell(message)
   156  }
   157  
   158  // TellTo tells (sends and forgets) a message to a target component
   159  // Internally this component will try to find the target component
   160  // using a hub set
   161  func (base *BaseComponent) TellTo(targetCompName string, message interface{}) {
   162  	if base.hub == nil {
   163  		logger.Warn().Str("from", base.GetName()).Str("to", targetCompName).Interface("msg", message).Msg("Hub is not set")
   164  		return // do nothing
   165  	}
   166  	base.hub.Tell(targetCompName, message)
   167  }
   168  
   169  // Request passes a given message to this component.
   170  // And a message sender will expect to get a response in form of
   171  // an actor request
   172  func (base *BaseComponent) Request(message interface{}, sender *actor.PID) {
   173  	if base.pid == nil {
   174  		logger.Warn().Str("to", base.GetName()).Str("from", sender.GetId()).Interface("msg", message).Msg("PID is empty")
   175  		return // do nothing
   176  	}
   177  	base.pid.Request(message, sender)
   178  }
   179  
   180  // RequestTo passes a given message to a target component
   181  // And a message sender, this component, will expect to get a response
   182  // from the target component in form of an actor request
   183  func (base *BaseComponent) RequestTo(targetCompName string, message interface{}) {
   184  	if base.hub == nil {
   185  		logger.Warn().Str("to", targetCompName).Str("from", base.GetName()).Interface("msg", message).Msg("Hub is not set")
   186  		return // do nothing
   187  	}
   188  	targetComp := base.hub.Get(targetCompName)
   189  	targetComp.Request(message, base.pid)
   190  }
   191  
   192  // RequestFuture is similar with Request; passes a given message to this component.
   193  // And this returns a future, that represent an asynchronous result
   194  func (base *BaseComponent) RequestFuture(message interface{}, timeout time.Duration, tip string) *actor.Future {
   195  	if base.pid == nil {
   196  		logger.Warn().Str("to", base.GetName()).Str("from", tip).Interface("msg", message).Msg("PID is empty")
   197  		retFuture := actor.NewFuturePrefix("NilPID", timeout)
   198  		retFuture.PID().Tell(ErrHubUnregistered)
   199  
   200  		return retFuture
   201  	}
   202  
   203  	return base.pid.RequestFuturePrefix(message, tip, timeout)
   204  }
   205  
   206  // RequestToFuture is similar with RequestTo; passes a given message to this component.
   207  // And this returns a future, that represent an asynchronous result
   208  func (base *BaseComponent) RequestToFuture(targetCompName string, message interface{}, timeout time.Duration) *actor.Future {
   209  	if base.hub == nil {
   210  		logger.Warn().Str("from", base.GetName()).Str("to", targetCompName).Interface("msg", message).Msg("Hub is not set")
   211  		retFuture := actor.NewFuturePrefix("NilHub", timeout)
   212  		retFuture.PID().Tell(ErrHubUnregistered)
   213  
   214  		return retFuture
   215  	}
   216  
   217  	return base.hub.RequestFuture(targetCompName, message, timeout, base.name)
   218  }
   219  
   220  // RequestToFutureResult is wrapper of RequestToFuture, but this api doesn't return actor.Future.
   221  // This api can be used when it is possible to use actor.Future type
   222  func (base *BaseComponent) RequestToFutureResult(targetCompName string, message interface{}, timeout time.Duration, tip string) (interface{}, error) {
   223  	retFuture := base.RequestToFuture(targetCompName, message, timeout)
   224  
   225  	return retFuture.Result()
   226  }
   227  
   228  // SetHub assigns a component hub to be used internally
   229  func (base *BaseComponent) SetHub(hub *ComponentHub) {
   230  	base.hub = hub
   231  }
   232  
   233  // Hub returns a component hub set
   234  func (base *BaseComponent) Hub() *ComponentHub {
   235  	return base.hub
   236  }
   237  
   238  // MsgQueueLen gives a number of queued msgs in this component's mailbox
   239  func (base *BaseComponent) MsgQueueLen() int32 {
   240  	return base.pid.MsgNum()
   241  }
   242  
   243  // Receive in the BaseComponent handles system messages and invokes actor's
   244  // receive function; implementation to handle incomming messages
   245  func (base *BaseComponent) Receive(context actor.Context) {
   246  	switch msg := context.Message().(type) {
   247  
   248  	case *actor.Started:
   249  		atomic.SwapUint32(&base.status, StartedStatus)
   250  
   251  	case *actor.Stopping:
   252  		atomic.SwapUint32(&base.status, StoppingStatus)
   253  
   254  	case *actor.Stopped:
   255  		atomic.SwapUint32(&base.status, StoppedStatus)
   256  
   257  	case *actor.Restarting:
   258  		atomic.SwapUint32(&base.status, RestartingStatus)
   259  
   260  	case *CompStatReq:
   261  		base.accProcessedMsg++
   262  		context.Respond(base.statics(msg))
   263  
   264  	default:
   265  		base.accProcessedMsg++
   266  	}
   267  
   268  	base.IActor.Receive(context)
   269  }
   270  
   271  // Status returns status of this component; started, stopped, stopping, restarting
   272  // This func is thread-safe
   273  func (base *BaseComponent) Status() Status {
   274  	return atomic.LoadUint32(&base.status)
   275  }
   276  
   277  func (base *BaseComponent) statics(req *CompStatReq) *CompStatRsp {
   278  	thisMsgLatency := time.Now().Sub(req.SentTime)
   279  
   280  	return &CompStatRsp{
   281  		Status:            StatusToString(base.status),
   282  		AccProcessedMsg:   base.accProcessedMsg,
   283  		MsgQueueLen:       uint64(base.pid.MsgNum()),
   284  		MsgProcessLatency: thisMsgLatency.String(),
   285  		Actor:             base.IActor.Statistics(),
   286  	}
   287  }