chrisboulton / php-resque

PHP port of resque (Workers and Queueing)
MIT License
3.44k stars 761 forks source link

how to retry a failed job just like resque-web providing a 'retry' button and recording retried? #324

Open ouonet opened 7 years ago

ouonet commented 7 years ago

how to retry a failed job just like resque-web providing a 'retry' button and recording retried?

eric1234 commented 7 years ago

I have a need to retry jobs as well although not manually via a web UI. I want to give a job X attempts with some time in between, then perm fail. To do this I created the following event hook. This looks for a property on the job class called retry_count. If defined, it will retry that many times with one hour between each attempt. If the property is not defined it will not retry.

This could be extended to do some sort of backoff delay instead of an hour each time. Also it assume the php-resque-scheduler is running as it uses this to queue the job in the future.

Putting this code here in case someone like me searching the repo for retry hoping for the desired functionality:

<?php
# Allows a job to be retried. Not all jobs are retryable. To indicate a job
# would like to be allowed to retry just assign a public static attribute named
# `retry_count` with a number of retries you would like to allow.
#
# Depends on php-resque-scheduler to run delayed tasks.
#
# NOTE: This retry mechanism is not 100% solid. There is a brief moment where
# the job is removed from the queue but the retry is not yet scheduled. If
# something goes wrong in those moments the job is lost. For our purposes that
# is good enough.

# Common code for both callbacks.
function if_retry($job, $callback) {
  # For easy reference
  $logger = $job->worker->logger;
  $job_class = $job->payload['class'];
  $args = $job->getArguments();

  # If there is no indication of previous retries but the class does indicate
  # they would like us to retry, initialize the retry_count
  if(!array_key_exists('retry_count', $args) && property_exists($job_class, 'retry_count'))
    $args['retry_count'] = $job_class::$retry_count;

  # Figure out the number of retries remaining.
  $args['retry_count'] -= 1;

  # See if they would like us to retry.
  if( array_key_exists('retry_count', $args) )
    call_user_func($callback, $job_class, $args, $logger);
}

# Pre-emptivly schedule retry under the assumption that a failure will occur
# (some failures like the parent process dying don't allow for detection).
Resque_Event::listen('afterFork', function($job) {
  if_retry($job, function($class, $args, $logger) use($job) {
    # If remaining then requeue in one hour
    if( $args['retry_count'] > 0 ) {
      $run_at = strtotime('1 hour');
      $logger->notice('Pre-emptively scheduling retry to re-run at {time}', ['time' => strftime('%c', $run_at)]);
      ResqueScheduler::enqueueAt($run_at, $job->queue, $class, $args);
    }
  });
});

# The job succeeded, remove the pre-emptive schedule
Resque_Event::listen('afterPerform', function($job) {
  if_retry($job, function($class, $args, $logger) use($job) {
    $logger->notice('Task succeeded, removing pre-emptive retry');
    ResqueScheduler::removeDelayed($job->queue, $class, $args);
  });
});