From 7060ecc163782527d096646dd5793ac7e49e510a Mon Sep 17 00:00:00 2001 From: gudvinr Date: Mon, 18 Aug 2025 23:10:18 +0300 Subject: [PATCH] refactor(cli): use time.Duration for scheduler frequency Polling frequency is undocumented so it's not exacly clear what units were. --- internal/cli/scheduler.go | 10 +++---- internal/config/config_test.go | 50 ++++++++++++++++++++++++++++++---- internal/config/options.go | 22 +++++++-------- internal/config/parser.go | 4 +-- 4 files changed, 63 insertions(+), 23 deletions(-) diff --git a/internal/cli/scheduler.go b/internal/cli/scheduler.go index a0ab9ad5..c3028c4a 100644 --- a/internal/cli/scheduler.go +++ b/internal/cli/scheduler.go @@ -26,12 +26,12 @@ func runScheduler(store *storage.Storage, pool *worker.Pool) { go cleanupScheduler( store, - config.Opts.CleanupFrequencyHours(), + config.Opts.CleanupFrequency(), ) } -func feedScheduler(store *storage.Storage, pool *worker.Pool, frequency, batchSize, errorLimit, limitPerHost int) { - for range time.Tick(time.Duration(frequency) * time.Minute) { +func feedScheduler(store *storage.Storage, pool *worker.Pool, frequency time.Duration, batchSize, errorLimit, limitPerHost int) { + for range time.Tick(frequency) { // Generate a batch of feeds for any user that has feeds to refresh. batchBuilder := store.NewBatchBuilder() batchBuilder.WithBatchSize(batchSize) @@ -49,8 +49,8 @@ func feedScheduler(store *storage.Storage, pool *worker.Pool, frequency, batchSi } } -func cleanupScheduler(store *storage.Storage, frequency int) { - for range time.Tick(time.Duration(frequency) * time.Hour) { +func cleanupScheduler(store *storage.Storage, frequency time.Duration) { + for range time.Tick(frequency) { runCleanupTasks(store) } } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 9a9208a7..7acc61e0 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -589,12 +589,22 @@ func TestDefaultCleanupFrequencyHoursValue(t *testing.T) { t.Fatalf(`Parsing failure: %v`, err) } - expected := defaultCleanupFrequencyHours - result := opts.CleanupFrequencyHours() + expected := defaultCleanupFrequency + result := opts.CleanupFrequency() if result != expected { t.Fatalf(`Unexpected CLEANUP_FREQUENCY_HOURS value, got %v instead of %v`, result, expected) } + + sorted := opts.SortedOptions(false) + i := slices.IndexFunc(sorted, func(opt *option) bool { + return opt.Key == "CLEANUP_FREQUENCY_HOURS" + }) + + expectedSerialized := int(defaultCleanupFrequency / time.Hour) + if got := sorted[i].Value; got != expectedSerialized { + t.Fatalf(`Unexpected value in option output, got %q instead of %q`, got, expectedSerialized) + } } func TestCleanupFrequencyHours(t *testing.T) { @@ -608,12 +618,22 @@ func TestCleanupFrequencyHours(t *testing.T) { t.Fatalf(`Parsing failure: %v`, err) } - expected := 42 - result := opts.CleanupFrequencyHours() + expected := 42 * time.Hour + result := opts.CleanupFrequency() if result != expected { t.Fatalf(`Unexpected CLEANUP_FREQUENCY_HOURS value, got %v instead of %v`, result, expected) } + + sorted := opts.SortedOptions(false) + i := slices.IndexFunc(sorted, func(opt *option) bool { + return opt.Key == "CLEANUP_FREQUENCY_HOURS" + }) + + expectedSerialized := 42 + if got := sorted[i].Value; got != expectedSerialized { + t.Fatalf(`Unexpected value in option output, got %q instead of %q`, got, expectedSerialized) + } } func TestDefaultCleanupArchiveReadDaysValue(t *testing.T) { @@ -737,6 +757,16 @@ func TestDefaultPollingFrequencyValue(t *testing.T) { if result != expected { t.Fatalf(`Unexpected POLLING_FREQUENCY value, got %v instead of %v`, result, expected) } + + sorted := opts.SortedOptions(false) + i := slices.IndexFunc(sorted, func(opt *option) bool { + return opt.Key == "POLLING_FREQUENCY" + }) + + expectedSerialized := int(defaultPollingFrequency / time.Minute) + if got := sorted[i].Value; got != expectedSerialized { + t.Fatalf(`Unexpected value in option output, got %q instead of %q`, got, expectedSerialized) + } } func TestPollingFrequency(t *testing.T) { @@ -749,12 +779,22 @@ func TestPollingFrequency(t *testing.T) { t.Fatalf(`Parsing failure: %v`, err) } - expected := 42 + expected := 42 * time.Minute result := opts.PollingFrequency() if result != expected { t.Fatalf(`Unexpected POLLING_FREQUENCY value, got %v instead of %v`, result, expected) } + + sorted := opts.SortedOptions(false) + i := slices.IndexFunc(sorted, func(opt *option) bool { + return opt.Key == "POLLING_FREQUENCY" + }) + + expectedSerialized := 42 + if got := sorted[i].Value; got != expectedSerialized { + t.Fatalf(`Unexpected value in option output, got %q instead of %q`, got, expectedSerialized) + } } func TestDefaultForceRefreshInterval(t *testing.T) { diff --git a/internal/config/options.go b/internal/config/options.go index b2c6eced..5941d30e 100644 --- a/internal/config/options.go +++ b/internal/config/options.go @@ -28,7 +28,7 @@ const ( defaultRootURL = "http://localhost" defaultBasePath = "" defaultWorkerPoolSize = 16 - defaultPollingFrequency = 60 + defaultPollingFrequency = 60 * time.Minute defaultForceRefreshInterval = 30 * time.Second defaultBatchSize = 100 defaultPollingScheduler = "round_robin" @@ -47,7 +47,7 @@ const ( defaultCertFile = "" defaultKeyFile = "" defaultCertDomain = "" - defaultCleanupFrequencyHours = 24 + defaultCleanupFrequency = 24 * time.Hour defaultCleanupArchiveReadDays = 60 defaultCleanupArchiveUnreadDays = 180 defaultCleanupArchiveBatchSize = 10000 @@ -125,7 +125,7 @@ type options struct { certFile string certDomain string certKeyFile string - cleanupFrequencyHours int + cleanupFrequencyInterval time.Duration cleanupArchiveReadDays int cleanupArchiveUnreadDays int cleanupArchiveBatchSize int @@ -137,7 +137,7 @@ type options struct { schedulerEntryFrequencyFactor int schedulerRoundRobinMinInterval time.Duration schedulerRoundRobinMaxInterval time.Duration - pollingFrequency int + pollingFrequency time.Duration pollingLimitPerHost int pollingParsingErrorLimit int pollingScheduler string @@ -209,7 +209,7 @@ func NewOptions() *options { certFile: defaultCertFile, certDomain: defaultCertDomain, certKeyFile: defaultKeyFile, - cleanupFrequencyHours: defaultCleanupFrequencyHours, + cleanupFrequencyInterval: defaultCleanupFrequency, cleanupArchiveReadDays: defaultCleanupArchiveReadDays, cleanupArchiveUnreadDays: defaultCleanupArchiveUnreadDays, cleanupArchiveBatchSize: defaultCleanupArchiveBatchSize, @@ -361,9 +361,9 @@ func (o *options) CertDomain() string { return o.certDomain } -// CleanupFrequencyHours returns the interval in hours for cleanup jobs. -func (o *options) CleanupFrequencyHours() int { - return o.cleanupFrequencyHours +// CleanupFrequencyHours returns the interval for cleanup jobs. +func (o *options) CleanupFrequency() time.Duration { + return o.cleanupFrequencyInterval } // CleanupArchiveReadDays returns the number of days after which marking read items as removed. @@ -402,7 +402,7 @@ func (o *options) BatchSize() int { } // PollingFrequency returns the interval to refresh feeds in the background. -func (o *options) PollingFrequency() int { +func (o *options) PollingFrequency() time.Duration { return o.pollingFrequency } @@ -721,10 +721,10 @@ func (o *options) SortedOptions(redactSecret bool) []*option { "BATCH_SIZE": o.batchSize, "CERT_DOMAIN": o.certDomain, "CERT_FILE": o.certFile, + "CLEANUP_FREQUENCY_HOURS": int(o.cleanupFrequencyInterval.Hours()), "CLEANUP_ARCHIVE_BATCH_SIZE": o.cleanupArchiveBatchSize, "CLEANUP_ARCHIVE_READ_DAYS": o.cleanupArchiveReadDays, "CLEANUP_ARCHIVE_UNREAD_DAYS": o.cleanupArchiveUnreadDays, - "CLEANUP_FREQUENCY_HOURS": o.cleanupFrequencyHours, "CLEANUP_REMOVE_SESSIONS_DAYS": o.cleanupRemoveSessionsDays, "CREATE_ADMIN": o.createAdmin, "DATABASE_CONNECTION_LIFETIME": o.databaseConnectionLifetime, @@ -770,7 +770,7 @@ func (o *options) SortedOptions(redactSecret bool) []*option { "OAUTH2_USER_CREATION": o.oauth2UserCreationAllowed, "DISABLE_LOCAL_AUTH": o.disableLocalAuth, "FORCE_REFRESH_INTERVAL": int(o.forceRefreshInterval.Seconds()), - "POLLING_FREQUENCY": o.pollingFrequency, + "POLLING_FREQUENCY": int(o.pollingFrequency.Minutes()), "POLLING_LIMIT_PER_HOST": o.pollingLimitPerHost, "POLLING_PARSING_ERROR_LIMIT": o.pollingParsingErrorLimit, "POLLING_SCHEDULER": o.pollingScheduler, diff --git a/internal/config/parser.go b/internal/config/parser.go index a0e3165b..eb42c58b 100644 --- a/internal/config/parser.go +++ b/internal/config/parser.go @@ -127,7 +127,7 @@ func (p *parser) parseLines(lines []string) (err error) { case "CERT_DOMAIN": p.opts.certDomain = parseString(value, defaultCertDomain) case "CLEANUP_FREQUENCY_HOURS": - p.opts.cleanupFrequencyHours = parseInt(value, defaultCleanupFrequencyHours) + p.opts.cleanupFrequencyInterval = parseInterval(value, time.Hour, defaultCleanupFrequency) case "CLEANUP_ARCHIVE_READ_DAYS": p.opts.cleanupArchiveReadDays = parseInt(value, defaultCleanupArchiveReadDays) case "CLEANUP_ARCHIVE_UNREAD_DAYS": @@ -143,7 +143,7 @@ func (p *parser) parseLines(lines []string) (err error) { case "BATCH_SIZE": p.opts.batchSize = parseInt(value, defaultBatchSize) case "POLLING_FREQUENCY": - p.opts.pollingFrequency = parseInt(value, defaultPollingFrequency) + p.opts.pollingFrequency = parseInterval(value, time.Minute, defaultPollingFrequency) case "POLLING_LIMIT_PER_HOST": p.opts.pollingLimitPerHost = parseInt(value, 0) case "POLLING_PARSING_ERROR_LIMIT":