github.com/sandwich-go/boost@v1.3.29/xtime/dispatcher.go (about)

     1  package xtime
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/sandwich-go/boost/internal/log"
    10  	"github.com/sandwich-go/boost/xpanic"
    11  
    12  	"github.com/sandwich-go/boost/xsync"
    13  	"github.com/sandwich-go/boost/xtime/cron"
    14  )
    15  
    16  const (
    17  	DefaultTimerDomain = "timer-domain-system"
    18  )
    19  
    20  // Lifecycle manages the start and close lifecycle methods.
    21  type Lifecycle interface {
    22  	Start()
    23  	Close()
    24  }
    25  
    26  // TimerDispatcher manages timer-related functionalities.
    27  type TimerDispatcher interface {
    28  	AfterFunc(d time.Duration, cb func()) *SafeTimer
    29  	AfterFuncWithOwnershipTransfer(td time.Duration, cb func()) *DanglingTimer
    30  	CronFunc(cronExpr *cron.Expression, cb func()) *Cron
    31  	// TickFunc 注册tick回调,key防止cb重复注册
    32  	TickFunc(key interface{}, cb TickFunc)
    33  
    34  	RemoveAllTimer()
    35  	RemoveAllTimerInDomain(domain string)
    36  	AfterFuncInDomain(td time.Duration, cb func(), domain string) *SafeTimer
    37  	AfterFuncWithOwnershipTransferInDomain(td time.Duration, cb func(), domain string) *DanglingTimer
    38  
    39  	TimerNotify() <-chan Timer
    40  }
    41  
    42  type Dispatcher interface {
    43  	TimerDispatcher
    44  	Lifecycle
    45  }
    46  
    47  type TickerDispatcher interface {
    48  	TickerC() <-chan time.Time
    49  	TriggerTickFuncs(context.Context)
    50  }
    51  
    52  type TickFunc func(context.Context)
    53  
    54  // dispatcher one dispatcher per goroutine (goroutine not safe)
    55  type dispatcher struct {
    56  	ChanTimer     chan Timer // one receiver, N senders
    57  	timerMutex    sync.RWMutex
    58  	runningTimers sync.Map
    59  	queueLen      int
    60  	stopChan      chan struct{}
    61  	closeFlag     xsync.AtomicInt32
    62  	cc            *Options
    63  	ticker        *time.Ticker
    64  	tickMutex     sync.RWMutex
    65  	tickFuncs     []*tickHandler
    66  }
    67  
    68  const (
    69  	stateRunning = 0
    70  	stateClosed  = 1
    71  )
    72  
    73  type tickHandler struct {
    74  	key interface{}
    75  	cb  TickFunc
    76  }
    77  
    78  // NewDispatcher 构造新的Dispatcher
    79  // Note:
    80  //   - Dispatcher目前主要服务于pkg/skeleton,用于Actor类型对象的有序动作处理
    81  //   - Dispatcher通过TimerNotify将事件通知到外层,并不执行注册的的回调方法,需逻辑层接管事件通知触发回调逻辑
    82  func NewDispatcher(l int, opts ...Option) Dispatcher {
    83  	d := new(dispatcher)
    84  	d.cc = NewOptions(opts...)
    85  	d.queueLen = l
    86  	d.closeFlag.Set(stateClosed)
    87  	d.Start()
    88  	return d
    89  }
    90  
    91  // Start for skeleton restart the dispatcher
    92  func (d *dispatcher) Start() {
    93  	if d.closeFlag.CompareAndSwap(stateClosed, stateRunning) {
    94  		d.stopChan = make(chan struct{})
    95  		d.ChanTimer = make(chan Timer, d.queueLen)
    96  		if d.cc.TickDuration > 0 {
    97  			d.ticker = time.NewTicker(d.cc.TickDuration)
    98  			d.cc.TickCount.Inc()
    99  			if d.cc.TickHostingMode {
   100  				go d.tickHosting()
   101  			}
   102  		}
   103  	}
   104  }
   105  
   106  func (d *dispatcher) tickHosting() {
   107  	for {
   108  		select {
   109  		case <-d.stopChan:
   110  			return
   111  		case <-d.ticker.C:
   112  			d.TriggerTickFuncs(context.Background())
   113  		}
   114  	}
   115  }
   116  
   117  func (d *dispatcher) TickFunc(key interface{}, cb TickFunc) {
   118  	d.tickMutex.Lock()
   119  	defer d.tickMutex.Unlock()
   120  	for _, h := range d.tickFuncs {
   121  		if h.key == key {
   122  			log.Warn(fmt.Sprintf("multi tick func, key:%s", key))
   123  			return
   124  		}
   125  	}
   126  	d.tickFuncs = append(d.tickFuncs, &tickHandler{
   127  		key: key,
   128  		cb:  cb,
   129  	})
   130  }
   131  
   132  // TickerC 返回ticker 的 chan,用于外部协程接管ticker执行协程
   133  // d := NewDispatcher(
   134  //
   135  //		64,
   136  //		WithTickDuration(time.Millisecond*50),
   137  //		WithTickHostingMode(false),
   138  //	)
   139  //	d.Start()
   140  //	td := d.(TickerDispatcher)
   141  //	for {
   142  //		select {
   143  //		case <-td.TickerC():
   144  //			td.TriggerTickFuncs(context.Background())
   145  //		}
   146  //	}
   147  func (d *dispatcher) TickerC() <-chan time.Time {
   148  	if d.ticker != nil {
   149  		if d.cc.TickHostingMode {
   150  			log.Warn("To indicate that the ticker is externally handled, you can set the TickHostingMode to false")
   151  			return nil
   152  		}
   153  		return d.ticker.C
   154  	}
   155  	return nil
   156  }
   157  
   158  func (d *dispatcher) TriggerTickFuncs(ctx context.Context) {
   159  	if d.closeFlag.Get() != stateRunning {
   160  		return
   161  	}
   162  	d.tickMutex.RLock()
   163  	defer d.tickMutex.RUnlock()
   164  	for _, h := range d.tickFuncs {
   165  		xpanic.Try(func() {
   166  			h.cb(ctx)
   167  		}).Catch(func(err xpanic.E) {
   168  			log.Error(fmt.Sprintf("panic in tick funcs, reason:%v", err))
   169  		})
   170  	}
   171  }
   172  
   173  func (d *dispatcher) TimerNotify() <-chan Timer { return d.ChanTimer }
   174  func (d *dispatcher) Close() {
   175  	if d.closeFlag.CompareAndSwap(stateRunning, stateClosed) {
   176  		close(d.stopChan)
   177  		if d.ticker != nil {
   178  			d.ticker.Stop()
   179  			d.cc.TickCount.Dec()
   180  		}
   181  		// close #174
   182  		d.timerMutex.Lock()
   183  		close(d.ChanTimer)
   184  		d.timerMutex.Unlock()
   185  
   186  		//clear all timers
   187  		for t := range d.ChanTimer {
   188  			t.Cb()
   189  		}
   190  		d.RemoveAllTimer()
   191  	}
   192  }
   193  
   194  func (d *dispatcher) RemoveTimer(t Timer) { d.runningTimers.Delete(t) }
   195  
   196  func (d *dispatcher) RemoveAllTimer() {
   197  	d.runningTimers.Range(func(key, value interface{}) bool {
   198  		t := key.(Timer)
   199  		t.stop()
   200  		d.RemoveTimer(t)
   201  		return true
   202  	})
   203  }
   204  func (d *dispatcher) RemoveAllTimerInDomain(domain string) {
   205  	d.runningTimers.Range(func(key, value interface{}) bool {
   206  		t := key.(Timer)
   207  		if t.GetDomain() != domain {
   208  			return true
   209  		}
   210  		t.stop()
   211  		d.RemoveTimer(t)
   212  		return true
   213  	})
   214  }
   215  
   216  // AfterFuncWithOwnershipTransfer 自己管理 *Timer 声明周期, 使用完毕后需要手动调用 xtime Stop() 方法释放资源, 以免内存泄露
   217  // 可以使用 Reset() 方法重置定时器
   218  func (d *dispatcher) AfterFuncWithOwnershipTransfer(td time.Duration, cb func()) *DanglingTimer {
   219  	return d.AfterFuncWithOwnershipTransferInDomain(td, cb, DefaultTimerDomain)
   220  }
   221  
   222  func (d *dispatcher) AfterFuncWithOwnershipTransferInDomain(td time.Duration, cb func(), domain string) *DanglingTimer {
   223  	t := new(DanglingTimer)
   224  	t.cb = cb
   225  	t.domain = domain
   226  	t.t = time.AfterFunc(td, func() {
   227  		// callback from another goroutine
   228  		select {
   229  		// FIRST read from no buffer chan, even closed, will return false
   230  		case <-d.stopChan:
   231  			return
   232  		default:
   233  			// close #174 (走到这里时,Close被执行了,这里的ChanTimer可能被close了)
   234  			d.timerMutex.RLock()
   235  			if d.closeFlag.Get() == stateRunning {
   236  				d.ChanTimer <- t
   237  			}
   238  			d.timerMutex.RUnlock()
   239  		}
   240  	})
   241  	log.Debug(fmt.Sprintf("Timer dispatcher add AfterFuncInDomain:%s after:%s", domain, td))
   242  	return t
   243  }
   244  
   245  // AfterFunc 框架管理 Timer, dispatcher close时自动释放, 无需手动Stop
   246  // Reset() 方法无效
   247  func (d *dispatcher) AfterFunc(td time.Duration, cb func()) *SafeTimer {
   248  	return d.AfterFuncInDomain(td, cb, DefaultTimerDomain)
   249  }
   250  func (d *dispatcher) AfterFuncInDomain(td time.Duration, cb func(), domain string) *SafeTimer {
   251  	t := new(SafeTimer)
   252  	t.cb = cb
   253  	t.domain = domain
   254  	t.t = time.AfterFunc(td, func() {
   255  		// callback from another goroutine
   256  		select {
   257  		// FIRST read from no buffer chan, even closed, will return false
   258  		case <-d.stopChan:
   259  			return
   260  		default:
   261  			// close #174 (走到这里时,Close被执行了,这里的ChanTimer可能被close了)
   262  			d.timerMutex.RLock()
   263  			if d.closeFlag.Get() == stateRunning {
   264  				d.ChanTimer <- t
   265  				// 这里需要删除,否则runningTimers有泄漏
   266  				d.RemoveTimer(t)
   267  			}
   268  			d.timerMutex.RUnlock()
   269  		}
   270  	})
   271  	d.runningTimers.Store(t, struct{}{})
   272  	log.Debug(fmt.Sprintf("Timer dispatcher add AfterFuncInDomain:%s after:%s", domain, td))
   273  	return t
   274  }
   275  
   276  func (d *dispatcher) CronFunc(cronExpr *cron.Expression, callBack func()) *Cron {
   277  	c := new(Cron)
   278  
   279  	now := time.Now()
   280  	nextTime := cronExpr.Next(now)
   281  	if nextTime.IsZero() {
   282  		return c
   283  	}
   284  
   285  	// callback
   286  	var cb func()
   287  	cb = func() {
   288  		defer callBack()
   289  		now := time.Now()
   290  		nextTime := cronExpr.Next(now)
   291  		if nextTime.IsZero() {
   292  			return
   293  		}
   294  		c.t = d.AfterFunc(nextTime.Sub(now), cb)
   295  	}
   296  
   297  	c.t = d.AfterFunc(nextTime.Sub(now), cb)
   298  	return c
   299  }