mirror of
https://github.com/hanzo-fi/stack.git
synced 2026-10-30 20:34:43 +00:00
132 lines
3.4 KiB
Go
132 lines
3.4 KiB
Go
package worker
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/formancehq/stack/libs/go-libs/logging"
|
|
webhooks "github.com/formancehq/webhooks/pkg"
|
|
"github.com/formancehq/webhooks/pkg/storage"
|
|
"github.com/google/uuid"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type Retrier struct {
|
|
httpClient *http.Client
|
|
store storage.Store
|
|
|
|
retriesCron time.Duration
|
|
retryPolicy webhooks.BackoffPolicy
|
|
|
|
stopChan chan chan struct{}
|
|
}
|
|
|
|
func NewRetrier(store storage.Store, httpClient *http.Client, retriesCron time.Duration, retryPolicy webhooks.BackoffPolicy) (*Retrier, error) {
|
|
return &Retrier{
|
|
httpClient: httpClient,
|
|
store: store,
|
|
retriesCron: retriesCron,
|
|
retryPolicy: retryPolicy,
|
|
stopChan: make(chan chan struct{}),
|
|
}, nil
|
|
}
|
|
|
|
func (w *Retrier) Run(ctx context.Context) error {
|
|
errChan := make(chan error)
|
|
ctxWithCancel, cancel := context.WithCancel(ctx)
|
|
defer cancel()
|
|
|
|
go w.attemptRetries(ctxWithCancel, errChan)
|
|
|
|
for {
|
|
select {
|
|
case ch := <-w.stopChan:
|
|
logging.FromContext(ctx).Debug("worker: received from stopChan")
|
|
close(ch)
|
|
return nil
|
|
case <-ctx.Done():
|
|
logging.FromContext(ctx).Debugf("worker: context done: %s", ctx.Err())
|
|
return nil
|
|
case err := <-errChan:
|
|
return errors.Wrap(err, "kafka.Retrier")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (w *Retrier) Stop(ctx context.Context) {
|
|
ch := make(chan struct{})
|
|
select {
|
|
case <-ctx.Done():
|
|
logging.FromContext(ctx).Debugf("worker stopped: context done: %s", ctx.Err())
|
|
return
|
|
case w.stopChan <- ch:
|
|
select {
|
|
case <-ctx.Done():
|
|
logging.FromContext(ctx).Debugf("worker stopped via stopChan: context done: %s", ctx.Err())
|
|
return
|
|
case <-ch:
|
|
logging.FromContext(ctx).Debug("worker stopped via stopChan")
|
|
}
|
|
default:
|
|
logging.FromContext(ctx).Debug("trying to stop worker: no communication")
|
|
}
|
|
}
|
|
|
|
var ErrNoAttemptsFound = errors.New("attemptRetries: no attempts found")
|
|
|
|
func (w *Retrier) attemptRetries(ctx context.Context, errChan chan error) {
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
default:
|
|
// Find all webhookIDs ready to be retried
|
|
webhookIDs, err := w.store.FindWebhookIDsToRetry(ctx)
|
|
if err != nil {
|
|
errChan <- errors.Wrap(err, "storage.Store.FindWebhookIDsToRetry")
|
|
continue
|
|
} else {
|
|
logging.FromContext(ctx).Debugf(
|
|
"found %d distinct webhookIDs to retry: %+v", len(webhookIDs), webhookIDs)
|
|
}
|
|
|
|
for _, webhookID := range webhookIDs {
|
|
atts, err := w.store.FindAttemptsToRetryByWebhookID(ctx, webhookID)
|
|
if err != nil {
|
|
errChan <- errors.Wrap(err, "storage.Store.FindAttemptsToRetryByWebhookID")
|
|
continue
|
|
}
|
|
if len(atts) == 0 {
|
|
errChan <- fmt.Errorf("%w for webhookID: %s", ErrNoAttemptsFound, webhookID)
|
|
continue
|
|
}
|
|
|
|
newAttemptNb := atts[0].RetryAttempt + 1
|
|
attempt, err := webhooks.MakeAttempt(ctx, w.httpClient, w.retryPolicy, uuid.NewString(),
|
|
webhookID, newAttemptNb, atts[0].Config, []byte(atts[0].Payload), false)
|
|
if err != nil {
|
|
errChan <- errors.Wrap(err, "webhooks.MakeAttempt")
|
|
continue
|
|
}
|
|
|
|
if err := w.store.InsertOneAttempt(ctx, attempt); err != nil {
|
|
errChan <- errors.Wrap(err, "storage.Store.InsertOneAttempt retried")
|
|
continue
|
|
}
|
|
|
|
if _, err := w.store.UpdateAttemptsStatus(ctx, webhookID, attempt.Status); err != nil {
|
|
if errors.Is(err, storage.ErrAttemptsNotModified) {
|
|
continue
|
|
}
|
|
errChan <- errors.Wrap(err, "storage.Store.UpdateAttemptsStatus")
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
time.Sleep(w.retriesCron)
|
|
}
|
|
}
|