-
Notifications
You must be signed in to change notification settings - Fork 174
Expand file tree
/
Copy pathsqlite_notification_cleaner.go
More file actions
152 lines (124 loc) · 4.57 KB
/
Copy pathsqlite_notification_cleaner.go
File metadata and controls
152 lines (124 loc) · 4.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package maintenance
import (
"cmp"
"context"
"errors"
"log/slog"
"time"
"github.com/riverqueue/river/riverdriver"
"github.com/riverqueue/river/rivershared/baseservice"
"github.com/riverqueue/river/rivershared/riversharedmaintenance"
"github.com/riverqueue/river/rivershared/startstop"
"github.com/riverqueue/river/rivershared/testsignal"
"github.com/riverqueue/river/rivershared/util/testutil"
"github.com/riverqueue/river/rivershared/util/timeutil"
)
const (
SQLiteNotificationCleanerIntervalDefault = time.Minute
SQLiteNotificationCleanerRetentionPeriodDefault = 5 * time.Minute
)
// SQLiteNotificationCleanerTestSignals are internal signals used exclusively in tests.
type SQLiteNotificationCleanerTestSignals struct {
DeletedBatch testsignal.TestSignal[struct{}] // notifies when runOnce finishes a pass
}
func (ts *SQLiteNotificationCleanerTestSignals) Init(tb testutil.TestingTB) {
ts.DeletedBatch.Init(tb)
}
type SQLiteNotificationCleanerConfig struct {
// Interval is the amount of time to wait between cleaner runs.
Interval time.Duration
// RetentionPeriod is the amount of time to keep notification rows around
// before they're removed.
RetentionPeriod time.Duration
// Schema where River tables are located. Empty string omits schema.
Schema string
// Timeout is the timeout for each delete query.
Timeout time.Duration
}
func (c *SQLiteNotificationCleanerConfig) mustValidate() *SQLiteNotificationCleanerConfig {
if c.Interval <= 0 {
panic("SQLiteNotificationCleanerConfig.Interval must be above zero")
}
if c.RetentionPeriod <= 0 {
panic("SQLiteNotificationCleanerConfig.RetentionPeriod must be above zero")
}
if c.Timeout <= 0 {
panic("SQLiteNotificationCleanerConfig.Timeout must be above zero")
}
return c
}
// SQLiteNotificationCleaner periodically removes old rows from SQLite's
// notification outbox. It is only needed for the SQLite driver's emulated
// listen/notify support.
type SQLiteNotificationCleaner struct {
riversharedmaintenance.QueueMaintainerServiceBase
startstop.BaseStartStop
// exported for test purposes
Config *SQLiteNotificationCleanerConfig
TestSignals SQLiteNotificationCleanerTestSignals
exec riverdriver.Executor
}
// NewSQLiteNotificationCleaner returns a SQLite notification cleaner.
func NewSQLiteNotificationCleaner(archetype *baseservice.Archetype, config *SQLiteNotificationCleanerConfig, exec riverdriver.Executor) *SQLiteNotificationCleaner {
return baseservice.Init(archetype, &SQLiteNotificationCleaner{
Config: (&SQLiteNotificationCleanerConfig{
Interval: cmp.Or(config.Interval, SQLiteNotificationCleanerIntervalDefault),
RetentionPeriod: cmp.Or(config.RetentionPeriod, SQLiteNotificationCleanerRetentionPeriodDefault),
Schema: config.Schema,
Timeout: cmp.Or(config.Timeout, riversharedmaintenance.TimeoutDefault),
}).mustValidate(),
exec: exec,
})
}
func (s *SQLiteNotificationCleaner) Start(ctx context.Context) error { //nolint:dupl
ctx, shouldStart, started, stopped := s.StartInit(ctx)
if !shouldStart {
return nil
}
s.StaggerStart(ctx)
go func() {
started()
defer stopped() // this defer should come first so it's last out
s.Logger.DebugContext(ctx, s.Name+riversharedmaintenance.LogPrefixRunLoopStarted)
defer s.Logger.DebugContext(ctx, s.Name+riversharedmaintenance.LogPrefixRunLoopStopped)
ticker := timeutil.NewTickerWithInitialTick(ctx, s.Config.Interval)
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
}
res, err := s.runOnce(ctx)
if err != nil {
if !errors.Is(err, context.Canceled) {
s.Logger.ErrorContext(ctx, s.Name+": Error cleaning SQLite notifications", slog.String("error", err.Error()))
}
continue
}
if res.NumNotificationsDeleted > 0 {
s.Logger.InfoContext(ctx, s.Name+riversharedmaintenance.LogPrefixRanSuccessfully,
slog.Int("num_notifications_deleted", res.NumNotificationsDeleted),
)
}
}
}()
return nil
}
type sqliteNotificationCleanerRunOnceResult struct {
NumNotificationsDeleted int
}
func (s *SQLiteNotificationCleaner) runOnce(ctx context.Context) (*sqliteNotificationCleanerRunOnceResult, error) {
ctx, cancelFunc := context.WithTimeout(ctx, s.Config.Timeout)
defer cancelFunc()
numDeleted, err := s.exec.NotificationDeleteBefore(ctx, &riverdriver.NotificationDeleteBeforeParams{
CreatedAtHorizon: time.Now().Add(-s.Config.RetentionPeriod),
Schema: s.Config.Schema,
})
if err != nil {
return nil, err
}
s.TestSignals.DeletedBatch.Signal(struct{}{})
return &sqliteNotificationCleanerRunOnceResult{
NumNotificationsDeleted: numDeleted,
}, nil
}