Skip to content

Retries

CMDx provides automatic retry functionality for tasks that encounter transient failures. This is essential for handling temporary issues like network timeouts, rate limits, or database locks without manual intervention.

Basic Usage

Configure retries upto n attempts without any delay.

class FetchExternalData < CMDx::Task
  settings retries: 3

  def work
    response = HTTParty.get("https://api.example.com/data")
    context.data = response.parsed_response
  end
end

When an exception occurs during execution, CMDx automatically retries up to the configured limit. Each retry attempt is logged at the warn level with retry metadata. If all retries are exhausted, the task fails with the original exception.

Selective Retries

By default, CMDx retries on StandardError and its subclasses. Narrow this to specific exception types:

class ProcessPayment < CMDx::Task
  settings retries: 5, retry_on: [Stripe::RateLimitError, Net::ReadTimeout]

  def work
    # Your logic here...
  end
end

Important

Only exceptions matching the retry_on configuration will trigger retries. Uncaught exceptions immediately fail the task.

Retry Jitter

Add delays between retry attempts to avoid overwhelming external services or to implement exponential backoff strategies.

Fixed Value

Use a numeric value to calculate linear delay (jitter * current_retry):

class ImportRecords < CMDx::Task
  settings retries: 3, retry_jitter: 0.5

  def work
    # Delays: 0s, 0.5s (retry 1), 1.0s (retry 2), 1.5s (retry 3)
    context.records = ExternalAPI.fetch_records
  end
end

Symbol References

Define an instance method for custom delay logic:

class SyncInventory < CMDx::Task
  settings retries: 5, retry_jitter: :exponential_backoff

  def work
    context.inventory = InventoryAPI.sync
  end

  private

  def exponential_backoff(current_retry)
    2 ** current_retry # 2s, 4s, 8s, 16s, 32s
  end
end

Proc or Lambda

Pass a proc for inline delay calculations:

class PollJobStatus < CMDx::Task
  # Proc
  settings retries: 10, retry_jitter: proc { |retry_count| [retry_count * 0.5, 5.0].min }

  # Lambda
  settings retries: 10, retry_jitter: ->(retry_count) { [retry_count * 0.5, 5.0].min }

  def work
    # Delays: 0.5s, 1.0s, 1.5s, 2.0s, 2.5s, 3.0s, 3.5s, 4.0s, 4.5s, 5.0s (capped)
    context.status = JobAPI.check_status(context.job_id)
  end
end

Class or Module

Implement reusable delay logic in dedicated modules and classes:

class ExponentialBackoff
  def call(task, retry_count)
    base_delay = task.context.base_delay || 1.0
    [base_delay * (2 ** retry_count), 60.0].min
  end
end

class FetchUserProfile < CMDx::Task
  # Class or Module
  settings retries: 4, retry_jitter: ExponentialBackoff

  # Instance
  settings retries: 4, retry_jitter: ExponentialBackoff.new

  def work
    # Your logic here...
  end
end