lantins/resque-retry — pull request #61 by dylanahsmith (2012-06-24 17:25:35+00:00)
Title: Get the retry_attempt for DirtyExit failures, where perform isn't called.

Problem:

A job can get infinitely retried through Resque::Worker#prune_dead_workers, using the first retry_delay of the first failure. Even worse, if [resque pull request #623](https://github.com/defunkt/resque/pull/623) gets accepted, this will also happen when a process killed with `kill -9`.

Diagnosis:

`@retry_attempt` depends on the before_perform hooks being called, but before_perform hooks will be called after forking and set in the child job process. If there is a crash that prevents the job from notifying the failure hooks itself, then a DirtyExit exception will be given to the on_failure hooks from a worker processes, where `@retry_attempt` will not have been set.

The `@on_failure_retry_hook_already_called` variable also needs to be ignored, because this is set to false in the before_perform hook.

Solution:

The only situation where the on_failure hooks are called outside for the the forked job process is when a DirtyExit exception is being passed to the on_failure hooks. Therefore, I used this to detect this code path, and explicitly set `@retry_attempt` explicitly. The `@on_failure_retry_hook_already_called` variable is ignored in this case because it is unreliable without the before_perform hook getting called.

This could be a lot cleaner if plugins were called on an instance rather than a class, but that is a poor design decision in `resque`, which will likely be kept for backwards compatiblity.