Billionmail/BillionMail · error

task %d: update database threads failed: %w

Error message

task %d: update database threads failed: %w

What it means

The final step of UpdateTaskThreads writes the new thread count to the database (g.DB update on the task row). If the Update() call errors, it is wrapped with %w as "task %d: update database threads failed: <cause>". The task lookup succeeded but the persistence step failed, so the in-memory pool state may have changed while the DB did not.

Source

Thrown at core/internal/service/batch_mail/task_executor.go:1562

					releaseMsg := fmt.Sprintf("task %d: old pool released", taskId)
					g.Log().Info(context.Background(), releaseMsg)
				}(oldPool, oldPoolSize, runningWorkers)
			}
		} else {
			keepMsg := fmt.Sprintf("task %d: pool size keep unchanged (%d), only adjust rate controller", taskId, oldPoolSize)
			g.Log().Info(context.Background(), keepMsg)
		}
	}

	// update threads in database
	_, err = g.DB().Model("email_tasks").
		Where("id", taskId).
		Data(g.Map{"threads": newThreads}).
		Update()

	if err != nil {

		return fmt.Errorf("task %d: update database threads failed: %w", taskId, err)
	}

	return nil
}

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Inspect the wrapped cause via errors.Is/As to distinguish connectivity, lock-timeout, or schema errors.
  2. Check DB health and re-run the update once connectivity is restored.
  3. Retry with backoff for transient errors (deadlock/serialization); consider optimistic locking if contention is common.
  4. Verify the tasks table schema matches the entity (run pending migrations).

Example fix

// before: fire and forget
_ = e.UpdateTaskThreads(taskId, threads)
// after
if err := e.UpdateTaskThreads(taskId, threads); err != nil {
    log.Printf("persist threads for task %d failed: %v", taskId, err)
    // reconcile pool size from DB or retry
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Go: pre-check the row is updatable before pool resize
count, err := g.DB().Model("tasks").Where("id", taskId).Count()
if err != nil || count == 0 { return fmt.Errorf("task %d not updatable", taskId) }

Type guard

func isTransientDBErr(err error) bool {
    msg := err.Error()
    return strings.Contains(msg, "deadlock") || strings.Contains(msg, "connection reset") || strings.Contains(msg, "i/o timeout")
}

Try / catch

if err := executor.UpdateTaskThreads(taskId, threads); err != nil {
    var wrapped error
    if strings.Contains(err.Error(), "update database threads failed") && errors.As(err, &wrapped) {
        log.Printf("db write failed for task %d: %v", taskId, wrapped)
        if isTransientDBErr(wrapped) { /* retry with backoff */ }
    }
    return err
}

Prevention

When it happens

Trigger: The ORM Update() fails: DB connection dropped mid-operation, unique/lock contention on the task row, table schema mismatch, or context cancellation during the write.

Common situations: Postgres connection pool exhaustion under bulk-send load; deadlock on the tasks row with a concurrent update; migration drift (missing column or changed type) after a version upgrade.

Related errors


AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05). Data as JSON: /api/errors/8359a9f89dee01ff. Report an issue: GitHub.