// Package delivery manages asynchronous event delivery // to configured targets. package delivery import ( "context" "fmt" "log/slog" "net/http" "sync" "time" "go.uber.org/fx" "gorm.io/gorm" "sneak.berlin/go/webhooker/internal/database" "sneak.berlin/go/webhooker/internal/lifecycle" "sneak.berlin/go/webhooker/internal/logger" "sneak.berlin/go/webhooker/internal/metrics" ) const ( // deliveryChannelSize is the buffer size for the delivery // channel. New Tasks from the webhook handler are sent // here. Workers drain this channel. Sized large enough // that the webhook handler should never block under // normal load. deliveryChannelSize = 10000 // retryChannelSize is the buffer size for the retry // channel. Timer-fired retries are sent here for // processing by workers. retryChannelSize = 10000 // defaultWorkers is the number of worker goroutines in // the delivery engine pool. At most this many deliveries // are in-flight at any time, preventing goroutine // explosions regardless of queue depth. defaultWorkers = 10 // retrySweepInterval is how often the periodic retry // sweep runs. retrySweepInterval = 60 * time.Second // pendingSweepMinAge is how long a delivery must have sat at // pending before the sweep treats it as stranded rather than as // in flight. // // A delivery is pending from the moment it is created until its // outcome is written, which includes the whole time a worker // spends on it, so the bound has to clear the longest a live // attempt can take: httpClientTimeout plus queueing behind the // other deliveries in front of it. Five minutes is far above // that, and still recovers a stranded delivery in minutes rather // than at the next restart. pendingSweepMinAge = 5 * time.Minute // pendingSweepBatch bounds how many stranded pending deliveries // one sweep of one webhook re-dispatches. The sweep runs every // retrySweepInterval, so a larger backlog drains across // successive sweeps instead of arriving as one burst against a // database that was already struggling to accept writes. pendingSweepBatch = 500 // MaxInlineBodySize is the maximum event body size that // will be carried inline in a Task through the channel. // Bodies at or above this size are left nil and fetched // from the per-webhook database on demand. MaxInlineBodySize = 16 * 1024 // httpClientTimeout is the timeout for outbound HTTP // requests. httpClientTimeout = 30 * time.Second // maxBodyLog is the maximum response body length to // store in DeliveryResult. maxBodyLog = 4096 // maxBackoffShift caps the exponential backoff shift to // avoid integer overflow in the 1<