github.com/godaddy-x/freego@v1.0.156/job/chain.go (about)

     1  package job
     2  
     3  import (
     4  	"fmt"
     5  	"runtime"
     6  	"sync"
     7  	"time"
     8  )
     9  
    10  // JobWrapper decorates the given Job with some behavior.
    11  type JobWrapper func(Job) Job
    12  
    13  // Chain is a sequence of JobWrappers that decorates submitted jobs with
    14  // cross-cutting behaviors like logging or synchronization.
    15  type Chain struct {
    16  	wrappers []JobWrapper
    17  }
    18  
    19  // NewChain returns a Chain consisting of the given JobWrappers.
    20  func NewChain(c ...JobWrapper) Chain {
    21  	return Chain{c}
    22  }
    23  
    24  // Then decorates the given job with all JobWrappers in the chain.
    25  //
    26  // This:
    27  //     NewChain(m1, m2, m3).Then(job)
    28  // is equivalent to:
    29  //     m1(m2(m3(job)))
    30  func (c Chain) Then(j Job) Job {
    31  	for i := range c.wrappers {
    32  		j = c.wrappers[len(c.wrappers)-i-1](j)
    33  	}
    34  	return j
    35  }
    36  
    37  // Recover panics in wrapped jobs and zlog them with the provided logger.
    38  func Recover(logger Logger) JobWrapper {
    39  	return func(j Job) Job {
    40  		return FuncJob(func() {
    41  			defer func() {
    42  				if r := recover(); r != nil {
    43  					const size = 64 << 10
    44  					buf := make([]byte, size)
    45  					buf = buf[:runtime.Stack(buf, false)]
    46  					err, ok := r.(error)
    47  					if !ok {
    48  						err = fmt.Errorf("%v", r)
    49  					}
    50  					logger.Error(err, "panic", "stack", "...\n"+string(buf))
    51  				}
    52  			}()
    53  			j.Run()
    54  		})
    55  	}
    56  }
    57  
    58  // DelayIfStillRunning serializes jobs, delaying subsequent runs until the
    59  // previous one is complete. Jobs running after a delay of more than a minute
    60  // have the delay logged at Info.
    61  func DelayIfStillRunning(logger Logger) JobWrapper {
    62  	return func(j Job) Job {
    63  		var mu sync.Mutex
    64  		return FuncJob(func() {
    65  			start := time.Now()
    66  			mu.Lock()
    67  			defer mu.Unlock()
    68  			if dur := time.Since(start); dur > time.Minute {
    69  				logger.Info("delay", "duration", dur)
    70  			}
    71  			j.Run()
    72  		})
    73  	}
    74  }
    75  
    76  // SkipIfStillRunning skips an invocation of the Job if a previous invocation is
    77  // still running. It logs skips to the given logger at Info level.
    78  func SkipIfStillRunning(logger Logger) JobWrapper {
    79  	var ch = make(chan struct{}, 1)
    80  	ch <- struct{}{}
    81  	return func(j Job) Job {
    82  		return FuncJob(func() {
    83  			select {
    84  			case v := <-ch:
    85  				j.Run()
    86  				ch <- v
    87  			default:
    88  				logger.Info("skip")
    89  			}
    90  		})
    91  	}
    92  }