github.com/webx-top/com@v1.2.12/goroutine.go (about)

     1  package com
     2  
     3  import (
     4  	"context"
     5  	"log"
     6  	"os"
     7  	"os/signal"
     8  	"sync"
     9  	"sync/atomic"
    10  	"time"
    11  )
    12  
    13  var ErrExitedByContext = context.Canceled
    14  
    15  func Loop(ctx context.Context, exec func() error, duration time.Duration) error {
    16  	if ctx == nil {
    17  		ctx = context.Background()
    18  	}
    19  	check := func() <-chan struct{} {
    20  		return ctx.Done()
    21  	}
    22  	for {
    23  		select {
    24  		case <-check():
    25  			log.Println(CalledAtFileLine(2), ErrExitedByContext)
    26  			return ErrExitedByContext
    27  		default:
    28  			if err := exec(); err != nil {
    29  				return err
    30  			}
    31  			time.Sleep(duration)
    32  		}
    33  	}
    34  }
    35  
    36  // Notify 等待系统信号
    37  // <-Notify()
    38  func Notify(sig ...os.Signal) chan os.Signal {
    39  	terminate := make(chan os.Signal, 1)
    40  	if len(sig) == 0 {
    41  		sig = []os.Signal{os.Interrupt}
    42  	}
    43  	signal.Notify(terminate, sig...)
    44  	return terminate
    45  }
    46  
    47  type DelayOncer interface {
    48  	Do(parentCtx context.Context, key string, f func() error, delayAndTimeout ...time.Duration) (isNew bool)
    49  	DoWithState(parentCtx context.Context, key string, f func(func() bool) error, delayAndTimeout ...time.Duration) (isNew bool)
    50  	Close() error
    51  }
    52  
    53  func NewDelayOnce(delay time.Duration, timeout time.Duration, debugMode ...bool) DelayOncer {
    54  	if timeout <= delay {
    55  		panic(`timeout must be greater than delay`)
    56  	}
    57  	var debug bool
    58  	if len(debugMode) > 0 {
    59  		debug = debugMode[0]
    60  	}
    61  	return &delayOnce{
    62  		mp:      sync.Map{},
    63  		delay:   delay,
    64  		timeout: timeout,
    65  		debug:   debug,
    66  	}
    67  }
    68  
    69  // delayOnce 触发之后延迟一定的时间后再执行。如果在延迟处理的时间段内再次触发,则延迟时间基于此处触发时间顺延
    70  // d := NewDelayOnce(time.Second*5, time.Hour)
    71  // ctx := context.TODO()
    72  //
    73  //	for i:=0; i<10; i++ {
    74  //		d.Do(ctx, `key`,func() error { return nil  })
    75  //	}
    76  type delayOnce struct {
    77  	mp      sync.Map
    78  	delay   time.Duration
    79  	timeout time.Duration
    80  	debug   bool
    81  }
    82  
    83  type eventSession struct {
    84  	cancel  context.CancelFunc
    85  	time    time.Time
    86  	mutex   sync.RWMutex
    87  	stop    chan struct{}
    88  	running atomic.Bool
    89  }
    90  
    91  func (e *eventSession) Renew(t time.Time) {
    92  	e.mutex.Lock()
    93  	e.time = t
    94  	e.mutex.Unlock()
    95  }
    96  
    97  func (e *eventSession) Time() time.Time {
    98  	e.mutex.RLock()
    99  	t := e.time
   100  	e.mutex.RUnlock()
   101  	return t
   102  }
   103  
   104  func (e *eventSession) Cancel() <-chan struct{} {
   105  	e.cancel()
   106  	return e.stop
   107  }
   108  
   109  func (d *delayOnce) checkAndStore(parentCtx context.Context, key string, timeout time.Duration) (*eventSession, context.Context) {
   110  	v, loaded := d.mp.Load(key)
   111  	if loaded {
   112  		session := v.(*eventSession)
   113  		if session.running.Load() {
   114  			return nil, nil
   115  		}
   116  		if time.Since(session.Time()) < timeout { // 超过 d.timeout 后重新处理
   117  			session.Renew(time.Now())
   118  			d.mp.Store(key, session)
   119  			return nil, nil
   120  		}
   121  
   122  		if d.debug {
   123  			log.Println(`[delayOnce] cancel -------------> ` + key)
   124  		}
   125  
   126  		<-session.Cancel()
   127  
   128  		if d.debug {
   129  			log.Println(`[delayOnce] canceled -------------> ` + key)
   130  		}
   131  
   132  		if session.running.Load() {
   133  			return nil, nil
   134  		}
   135  	}
   136  	ctx, cancel := context.WithCancel(parentCtx)
   137  	session := &eventSession{
   138  		cancel: cancel,
   139  		time:   time.Now(),
   140  		stop:   make(chan struct{}, 1),
   141  	}
   142  	d.mp.Store(key, session)
   143  	return session, ctx
   144  }
   145  
   146  func (d *delayOnce) getDelayAndTimeout(delayAndTimeout ...time.Duration) (time.Duration, time.Duration) {
   147  	delay := d.delay
   148  	timeout := d.timeout
   149  	if len(delayAndTimeout) > 0 {
   150  		if delayAndTimeout[0] > 0 {
   151  			delay = delayAndTimeout[0]
   152  		}
   153  		if len(delayAndTimeout) > 1 {
   154  			if delayAndTimeout[1] > 0 {
   155  				timeout = delayAndTimeout[1]
   156  			}
   157  		}
   158  	}
   159  	return delay, timeout
   160  }
   161  
   162  func (d *delayOnce) Do(parentCtx context.Context, key string, f func() error, delayAndTimeout ...time.Duration) (isNew bool) {
   163  	delay, timeout := d.getDelayAndTimeout(delayAndTimeout...)
   164  	session, ctx := d.checkAndStore(parentCtx, key, timeout)
   165  	if session == nil {
   166  		return false
   167  	}
   168  	go func(key string) {
   169  		t := time.NewTicker(time.Second)
   170  		defer t.Stop()
   171  		for {
   172  			select {
   173  			case <-ctx.Done(): // 如果先进入“<-t.C”分支,会等“<-t.C”分支内的代码执行完毕后才有机会执行本分支
   174  				d.mp.Delete(key)
   175  				session.stop <- struct{}{}
   176  				close(session.stop)
   177  				if d.debug {
   178  					log.Println(`[delayOnce] close -------------> ` + key)
   179  				}
   180  				return
   181  			case <-t.C:
   182  				if time.Since(session.Time()) >= delay { // 时间超过d.delay才触发
   183  					session.running.Store(true)
   184  					err := f()
   185  					session.running.Store(false)
   186  					session.Cancel()
   187  					if err != nil {
   188  						log.Println(key+`:`, err)
   189  					}
   190  				}
   191  			}
   192  		}
   193  	}(key)
   194  	return true
   195  }
   196  
   197  func (d *delayOnce) DoWithState(parentCtx context.Context, key string, f func(func() bool) error, delayAndTimeout ...time.Duration) (isNew bool) {
   198  	delay, timeout := d.getDelayAndTimeout(delayAndTimeout...)
   199  	session, ctx := d.checkAndStore(parentCtx, key, timeout)
   200  	if session == nil {
   201  		return false
   202  	}
   203  	go func(key string) {
   204  		var state int32
   205  		isAbort := func() bool {
   206  			return atomic.LoadInt32(&state) > 0
   207  		}
   208  		go func() {
   209  			<-ctx.Done()
   210  			atomic.AddInt32(&state, 1)
   211  		}()
   212  		t := time.NewTicker(time.Second)
   213  		defer t.Stop()
   214  		for {
   215  			select {
   216  			case <-ctx.Done(): // 如果先进入“<-t.C”分支,会等“<-t.C”分支内的代码执行完毕后才有机会执行本分支
   217  				d.mp.Delete(key)
   218  				session.stop <- struct{}{}
   219  				close(session.stop)
   220  				if d.debug {
   221  					log.Println(`[delayOnce] close -------------> ` + key)
   222  				}
   223  				return
   224  			case <-t.C:
   225  				if time.Since(session.Time()) >= delay { // 时间超过d.delay才触发
   226  					session.running.Store(true)
   227  					err := f(isAbort)
   228  					session.running.Store(false)
   229  					session.Cancel()
   230  					if err != nil {
   231  						log.Println(key+`:`, err)
   232  					}
   233  				}
   234  			}
   235  		}
   236  	}(key)
   237  	return true
   238  }
   239  
   240  func (d *delayOnce) Close() error {
   241  	d.mp.Range(func(key, value interface{}) bool {
   242  		session := value.(*eventSession)
   243  		session.Cancel()
   244  		return true
   245  	})
   246  	return nil
   247  }