github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/internal/repeater/repeater.go (about)

     1  package repeater
     2  
     3  import (
     4  	"context"
     5  	"time"
     6  
     7  	"github.com/jonboulle/clockwork"
     8  
     9  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/backoff"
    10  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/stack"
    11  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext"
    12  	"github.com/ydb-platform/ydb-go-sdk/v3/trace"
    13  )
    14  
    15  type Repeater interface {
    16  	Stop()
    17  	Force()
    18  }
    19  
    20  // repeater contains logic of repeating some task.
    21  type repeater struct {
    22  	// Interval contains an interval between task execution.
    23  	// Interval must be greater than zero; if not, Repeater will panic.
    24  	interval time.Duration
    25  
    26  	name  string
    27  	trace *trace.Driver
    28  
    29  	// Task is a function that must be executed periodically.
    30  	task func(context.Context) error
    31  
    32  	cancel  context.CancelFunc
    33  	stopped chan struct{}
    34  
    35  	force chan struct{}
    36  	clock clockwork.Clock
    37  }
    38  
    39  type option func(r *repeater)
    40  
    41  func WithName(name string) option {
    42  	return func(r *repeater) {
    43  		r.name = name
    44  	}
    45  }
    46  
    47  func WithTrace(trace *trace.Driver) option {
    48  	return func(r *repeater) {
    49  		r.trace = trace
    50  	}
    51  }
    52  
    53  func WithInterval(interval time.Duration) option {
    54  	return func(r *repeater) {
    55  		r.interval = interval
    56  	}
    57  }
    58  
    59  func WithClock(clock clockwork.Clock) option {
    60  	return func(r *repeater) {
    61  		r.clock = clock
    62  	}
    63  }
    64  
    65  type Event = string
    66  
    67  const (
    68  	EventUnknown = Event("")
    69  	EventInit    = Event("init")
    70  	EventTick    = Event("tick")
    71  	EventForce   = Event("force")
    72  	EventCancel  = Event("cancel")
    73  )
    74  
    75  type ctxEventTypeKey struct{}
    76  
    77  func EventType(ctx context.Context) Event {
    78  	if eventType, ok := ctx.Value(ctxEventTypeKey{}).(Event); ok {
    79  		return eventType
    80  	}
    81  
    82  	return EventUnknown
    83  }
    84  
    85  func WithEvent(ctx context.Context, event Event) context.Context {
    86  	return context.WithValue(ctx,
    87  		ctxEventTypeKey{},
    88  		event,
    89  	)
    90  }
    91  
    92  // New creates and begins to execute task periodically.
    93  func New(
    94  	ctx context.Context,
    95  	interval time.Duration,
    96  	task func(ctx context.Context) (err error),
    97  	opts ...option,
    98  ) *repeater {
    99  	ctx, cancel := xcontext.WithCancel(ctx)
   100  
   101  	r := &repeater{
   102  		interval: interval,
   103  		task:     task,
   104  		cancel:   cancel,
   105  		stopped:  make(chan struct{}),
   106  		force:    make(chan struct{}, 1),
   107  		clock:    clockwork.NewRealClock(),
   108  		trace:    &trace.Driver{},
   109  	}
   110  
   111  	for _, opt := range opts {
   112  		if opt != nil {
   113  			opt(r)
   114  		}
   115  	}
   116  
   117  	go r.worker(ctx, r.clock.NewTicker(interval))
   118  
   119  	return r
   120  }
   121  
   122  func (r *repeater) stop(onCancel func()) {
   123  	r.cancel()
   124  	if onCancel != nil {
   125  		onCancel()
   126  	}
   127  	<-r.stopped
   128  }
   129  
   130  // Stop stops to execute its task.
   131  func (r *repeater) Stop() {
   132  	r.stop(nil)
   133  }
   134  
   135  func (r *repeater) Force() {
   136  	select {
   137  	case r.force <- struct{}{}:
   138  	default:
   139  	}
   140  }
   141  
   142  func (r *repeater) wakeUp(e Event) (err error) {
   143  	ctx := WithEvent(context.Background(), e)
   144  
   145  	onDone := trace.DriverOnRepeaterWakeUp(r.trace, &ctx,
   146  		stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/repeater.(*repeater).wakeUp"),
   147  		r.name, e,
   148  	)
   149  	defer func() {
   150  		onDone(err)
   151  
   152  		if err != nil {
   153  			r.Force()
   154  		} else {
   155  			select {
   156  			case <-r.force:
   157  			default:
   158  			}
   159  		}
   160  	}()
   161  
   162  	return r.task(ctx)
   163  }
   164  
   165  func (r *repeater) worker(ctx context.Context, tick clockwork.Ticker) {
   166  	defer close(r.stopped)
   167  	defer tick.Stop()
   168  
   169  	// force returns backoff with delays [500ms...32s]
   170  	force := backoff.New(
   171  		backoff.WithSlotDuration(500*time.Millisecond), //nolint:gomnd
   172  		backoff.WithCeiling(6),                         //nolint:gomnd
   173  		backoff.WithJitterLimit(1),
   174  	)
   175  
   176  	// forceIndex defines delay index for force backoff
   177  	forceIndex := 0
   178  
   179  	waitForceEvent := func() Event {
   180  		if forceIndex == 0 {
   181  			return EventForce
   182  		}
   183  
   184  		force := r.clock.NewTimer(force.Delay(forceIndex))
   185  		defer force.Stop()
   186  
   187  		select {
   188  		case <-ctx.Done():
   189  			return EventCancel
   190  		case <-tick.Chan():
   191  			return EventTick
   192  		case <-force.Chan():
   193  			return EventForce
   194  		}
   195  	}
   196  
   197  	// processEvent func checks wakeup error and returns new force index
   198  	processEvent := func(event Event) {
   199  		if event == EventCancel {
   200  			return
   201  		}
   202  		if err := r.wakeUp(event); err != nil {
   203  			forceIndex++
   204  		} else {
   205  			forceIndex = 0
   206  		}
   207  	}
   208  
   209  	for {
   210  		select {
   211  		case <-ctx.Done():
   212  			return
   213  
   214  		case <-tick.Chan():
   215  			processEvent(EventTick)
   216  
   217  		case <-r.force:
   218  			processEvent(waitForceEvent())
   219  		}
   220  	}
   221  }