RunLoop runs the main event loop that listens for refresh requests and fallback poll ticks. It calls scanFn whenever a scan should happen (rate-limited to scanCooldown). It blocks until ctx is canceled.
(ctx context.Context, scanFn func())
| 243 | // happen (rate-limited to scanCooldown). It blocks until ctx is |
| 244 | // canceled. |
| 245 | func (h *Handler) RunLoop(ctx context.Context, scanFn func()) { |
| 246 | fallbackTicker := h.clock.NewTicker(fallbackPollInterval) |
| 247 | defer fallbackTicker.Stop() |
| 248 | |
| 249 | for { |
| 250 | select { |
| 251 | case <-ctx.Done(): |
| 252 | return |
| 253 | |
| 254 | case <-h.scanTrigger: |
| 255 | h.rateLimitedScan(ctx, scanFn) |
| 256 | |
| 257 | case <-fallbackTicker.C: |
| 258 | // Skip when a recent trigger-driven scan already covered |
| 259 | // this interval, so a busy chat pays near-zero poll cost. |
| 260 | h.mu.Lock() |
| 261 | recent := !h.lastScanAt.IsZero() && |
| 262 | h.clock.Since(h.lastScanAt) < fallbackPollInterval |
| 263 | h.mu.Unlock() |
| 264 | if recent { |
| 265 | continue |
| 266 | } |
| 267 | h.rateLimitedScan(ctx, scanFn) |
| 268 | } |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | func (h *Handler) rateLimitedScan(ctx context.Context, scanFn func()) { |
| 273 | h.mu.Lock() |