goserver_sync/core/basic/object.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
}