mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix(coderd/x/chatd): reset auto archive ticker after runs (#26512)
Prevents slow chat auto-archive runs from causing a constant archival loop by resetting the ticker only after each run completes. Also documents the UTC midnight cutoff used for archive eligibility so chats with activity on the same UTC calendar date stay eligible or ineligible for the full day. Addresses deferred review comments: - https://github.com/coder/coder/pull/26109#discussion_r3380197310 - https://github.com/coder/coder/pull/26109#discussion_r3380219922 Generated by Coder Agents and closely reviewed by Hugo.
This commit is contained in:
@@ -33,13 +33,19 @@ type autoArchivedChat struct {
|
||||
}
|
||||
|
||||
func (w *chatWorker) archiveLoop(ctx context.Context) {
|
||||
run := func(start time.Time) {
|
||||
w.archiveOnce(ctx, dbtime.Time(start).UTC())
|
||||
}
|
||||
run(w.opts.Clock.Now("chatworker", "auto-archive"))
|
||||
|
||||
ticker := w.opts.Clock.NewTicker(w.opts.ArchiveInterval, "chatworker", "auto-archive")
|
||||
defer ticker.Stop()
|
||||
w.archiveOnce(ctx, dbtime.Time(w.opts.Clock.Now("chatworker", "auto-archive")).UTC())
|
||||
defer ticker.Stop("chatworker", "auto-archive")
|
||||
for {
|
||||
select {
|
||||
case tick := <-ticker.C:
|
||||
w.archiveOnce(ctx, dbtime.Time(tick).UTC())
|
||||
ticker.Stop("chatworker", "auto-archive")
|
||||
run(tick)
|
||||
ticker.Reset(w.opts.ArchiveInterval, "chatworker", "auto-archive")
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
@@ -65,7 +71,11 @@ func (w *chatWorker) archiveOnce(ctx context.Context, start time.Time) {
|
||||
return
|
||||
}
|
||||
|
||||
archiveCutoff := dbtime.StartOfDay(start).Add(-time.Duration(autoArchiveDays) * 24 * time.Hour)
|
||||
// Anchor the cutoff at 00:00 UTC so every chat with activity on the
|
||||
// same UTC calendar date stays eligible or ineligible for the whole
|
||||
// day. This avoids trickling chats into auto-archive as wall-clock
|
||||
// time advances.
|
||||
archiveCutoff := dbtime.StartOfDay(start.UTC()).Add(-time.Duration(autoArchiveDays) * 24 * time.Hour)
|
||||
rows, err := w.opts.Store.GetAutoArchiveInactiveChatCandidates(ctx, database.GetAutoArchiveInactiveChatCandidatesParams{
|
||||
ArchiveCutoff: archiveCutoff,
|
||||
LimitCount: w.opts.ArchiveBatchSize,
|
||||
|
||||
@@ -668,8 +668,14 @@ func TestWorker_AutoArchiveLoopRunsImmediatelyAndOnTick(t *testing.T) {
|
||||
opts.ArchiveInterval = time.Minute
|
||||
worker := f.newArchiveWorkerWithOptions(t, opts)
|
||||
|
||||
trap := mClock.Trap().NewTicker("chatworker", "auto-archive")
|
||||
defer trap.Close()
|
||||
nowTrap := mClock.Trap().Now("chatworker", "auto-archive")
|
||||
defer nowTrap.Close()
|
||||
tickerTrap := mClock.Trap().NewTicker("chatworker", "auto-archive")
|
||||
defer tickerTrap.Close()
|
||||
tickerStopTrap := mClock.Trap().TickerStop("chatworker", "auto-archive")
|
||||
defer tickerStopTrap.Close()
|
||||
tickerResetTrap := mClock.Trap().TickerReset("chatworker", "auto-archive")
|
||||
defer tickerResetTrap.Close()
|
||||
|
||||
loopCtx, cancel := context.WithCancel(ctx)
|
||||
done := make(chan struct{})
|
||||
@@ -678,20 +684,26 @@ func TestWorker_AutoArchiveLoopRunsImmediatelyAndOnTick(t *testing.T) {
|
||||
worker.archiveLoop(loopCtx)
|
||||
}()
|
||||
|
||||
// archiveLoop creates the ticker before the immediate startup tick.
|
||||
trap.MustWait(ctx).MustRelease(ctx)
|
||||
nowTrap.MustWait(ctx).MustRelease(ctx)
|
||||
testutil.Eventually(ctx, t, func(context.Context) bool {
|
||||
return f.archived(t, first.ID)
|
||||
}, testutil.IntervalFast, "immediate startup tick should archive the first candidate")
|
||||
tickerTrap.MustWait(ctx).MustRelease(ctx)
|
||||
|
||||
// A second candidate is only archived once the interval ticker fires.
|
||||
second := f.createArchiveCandidate(t, now.Add(-120*24*time.Hour))
|
||||
mClock.Advance(time.Minute).MustWait(ctx)
|
||||
advanced := mClock.Advance(time.Minute)
|
||||
tickerStopTrap.MustWait(ctx).MustRelease(ctx)
|
||||
testutil.Eventually(ctx, t, func(context.Context) bool {
|
||||
return f.archived(t, second.ID)
|
||||
}, testutil.IntervalFast, "interval tick should archive the second candidate")
|
||||
resetCall := tickerResetTrap.MustWait(ctx)
|
||||
require.Equal(t, time.Minute, resetCall.Duration)
|
||||
resetCall.MustRelease(ctx)
|
||||
advanced.MustWait(ctx)
|
||||
|
||||
cancel()
|
||||
tickerStopTrap.MustWait(ctx).MustRelease(ctx)
|
||||
select {
|
||||
case <-done:
|
||||
case <-ctx.Done():
|
||||
|
||||
Reference in New Issue
Block a user