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 }