433 lines
9.4 KiB
Go
433 lines
9.4 KiB
Go
package basic
|
|
|
|
import (
|
|
"container/list"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"fmt"
|
|
"mongo.games.com/goserver/core/container"
|
|
"mongo.games.com/goserver/core/logger"
|
|
"mongo.games.com/goserver/core/utils"
|
|
)
|
|
|
|
const (
|
|
DefaultQueueBacklog int = 4
|
|
)
|
|
|
|
var (
|
|
// Waitor = utils.NewWaitor()
|
|
)
|
|
|
|
// Base class for need alone goroutine objects
|
|
// that easy to start and when to exit the unified management
|
|
// Feature.
|
|
// establish a tree structure between objects
|
|
// asynchronous message queue
|
|
type Object struct {
|
|
*utils.Waitor
|
|
sync.RWMutex
|
|
// Identify
|
|
Id int
|
|
|
|
// Name
|
|
Name string
|
|
|
|
// True if termination was already initiated. If so, we can destroy
|
|
// the object if there are no more child objects or pending term acks.
|
|
terminating bool
|
|
|
|
// True if termination was already finished.
|
|
terminated bool
|
|
|
|
// Sequence number of the last command sent to this object.
|
|
sentSeqnum uint32
|
|
|
|
// Sequence number of the last command processed by this object.
|
|
processedSeqnum uint32
|
|
|
|
// Number of events we have to get before we can destroy the object.
|
|
termAcks int
|
|
|
|
// List of all objects owned by this object. We are responsible
|
|
// for deallocating them before we quit.
|
|
childs *container.SynchronizedMap
|
|
|
|
// Socket owning this object. It's responsible for shutting down
|
|
// this object.
|
|
owner *Object
|
|
|
|
// Command queue
|
|
que *list.List
|
|
|
|
// Configuration Options
|
|
opt Options
|
|
|
|
// Currently resides goroutine id. I do not know how get it.
|
|
gid int
|
|
|
|
//
|
|
waitActive chan struct{}
|
|
//
|
|
waitEnlarge chan struct{}
|
|
|
|
// UserData
|
|
UserData interface{}
|
|
//
|
|
sinker Sinker
|
|
//
|
|
timer *time.Ticker
|
|
//object local storage
|
|
ols [OLS_MAX_SLOT]interface{}
|
|
//
|
|
recvCmdCnt int64
|
|
//
|
|
sendCmdCnt int64
|
|
//
|
|
cond *Cond
|
|
}
|
|
|
|
func NewObject(id int, name string, opt Options, sinker Sinker) *Object {
|
|
o := &Object{
|
|
Id: id,
|
|
Name: name,
|
|
opt: opt,
|
|
sinker: sinker,
|
|
waitActive: make(chan struct{}, 1),
|
|
waitEnlarge: make(chan struct{}, 1),
|
|
childs: container.NewSynchronizedMap(),
|
|
cond: NewCond(1),
|
|
}
|
|
|
|
o.init()
|
|
go func() {
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
logger.Logger.Error(o, "panic, o.ProcessCommand error=", err)
|
|
}
|
|
}()
|
|
o.ProcessCommand()
|
|
}()
|
|
|
|
return o
|
|
}
|
|
|
|
func (o *Object) GetTreeName() string {
|
|
name := o.Name
|
|
parent := o.owner
|
|
for parent != nil {
|
|
name = parent.Name + "/" + name
|
|
parent = parent.owner
|
|
}
|
|
return "/" + name
|
|
}
|
|
|
|
func (o *Object) init() {
|
|
o.que = list.New()
|
|
}
|
|
|
|
// Active inner goroutine
|
|
func (o *Object) Active() {
|
|
o.waitActive <- struct{}{}
|
|
}
|
|
|
|
// Launch the supplied object and become its owner.
|
|
func (o *Object) LaunchChild(c *Object) {
|
|
if c == nil {
|
|
return
|
|
}
|
|
|
|
if c.owner != nil {
|
|
panic("An object can have only one parent node")
|
|
}
|
|
|
|
c.owner = o
|
|
c.Waitor = o.Waitor
|
|
c.Active()
|
|
c.safeStart()
|
|
|
|
// Take ownership of the object.
|
|
SendOwn(o, c)
|
|
}
|
|
|
|
// thread safe
|
|
func (o *Object) GetChildById(id int) *Object {
|
|
c := o.childs.Get(id)
|
|
if cc, ok := c.(*Object); ok {
|
|
return cc
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// When another owned object wants to send command to this object
|
|
// it calls this function to let it know it should not shut down
|
|
// before the command is delivered.
|
|
func (o *Object) incSeqnum() {
|
|
atomic.AddUint32(&(o.sentSeqnum), 1)
|
|
}
|
|
|
|
// Special handler called after a command that requires a seqnum
|
|
// was processed. The implementation should catch up with its counter
|
|
// of processed commands here.
|
|
func (o *Object) ProcessSeqnum() {
|
|
// Catch up with counter of processed commands.
|
|
o.processedSeqnum++
|
|
|
|
// We may have catched up and still have pending terms acks.
|
|
o.checkTermAcks()
|
|
}
|
|
|
|
// Check whether all the peding term acks were delivered.
|
|
// If so, deallocate this object.
|
|
func (o *Object) checkTermAcks() {
|
|
name := o.GetTreeName()
|
|
logger.Logger.Debugf("(%v) object checkTermAcks terminating=%v processedSeqnum=%v sentSeqnum=%v termAcks=%v ", name, o.terminating, o.processedSeqnum, o.sentSeqnum, o.termAcks)
|
|
if o.terminating && o.processedSeqnum == o.sentSeqnum && o.termAcks == 0 {
|
|
|
|
// Sanity check. There should be no active children at this point.
|
|
|
|
// The root object has nobody to confirm the termination to.
|
|
// Other nodes will confirm the termination to the owner.
|
|
if o.owner != nil {
|
|
logger.Logger.Debugf("(%v)->(%v) Object SendTermAck ", o.Name, o.owner.Name)
|
|
SendTermAck(o.owner)
|
|
}
|
|
|
|
// Deallocate the resources.
|
|
o.processDestroy()
|
|
}
|
|
}
|
|
|
|
// Ask owner object to terminate this object. It may take a while
|
|
// while actual termination is started. This function should not be
|
|
// called more than once.
|
|
func (o *Object) Terminate(s *Object) {
|
|
// If termination is already underway, there's no point
|
|
// in starting it anew.
|
|
if o.terminating {
|
|
return
|
|
}
|
|
|
|
name := o.GetTreeName()
|
|
logger.Logger.Debugf("(%v) object Terminate ", name)
|
|
// As for the root of the ownership tree, there's noone to terminate it,
|
|
// so it has to terminate itself.
|
|
if o.owner == nil {
|
|
o.processTerm()
|
|
return
|
|
}
|
|
|
|
// If I am an owned object, I'll ask my owner to terminate me.
|
|
SendTermReq(o.owner, o)
|
|
}
|
|
|
|
// Term handler is protocted rather than private so that it can
|
|
// be intercepted by the derived class. This is useful to add custom
|
|
// steps to the beginning of the termination process.
|
|
func (o *Object) processTerm() {
|
|
// Double termination should never happen.
|
|
if o.terminating {
|
|
return
|
|
}
|
|
|
|
// Send termination request to all owned objects.
|
|
cnt := 0
|
|
childs := o.childs.Items()
|
|
for _, c := range childs {
|
|
if cc, ok := c.(*Object); ok && cc != nil {
|
|
SendTerm(cc)
|
|
cnt++
|
|
}
|
|
}
|
|
o.termAcks += cnt
|
|
|
|
name := o.GetTreeName()
|
|
logger.Logger.Debugf("(%v) object processTerm, termAcks=%v", name, o.termAcks)
|
|
|
|
o.safeStop()
|
|
// Start termination process and check whether by chance we cannot
|
|
// terminate immediately.
|
|
o.terminating = true
|
|
o.checkTermAcks()
|
|
}
|
|
|
|
// A place to hook in when phyicallal destruction of the object
|
|
// is to be delayed.
|
|
func (o *Object) processDestroy() {
|
|
name := o.GetTreeName()
|
|
logger.Logger.Debugf("(%v) object processDestroy ", name)
|
|
o.terminated = true
|
|
//clear ols
|
|
o.OlsClrValue()
|
|
}
|
|
|
|
func (o *Object) GetPendingCommandCnt() int {
|
|
o.RLock()
|
|
cnt := o.que.Len()
|
|
o.RUnlock()
|
|
return cnt
|
|
}
|
|
|
|
// Enqueue command
|
|
func (o *Object) SendCommand(c Command, incseq bool) bool {
|
|
if incseq {
|
|
o.incSeqnum()
|
|
}
|
|
|
|
o.Lock()
|
|
o.que.PushBack(c)
|
|
o.Unlock()
|
|
|
|
atomic.AddInt64(&o.sendCmdCnt, 1)
|
|
|
|
//notify
|
|
o.cond.Signal()
|
|
return true
|
|
}
|
|
|
|
// Dequeue command and process it.
|
|
func (o *Object) ProcessCommand() {
|
|
|
|
//wait for active
|
|
<-o.waitActive
|
|
|
|
//deamon or no
|
|
if o.Waitor != nil {
|
|
o.Waitor.Add(o.Name, 1)
|
|
defer o.Waitor.Done(o.Name)
|
|
}
|
|
|
|
var tickMode bool
|
|
if o.opt.Interval > 0 && o.sinker != nil && o.timer == nil {
|
|
o.timer = time.NewTicker(o.opt.Interval)
|
|
defer o.timer.Stop()
|
|
tickMode = true
|
|
}
|
|
|
|
name := o.GetTreeName()
|
|
logger.Logger.Debug("(", name, ") object active!!!")
|
|
doneCnt := 0
|
|
for !o.terminated {
|
|
cnt := o.GetPendingCommandCnt()
|
|
if cnt == 0 {
|
|
if tickMode {
|
|
if o.cond.WaitForTick(o.timer) {
|
|
//logger.Logger.Debug("(", name, ") object safeTick 1 ", time.Now())
|
|
o.safeTick()
|
|
doneCnt = 0
|
|
continue
|
|
}
|
|
} else {
|
|
o.cond.Wait()
|
|
}
|
|
}
|
|
|
|
o.Lock()
|
|
e := o.que.Front()
|
|
if e != nil {
|
|
o.que.Remove(e)
|
|
}
|
|
o.Unlock()
|
|
|
|
if e != nil {
|
|
if cmd, ok := e.Value.(Command); ok {
|
|
o.safeDone(cmd)
|
|
doneCnt++
|
|
}
|
|
}
|
|
|
|
if tickMode {
|
|
select {
|
|
case <-o.timer.C:
|
|
//logger.Logger.Debug("(", name, ") object safeTick 2 ", time.Now())
|
|
o.safeTick()
|
|
doneCnt = 0
|
|
default:
|
|
}
|
|
// 在一个心跳周期内待处理任务过多
|
|
// cnt 剩余任务数量(待处理任务)
|
|
// MaxDone 允许最大待处理任务数量
|
|
// doneCnt 当前心跳周期内已经处理的任务数量
|
|
if doneCnt > o.opt.MaxDone || cnt > o.opt.MaxDone {
|
|
logger.Logger.Warn("(", name, ") object queue cmd count(", cnt, ") maxdone(", o.opt.MaxDone, ")", " this tick process cnt(", doneCnt, ")")
|
|
}
|
|
}
|
|
}
|
|
|
|
cnt := o.GetPendingCommandCnt()
|
|
logger.Logger.Debug("(", name, ") object ProcessCommand done!!! queue rest cmd count(", cnt, ") ")
|
|
}
|
|
|
|
func (o *Object) safeDone(cmd Command) {
|
|
defer utils.DumpStackIfPanic("Object::Command::Done")
|
|
if StatsWatchMgr != nil {
|
|
watch := StatsWatchMgr.WatchStart(fmt.Sprintf("/object/%v/cmdone", o.Name), 4)
|
|
if watch != nil {
|
|
defer watch.Stop()
|
|
}
|
|
}
|
|
|
|
err := cmd.Done(o)
|
|
atomic.AddInt64(&o.recvCmdCnt, 1)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (o *Object) safeStart() {
|
|
defer utils.DumpStackIfPanic("Object::OnStart")
|
|
|
|
if o.sinker != nil {
|
|
o.sinker.OnStart()
|
|
}
|
|
}
|
|
|
|
func (o *Object) safeTick() {
|
|
defer utils.DumpStackIfPanic("Object::OnTick")
|
|
|
|
if o.sinker != nil {
|
|
o.sinker.OnTick()
|
|
}
|
|
}
|
|
|
|
func (o *Object) safeStop() {
|
|
defer utils.DumpStackIfPanic("Object::OnStop")
|
|
|
|
if o.sinker != nil {
|
|
o.sinker.OnStop()
|
|
}
|
|
}
|
|
|
|
func (o *Object) IsTermiated() bool {
|
|
return o.terminated
|
|
}
|
|
|
|
func (o *Object) StatsSelf() (stats CmdStats) {
|
|
stats.PendingCnt = int64(o.GetPendingCommandCnt())
|
|
stats.SendCmdCnt = atomic.LoadInt64(&o.sendCmdCnt)
|
|
stats.RecvCmdCnt = atomic.LoadInt64(&o.recvCmdCnt)
|
|
return
|
|
}
|
|
|
|
func (o *Object) GetStats() map[string]CmdStats {
|
|
if o.childs == nil {
|
|
return nil
|
|
}
|
|
stats := make(map[string]CmdStats)
|
|
stats[o.GetTreeName()] = o.StatsSelf()
|
|
childs := o.childs.Items()
|
|
for _, c := range childs {
|
|
if cc, ok := c.(*Object); ok && cc != nil {
|
|
stats[cc.GetTreeName()] = cc.StatsSelf()
|
|
subStats := cc.GetStats()
|
|
if subStats != nil && len(subStats) > 0 {
|
|
for k, v := range subStats {
|
|
stats[k] = v
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return stats
|
|
}
|