Commit Graph

60 Commits

Author SHA1 Message Date
Daniel Morton
94ccd5410d
retry_on parameter attempts now accepts :unlimited (#41761)
In some applications, some classes of errors may be raised during the
execution of a job which the developer would want to retry forever.

These classes of errors would most likely be infrastructure problems that
the developer knows will be resolved eventually but may take a variable
amount of time or errors where due to application business logic, there
could be something temporarily blocking the job from executing, like a
resource that is needed for the job being locked for a lengthy amount of
time.

While an arbitrarily large number of attempts could previously be passed,
this is inexpressive as sometimes the developer may just need the job to
continue to be retried until it eventually succeeds. Without this,
developers would need to include additional code to handle the situation
where the job eventually fails its attempts limit and has to be re-enqueued
manually.

As with many things this should be used with caution and only for errors
that the developer knows will definitely eventually be resolved, allowing
the job to continue.

[Daniel Morton + Rafael Mendonça França]
2021-07-28 18:32:19 -04:00
Daniel Morton
ee60ce5606 Communicate enqueue failures to callers of perform_later
There is presently no clean way of telling a caller of `perform_later`
the reason why a job failed to enqueue. When the job is enqueued
successfully, the job object itself is returned, but when the job can
not be enqueued, only `false` is returned. This does not allow callers
to distinguish between classes of failures.

One important class of failures is when the job backend experiences a
network partition when communicating with its underlying datastore. It
is entirely possible for that network partition to recover and as such,
code attempting to enqueue a job may wish to take action to reenqueue
that job after a brief delay. This is distinguished from the class of
failures where due a business rule defined in a callback in the
application, a job fails to enqueue and should not be retried.

This PR changes the following:

- Allows a block to be passed to the `perform_later` method. After the
  `enqueue` method is executed, but before the result is returned, the
  job will be yielded to the block. This allows the code invoking the
  `perform_later` method to inspect the job object, even in failure
  scenarios.

- Adds an exception `EnqueueError` which job adapters can raise if they
  detect a problem specific to their underlying implementation or
  infrastructure during the enqueue process.

- Adds two properties to the job base class: `successfully_enqueued` and
  `enqueue_error`. `enqueue_error` will be populated by the `enqueue`
  method if it rescues an `EnqueueError` raised by the job backend.
  `successfully_enqueued` will be true if the job is not rejected by
  callbacks and does not cause the job backend to raise an
  `EnqueueError` and will be `false` otherwise.

This will allow developers to do something like the following:

    MyJob.perform_later do |job|
      unless job.successfully_enqueued?
        if job.enqueue_error&.message == "Redis was unavailable"
          # invoke some code that will retry the job after a delay
        end
      end
    end
2021-02-05 16:32:43 -05:00
Étienne Barrié
142ae54e54 Allow jobs to rescue all exceptions
Before this commit, only StandardError exceptions can be handled by
rescue_from handlers.

This changes the rescue clause to catch all Exception objects, allowing
rescue handlers to be defined for Exception classes not inheriting from
StandardError.

This means that rescue handlers that are rescuing Exceptions outside of
StandardError exceptions may rescue exceptions that were not being
rescued before this change.

Co-authored-by: Adrianna Chang <adrianna.chang@shopify.com>
2021-01-23 08:35:51 -05:00
Rafael Mendonça França
68b471c4dd
Make sure job instrumentation keep return value
The implementaiton of `instrument` in `ActiveJob::Instrumentation` was
not keeping the API of `ActiveSupport::Notification.instrument` and
returning the value of the block.

Fixes #40931.
2020-12-28 05:30:30 +00:00
Jonathan Hefner
648da12519 Wrap evaluation of db/seeds.rb with the executor
Before #34953, when using the `:async` Active Job queue adapter, jobs
enqueued in `db/seeds.rb`, such as Active Storage analysis jobs, would
cause a hang (see #34939).  Therefore, #34953 changed all jobs enqueued
in `db/seeds.rb` to use the `:inline` queue adapter instead.  (This
behavior was later limited to only take effect when the `:async` adapter
was configured, see #35905.)  However, inline jobs in `db/seeds.rb`
cleared `CurrentAttributes` values (see #37526).  Therefore, #37568
changed the `:inline` adapter to wrap each job in its own thread, for
isolation.  However, wrapping a job in its own thread affects which
database connection it uses.  Thus inline jobs can no longer execute
within the calling thread's database transaction, including seeing any
uncommitted changes.  Additionally, if the calling thread is not wrapped
with the executor, the inline job thread (which is wrapped with the
executor) can deadlock on the load interlock.  And when testing (with
`connection_pool.lock_thread = true`), the inline job thread can
deadlock on one of the locks added by #28083.

Therefore, this commit reverts the solutions of #34953 and #37568, and
instead wraps evaluation of `db/seeds.rb` with the executor.  This
eliminates the original hang from #34939, which was also due to running
multiple threads and not wrapping all of them with the executor.  And,
because nested calls to `executor.wrap` are ignored, any inline jobs in
`db/seeds.rb` will not clear `CurrentAttributes` values.

Alternative fix for #34939.
Reverts #34953.
Reverts #35905.
Partially reverts #35896.

Alternative fix for #37526.
Reverts #37568.

Fixes #40552.
2020-11-18 16:26:20 -06:00
Edouard CHIN
13cb5b78a8 Fix AJ TestAdapter#performed_jobs not properly counting job:
- ### Problem

  If we use `perform_enqueued_jobs` without a block,
  a job that raises an error wouldn't be appended to
  the list of `performed_jobs`.

  ### Solution

  Push the job in the array before it is actually performed.
2020-03-09 19:46:11 -04:00
Edouard CHIN
d35cf4c05d Fix ActiveJob Test adapter not respecting retry attempts:
- ### Problem

  Given the below example the test adapter will retry the job
  indefinitely:

  ```ruby
    class BuggyJob < ActiveJob::Base
      retry_on(Exception,  attempts: 2)

      def perform
        raise "error"
      end
    end

    BuggyJob.perform_later
    perform_enqueued_jobs
  ```

  The problem is that when the job get retried, the
  `exception_executions` variable is not serialized/deserialized,
  resulting in ActiveJob to not be able to determine how many time
  this job was retried.

  The solution in this PR is to deserialize the whole job in the test
  adapter, and reserialize it before retrying.

  Fix #38391
2020-02-10 17:59:20 -04:00
Cliff Pruitt
db0bc5e6d5 Disable ActiveJob retry jitter when given zero/falsey value (#38003)
* Add failing ActiveJob exceptions test for "disable retry jitter"

Thanks to @kaspth for the starting point.

* Update ActiveJob retry jitter to correctly use zero value

* Simplify "disable retry jitter" test

We don't need to repeat this many times. Fewer is shorter.

* Refactor determine_delay with jitter

* Fix indentation

* Close the curtains and give JITTER_DEFAULT some privacy

* Use .zero? instead of == to check jitter value

* Add ActiveJob test for explicit zero jitter

Co-authored-by: Kasper Timm Hansen <hey@kaspth.com>
Co-authored-by: Cliff Pruitt <cliff.pruitt@cliffpruitt.com>
2019-12-17 15:23:52 -03:00
Edouard CHIN
bbfab0b33a Don't run AJ after_enqueue / after_perform when chain is halted:
- ### Problem

  ```ruby
    MyJob < ApplicationJob
      before_enqueue { throw(:abort) }
      after_enqueue { # enters here }
    end
  ```
  I find AJ behaviour on after_enqueue and after_perform callbacks
  weird as they get run even when the callback chain is halted.
  It's counter intuitive to run the after_enqueue callbacks even
  though the job wasn't event enqueued.

  ### Solution

  In Rails 6.2, I propose to make the new behaviour the default
  and stop running after callbacks when the chain is halted.
  For application that wants this behaviour now or in 6.1
  they can do so by adding the `config.active_job.skip_after_callbacks_if_terminated = true`
  in their configuration file.
2019-12-09 17:17:23 +01:00
Edouard CHIN
0d3aec4969 Fix ActiveJob logging when callback chain is halted:
- ### Problem

  ActiveJob will always log "Enqueued MyJob (Job ID) ..." even
  if the job doesn't get enqueued through the adapter.
  Same problem happens when performing a Job, "Performed MyJob (Job ID) ..." will be logged even when job wasn't performed at all.
  This situation can happen either if the callback chain is terminated
  (before_enqueue throwing an `abort`) or if an exception is raised.

  ### Solution

  Check if the callback chain is aborted/exception is raised, and  log accordingly.
2019-12-09 16:17:55 +01:00
Rafael Mendonça França
ce085f62d4
Add an option to disable logging for jobs with sensitive arguments
class SensitiveJob < ApplicationJob
      self.log_arguments = false

      def perform(my_sensitive_argument)
      end
    end

When dealing with sensitive arugments as password and tokens it is
now possible to configure the job to not put the sensitive argument
in the logs.

Closes #34438.
2019-11-08 13:53:23 -05:00
Gannon McGibbon
8319f9ecea Run inline jobs in separate threads 2019-11-01 20:14:40 -04:00
Lucas Mansur
89df4f2e0c Stop queue_name_prefix from being global
Changes in `queue_name_prefix` no longer affects unrelated jobs.
2019-09-13 18:30:19 -03:00
Rafael Mendonça França
dd16e208a6
Require time extensions in the job that depends on time 2019-08-02 00:52:02 -04:00
Ryuta Kamizono
c81af6ae72 Enable Layout/EmptyLinesAroundAccessModifier cop
We sometimes say "✂️ newline after `private`" in a code review (e.g.
https://github.com/rails/rails/pull/18546#discussion_r23188776,
https://github.com/rails/rails/pull/34832#discussion_r244847195).

Now `Layout/EmptyLinesAroundAccessModifier` cop have new enforced style
`EnforcedStyle: only_before` (https://github.com/rubocop-hq/rubocop/pull/7059).

That cop and enforced style will reduce the our code review cost.
2019-06-13 12:00:45 +09:00
Rosa Gutierrez
154057b4f7 Rewrite ActiveJob exception tests so it runs with the real adapters
Previously, by extending ActiveJob::TestCase, the test adapter provided
for tests was being used always, in all executions where supposedly
different adapters were being used. As a consequence, some bugs visible
only for some adapters might have gone undetected. This commit changes
that, skipping queue adapters for which we can't test scheduling jobs
with a delay.
2019-01-08 12:07:38 +01:00
Rosa Gutierrez
88349cee3c Support in-flight jobs stored before individual execution counters for retry_on (#34731)
Also, make tests and examples for individual execution counters
clearer, as it wasn't entierly clear what would happen in this case:

```
retry_on CustomException, OtherException, attempts: 3
```

The job would be retried at most 3 times in total, for both
CustomException and OtherException. To have the job retry 3 times at
most for each exception individually, the following retry_on
declarations are necessary:

```
retry_on CustomException, attempts: 3
retry_on OtherException, attempts: 3
```
2019-01-05 13:54:38 +13:00
Rafael França
299a213a73
Merge pull request #33992 from kirs/enqueue-return-false
Make AJ::Base#enqueue return false if the job wasn't enqueued
2018-12-05 11:24:19 -05:00
Alberto Almagro
95d9c3b3d6 Keep executions for each specific exception (#34352)
* Keep executions for each specific declaration

Fixes #34337

ActiveJob used the global executions counter to control the number of
times a job should be retried. The problem with this approach was that
in case a job raised different exceptions during its executions they
weren't retried the number of times defined by their `attemps` number.

**Example:**

Having the following job:
```ruby
class BuggyJob < ActiveJob::Base
  retry_on CustomException, attemps: 3
  retry_on OtherException, attempts: 3
end
```
If the job raised `CustomException` in the first two executions and then
it raised `OtherException`, the job wasn't retried anymore because the
global executions counter was already indicating 3 attempts.

With this patch each `retry_on` declaration has its specific counter so
that the first two executions that raise `CustomException` don't affect
the retries count that future exceptions may have.

* Revert "clarifies documentation around the attempts arugment to retry_on"

This reverts commit 86aa8f8c5631f77ed9a208e5107003c01512133e.
2018-11-23 11:31:14 -08:00
Kir Shatrov
ee9fc12024 Make AJ::Base#enqueue return false if the job wasn't enqueued 2018-10-28 15:02:44 -04:00
Sharang Dashputre
14e278f876 Make assert_<enqueued|performed>_with() compare hashes ignoring order of keys
The test helpers now treat `{ a: 1, b: 2 }` and `{ b: 2, a: 1 }` as equals
2018-09-21 00:09:40 +05:30
yuuji.yaginuma
e49fb7921e Increment execution count before deserialize arguments
Currently, the execution count increments after deserializes arguments.
Therefore, if an error occurs with deserialize, it retries indefinitely.

In order to prevent this, the count is moved before deserialize.
Fixes #33344.
2018-08-18 11:22:41 +09:00
George Claghorn
3110caecbe Allow passing multiple exceptions to retry_on/discard_on 2018-06-25 18:16:58 -04:00
Kasper Timm Hansen
ba07b5fc12
Rename exception variable to error.
Follows the change from 6fac9bd, so the naming is consistent.
2018-05-21 15:43:16 +02:00
yuuji.yaginuma
6fac9bd599 Pass the error instance as the second parameter of block executed by discard_on
I'm not sure what originally wanted to pass to the argument.
However, as long as see the document added along with the commit, it seems just
to be mistaken that trying to pass the error instance.
https://github.com/rails/rails/pull/30622/files#diff-59beb0189c8c6bc862edf7fdb84ff5a7R64

Fixes #32853
2018-05-12 13:55:25 +09:00
Andrew White
a9d1167b1f Add support for timezones to Active Job
Record what was the current timezone in effect when the job was
enqueued and then restore when the job is executed in same way
that the current locale is recorded and restored.
2018-02-22 14:14:42 +00:00
Aidan Haran
66f34a8ea5
Merge branch 'master' into custom-discarded-job-handling 2017-12-09 13:41:02 +00:00
Kazunori Kajihiro
34d7b05d27 Test exception message to ensure an exception instance is yielded 2017-10-13 11:55:36 +09:00
Aidan Haran
3291fa3630 Allow for custom handling of exceptions that are discarded 2017-09-16 19:59:32 +01:00
Koichi ITO
aa28c5ca65 [Active Job] rubocop -a --only Layout/EmptyLineAfterMagicComment 2017-07-11 13:12:32 +09:00
Kir Shatrov
14ece5e429 Use frozen-string-literal in ActiveJob 2017-07-09 20:50:52 +03:00
Matthew Draper
87b3e226d6 Revert "Merge pull request #29540 from kirs/rubocop-frozen-string"
This reverts commit 3420a14590c0e6915d8b6c242887f74adb4120f9, reversing
changes made to afb66a5a598ce4ac74ad84b125a5abf046dcf5aa.
2017-07-02 02:15:17 +09:30
Kir Shatrov
cfade1ec7e Enforce frozen string in Rubocop 2017-07-01 02:11:03 +03:00
Steven Bull
452f9ee0bc Add error logging to Active Job
Active Job logging instrumentation is changed to log errors (with
backtrace) when a job raises an exception in #perform. This improves
debugging during development and test with the default configuration.

Prior to Rails 5, the default development configuration ran jobs with
InlineAdapter, which would raise exceptions to the caller and be
shown in the development log. In Rails 5, the default adapter was
changed to AsyncAdapter, which would silently swallow exceptions
and log a "Performed SomeJob from Async..." info message. This could
be confusing to a developer, as it would seem that the job was
performed successfully.

This patch removes the "Performed..." info message from the log
and adds an error-level "Error performing SomeJob..." log message
which includes the exception backtrace for jobs that raise an
exception within the #perform method. It provides this behavior for
all adapters.
2017-03-27 17:10:24 -07:00
Yuji Yaginuma
80dc309821 correctly set test adapter when configure the queue adapter on a per job (#26690)
The `ActiveJob::TestHelper` replace the adapter to test adapter in
`before_setup`. It gets the target class using the `descendants`, but if
the test target job class is not loaded, will not be a replacement of
the adapter.
Therefore, instead of replacing with `before_setup`, modified to
replace when setting adapter.

Fixes #26360
2017-01-31 16:37:16 -05:00
Snowmanzzz(Zhengzhong Zhao)
7eef8d35d7 remove useless import 2016-12-30 19:40:26 +08:00
yuuji.yaginuma
0b2197774c use descendants to get class that inherited ActiveJob::Base
`subclasses` get only child classes.
Therefore, if create a job common parent class as `ApplicationJob`,
inherited class does not get properly.
2016-09-05 09:21:10 +09:00
David Heinemeier Hansson
a1e4c197cb Yield the job instance so you have access to things like job.arguments on the custom logic after retries fail 2016-08-16 16:01:59 -07:00
Ryuta Kamizono
762e3f05f3 Add Style/EmptyLines in .rubocop.yml and remove extra empty lines 2016-08-07 17:50:59 +09:00
Xavier Noria
b326e82dc0 applies remaining conventions across the project 2016-08-06 20:20:22 +02:00
Xavier Noria
80e66cc4d9 normalizes indentation and whitespace across the project 2016-08-06 20:16:27 +02:00
Xavier Noria
bde6547bb6 applies new string literal convention in activejob/test
The current code base is not uniform. After some discussion,
we have chosen to go with double quotes by default.
2016-08-06 18:41:18 +02:00
David Heinemeier Hansson
7efd77fae5 Fix tests against ActiveSupport::Durations 2016-08-02 14:27:01 -07:00
David Heinemeier Hansson
9d8d4ee05e Allow for custom handling of exceptions that persist beyond the retry attempts 2016-08-01 16:51:11 -07:00
David Heinemeier Hansson
0be5d5d4c4 Add exponentially_longer and custom wait algorithms 2016-08-01 16:44:29 -07:00
David Heinemeier Hansson
08a92d47b0 Use descriptive exception names 2016-08-01 16:09:16 -07:00
David Heinemeier Hansson
8b5c04e423 Add retry_on/discard_on for better exception handling 2016-07-29 13:54:55 -07:00
Azzurrio
80e825915c Fix accessing provider_job_id inside active jobs for sidekiq adapter 2016-07-28 04:12:15 +02:00
Jean Boussier
be491ecec2 Fix jobs overriding AJ::Base#logger 2016-06-17 13:04:17 -04:00
Yuki Nishijima
266455cf25 Deprecate exception#original_exception in favor of exception#cause 2015-11-03 06:54:34 -08:00