process is responsible for coordinating the retrieval, processing, and delivery of messages. Messages are dispatched concurrently, but they may block when success/failure channels are full. NOTE: it is _possible_ that these goroutines could block for long enough to exceed CODER_NOTIFICATIONS_DISPAT
(ctx context.Context, success chan<- dispatchResult, failure chan<- dispatchResult)
| 156 | // resulting in a failed attempt for each notification when their contexts are canceled; this is not possible with the |
| 157 | // default configurations but could be brought about by an operator tuning things incorrectly. |
| 158 | func (n *notifier) process(ctx context.Context, success chan<- dispatchResult, failure chan<- dispatchResult) error { |
| 159 | msgs, err := n.fetch(ctx) |
| 160 | if err != nil { |
| 161 | return xerrors.Errorf("fetch messages: %w", err) |
| 162 | } |
| 163 | |
| 164 | n.log.Debug(ctx, "dequeued messages", slog.F("count", len(msgs))) |
| 165 | |
| 166 | if len(msgs) == 0 { |
| 167 | return nil |
| 168 | } |
| 169 | |
| 170 | var eg errgroup.Group |
| 171 | for _, msg := range msgs { |
| 172 | // If a notification template has been disabled by the user after a notification was enqueued, mark it as inhibited |
| 173 | if msg.Disabled { |
| 174 | failure <- n.newInhibitedDispatch(msg) |
| 175 | continue |
| 176 | } |
| 177 | |
| 178 | // A message failing to be prepared correctly should not affect other messages. |
| 179 | deliverFn, err := n.prepare(ctx, msg) |
| 180 | if err != nil { |
| 181 | if database.IsQueryCanceledError(err) { |
| 182 | n.log.Debug(ctx, "dispatcher construction canceled", slog.F("msg_id", msg.ID), slog.Error(err)) |
| 183 | } else { |
| 184 | n.log.Error(ctx, "dispatcher construction failed", slog.F("msg_id", msg.ID), slog.Error(err)) |
| 185 | } |
| 186 | failure <- n.newFailedDispatch(msg, err, xerrors.Is(err, decorateHelpersError{})) |
| 187 | n.metrics.PendingUpdates.Set(float64(len(success) + len(failure))) |
| 188 | continue |
| 189 | } |
| 190 | |
| 191 | eg.Go(func() error { |
| 192 | // Dispatch must only return an error for exceptional cases, NOT for failed messages. |
| 193 | return n.deliver(ctx, msg, deliverFn, success, failure) |
| 194 | }) |
| 195 | } |
| 196 | |
| 197 | if err = eg.Wait(); err != nil { |
| 198 | n.log.Debug(ctx, "dispatch failed", slog.Error(err)) |
| 199 | return xerrors.Errorf("dispatch failed: %w", err) |
| 200 | } |
| 201 | |
| 202 | n.log.Debug(ctx, "batch completed", slog.F("count", len(msgs))) |
| 203 | return nil |
| 204 | } |
| 205 | |
| 206 | // fetch retrieves messages from the queue by "acquiring a lease" whereby this notifier is the exclusive handler of these |
| 207 | // messages until they are dispatched - or until the lease expires (in exceptional cases). |
no test coverage detected