Hard daily cap, no-flood shutdown, ctx-aware poller, double-image fix
Four related fixes after Pete flooded a channel and ignored Ctrl-C: 1. Global daily cap (posting.daily_cap_total, default 5): hard ceiling on posts across ALL channels in a rolling 24h window. Checked before the per-channel min-interval and burst-cap. 2. Shutdown no longer flushes the queue. Previous drainAll posted every remaining item with rate limits disabled — which was literally the flood. Replaced with dropOnShutdown that clears queues and logs the count. 3. Poller respects ctx mid-loop. pollOnceWithErr now takes ctx and bails between items, so Ctrl-C doesn't have to wait for ~30s of network per pending story before shutdown can complete. 4. Double-image fix. PostStory now reports imageSent; the queue clears ImageURL before retry so a text-send failure after a successful image upload doesn't re-post the image.
This commit is contained in:
@@ -56,7 +56,7 @@ func (p *Poller) pollSource(ctx context.Context, src config.SourceConfig) {
|
||||
slog.Info("starting poller", "source", src.Name, "interval", interval)
|
||||
|
||||
// Poll immediately on start, then on ticker
|
||||
p.pollOnce(src)
|
||||
p.pollOnce(ctx, src)
|
||||
|
||||
consecutiveFailures := 0
|
||||
const adminWarningThreshold = 5
|
||||
@@ -67,7 +67,7 @@ func (p *Poller) pollSource(ctx context.Context, src config.SourceConfig) {
|
||||
slog.Info("poller stopping", "source", src.Name)
|
||||
return
|
||||
case <-ticker.C:
|
||||
if err := p.pollOnceWithErr(src); err != nil {
|
||||
if err := p.pollOnceWithErr(ctx, src); err != nil {
|
||||
consecutiveFailures++
|
||||
slog.Error("feed poll failed",
|
||||
"source", src.Name,
|
||||
@@ -88,13 +88,13 @@ func (p *Poller) pollSource(ctx context.Context, src config.SourceConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Poller) pollOnce(src config.SourceConfig) {
|
||||
if err := p.pollOnceWithErr(src); err != nil {
|
||||
func (p *Poller) pollOnce(ctx context.Context, src config.SourceConfig) {
|
||||
if err := p.pollOnceWithErr(ctx, src); err != nil {
|
||||
slog.Error("feed poll failed", "source", src.Name, "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Poller) pollOnceWithErr(src config.SourceConfig) error {
|
||||
func (p *Poller) pollOnceWithErr(ctx context.Context, src config.SourceConfig) error {
|
||||
items, err := FetchFeed(src.FeedURL)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -102,6 +102,10 @@ func (p *Poller) pollOnceWithErr(src config.SourceConfig) error {
|
||||
|
||||
newCount := 0
|
||||
for i := range items {
|
||||
if ctx.Err() != nil {
|
||||
slog.Info("poller: cancellation observed mid-loop", "source", src.Name, "processed", newCount)
|
||||
return nil
|
||||
}
|
||||
if storage.IsGUIDSeen(items[i].GUID) {
|
||||
continue
|
||||
}
|
||||
@@ -193,6 +197,9 @@ func (p *Poller) pollOnceWithErr(src config.SourceConfig) error {
|
||||
return nil
|
||||
}
|
||||
for _, s := range unclassified {
|
||||
if ctx.Err() != nil {
|
||||
return nil
|
||||
}
|
||||
// Skip stories we just ingested (they're already being processed above)
|
||||
alreadyProcessed := false
|
||||
for _, item := range items {
|
||||
|
||||
Reference in New Issue
Block a user