Skip to content

Queue never retries with a smaller batch after HTTP 413; the whole batch is dropped after the max attempts #388

Open

Description

@ejsmith

In Exceptionless 6.2.0 (core Exceptionless package), when the server responds 413 (Request Entity Too Large), DefaultEventQueue.ProcessAsync lowers a local batchSize and logs "The event will be retried with a smaller batch size", but it then exits the loop immediately. The next run starts again at SubmissionBatchSize, so the smaller batch is never sent.

Where

  • DateTime maxCreatedDate = DateTime.Now;
    int batchSize = _config.SubmissionBatchSize;
    var batch = _storage.GetEventBatch(queueName, batchSize, maxCreatedDate);
    (batchSize is a local reset to _config.SubmissionBatchSize on every run)
  • } else if (response.RequestEntityTooLarge) {
    if (batchSize > 1) {
    _log.Error(typeof(DefaultEventQueue), "Event submission discarded for being too large. The event will be retried with a smaller batch size.");
    batchSize = Math.Max(1, (int)Math.Round(batchSize / 1.5d, 0));
    deleteBatch = false;
    } else {
    _log.Error(typeof(DefaultEventQueue), "Event submission discarded for being too large. The event will not be submitted.");
    }
    (413 handling reduces batchSize and sets deleteBatch = false)
  • if (deleteBatch)
    _storage.DeleteBatch(batch);
    else
    _storage.ReleaseBatch(batch);
    if (!deleteBatch || IsQueueProcessingSuspended)
    break;
    batch = _storage.GetEventBatch(queueName, batchSize, maxCreatedDate);
    (if (!deleteBatch || ...) break; exits before the reduced size is used)
  • public static void CleanupQueueFiles(this IObjectStorage storage, string queueName, TimeSpan? maxAge = null, int? maxAttempts = null) {
    maxAge = maxAge ?? TimeSpan.FromDays(7);
    maxAttempts = maxAttempts ?? 3;
    foreach (var file in storage.GetObjectList(Path.Combine(queueName, "q", "*"), 500).ToList()) {
    if (DateTime.Now.Subtract(file.Created) > maxAge.Value)
    storage.DeleteObject(file.Path);
    if (GetAttempts(file) >= maxAttempts.Value)
    storage.DeleteObject(file.Path);
    }
    and
    public static IList<Tuple<ObjectInfo, Event>> GetEventBatch(this IObjectStorage storage, string queueName, int batchSize = 50, DateTime? maxCreatedDate = null) {
    var events = new List<Tuple<ObjectInfo, Event>>();
    lock (_lockObject) {
    foreach (var file in storage.GetQueueFiles(queueName, batchSize * 5, maxCreatedDate)) {
    if (!storage.LockFile(file))
    continue;
    try {
    storage.IncrementAttempts(file);
    } catch {}
    (each batch fetch increments the attempt count; files at QueueMaxAttempts are deleted)

What happens

  1. A batch of up to 50 events (the default SubmissionBatchSize) gets a 413.
  2. batchSize becomes 33, the batch is released, and the loop breaks.
  3. About 10 seconds later the timer runs ProcessAsync again, which starts at 50 again and resends the same oversized batch.
  4. Each attempt increments the queued files' attempt counter. Once they reach QueueMaxAttempts (default 3), CleanupQueueFiles deletes them.

The result is that every event in the batch is discarded, including events that would have been accepted in a smaller batch. A 413 also does not suspend processing, so the oversized request is resent on every timer tick until the events are deleted.

Expected

After a 413, the queue retries the same events with progressively smaller batches (down to 1). Only an event that is too large on its own is discarded.

Steps to reproduce

  1. Use a submission client (or a test ISubmissionClient) that returns 413 for any request with more than N events.
  2. Queue more than N events and call ProcessQueueAsync() repeatedly.
  3. Every request contains SubmissionBatchSize events. After 3 attempts the events are removed from the queue without being sent.

With the 6.2.0 package, a fake ISubmissionClient that returns 413 for more than 10 events, and 50 queued events, five calls to ProcessQueueAsync() send requests of [50, 50, 50] and then nothing: no smaller batch is ever tried and all 50 events are gone.

Suggested fix

Keep processing in the same run after a 413 (do not break when the batch was released only to shrink it), and/or keep the reduced batch size in a field so later runs use it. Consider not counting a 413 retry against QueueMaxAttempts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions