(ctx context.Context, logger slog.Logger, db database.Store, enqueuer notifications.Enqueuer, clk quartz.Clock)
| 26 | ) |
| 27 | |
| 28 | func NewReportGenerator(ctx context.Context, logger slog.Logger, db database.Store, enqueuer notifications.Enqueuer, clk quartz.Clock) io.Closer { |
| 29 | closed := make(chan struct{}) |
| 30 | |
| 31 | ctx, cancelFunc := context.WithCancel(ctx) |
| 32 | //nolint:gocritic // The system generates periodic reports without direct user input. |
| 33 | ctx = dbauthz.AsSystemRestricted(ctx) |
| 34 | |
| 35 | // Start the ticker with the initial delay. |
| 36 | ticker := clk.NewTicker(delay) |
| 37 | ticker.Stop() |
| 38 | doTick := func(start time.Time) { |
| 39 | defer ticker.Reset(delay) |
| 40 | // Start a transaction to grab advisory lock, we don't want to run generator jobs at the same time (multiple replicas). |
| 41 | if err := db.InTx(func(tx database.Store) error { |
| 42 | // Acquire a lock to ensure that only one instance of the generator is running at a time. |
| 43 | ok, err := tx.TryAcquireLock(ctx, database.LockIDNotificationsReportGenerator) |
| 44 | if err != nil { |
| 45 | return xerrors.Errorf("failed to acquire report generator lock: %w", err) |
| 46 | } |
| 47 | if !ok { |
| 48 | logger.Debug(ctx, "unable to acquire lock for generating periodic reports, skipping") |
| 49 | return nil |
| 50 | } |
| 51 | |
| 52 | err = reportFailedWorkspaceBuilds(ctx, logger, tx, enqueuer, clk) |
| 53 | if err != nil { |
| 54 | return xerrors.Errorf("unable to generate reports with failed workspace builds: %w", err) |
| 55 | } |
| 56 | |
| 57 | logger.Info(ctx, "report generator finished", slog.F("duration", clk.Since(start))) |
| 58 | |
| 59 | return nil |
| 60 | }, nil); err != nil { |
| 61 | logger.Error(ctx, "failed to generate reports", slog.Error(err)) |
| 62 | return |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | go func() { |
| 67 | defer close(closed) |
| 68 | defer ticker.Stop() |
| 69 | // Force an initial tick. |
| 70 | doTick(dbtime.Time(clk.Now()).UTC()) |
| 71 | for { |
| 72 | select { |
| 73 | case <-ctx.Done(): |
| 74 | logger.Debug(ctx, "closing report generator") |
| 75 | return |
| 76 | case tick := <-ticker.C: |
| 77 | ticker.Stop() |
| 78 | |
| 79 | doTick(dbtime.Time(tick).UTC()) |
| 80 | } |
| 81 | } |
| 82 | }() |
| 83 | return &reportGenerator{ |
| 84 | cancel: cancelFunc, |
| 85 | closed: closed, |
no test coverage detected