https://github.com/stitchfix/resqutils
Handy methods, classes, and test support for applications that use Resque
https://github.com/stitchfix/resqutils
gem opensource ruby
Last synced: about 1 year ago
JSON representation
Handy methods, classes, and test support for applications that use Resque
- Host: GitHub
- URL: https://github.com/stitchfix/resqutils
- Owner: stitchfix
- License: apache-2.0
- Created: 2014-09-19T03:31:09.000Z (almost 12 years ago)
- Default Branch: main
- Last Pushed: 2024-11-08T15:41:45.000Z (over 1 year ago)
- Last Synced: 2024-11-08T16:38:38.792Z (over 1 year ago)
- Topics: gem, opensource, ruby
- Language: Ruby
- Homepage: http://www.rubydoc.info/github/stitchfix/resqutils/
- Size: 88.9 KB
- Stars: 5
- Watchers: 89
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE.txt
- Code of conduct: CODE_OF_CONDUCT.md
- Codeowners: .github/CODEOWNERS
Awesome Lists containing this project
README
# resqutils - useful stuff when you have Resque in your app
This is a small library of useful modules and functions that can help dealing with Resque.
Currently:
* Job that kills stale workers
* Means to identify stale workers
* Spec helper `:some_queue.should have_job_queued(class: FooJob)`
* Methods to introspect queues, including the delayed queue, in your specs
* Simple `resque:work` task wrapper to better handle exceptions in the worker
* Marker interface to document jobs which should not be retried
Maybe will have more stuff someday.
## To use
Add to your `Gemfile`:
```ruby
gem 'resqutils'
```
## Stale Workers
It's possible (and, on Heroku, highly likely) that your jobs will appear to be running for "too long". Usually, this happens
when a worker exits without cleaning up after itself. Since Resque stores all state in Redis, and is process-based, it's
actually fairly easy to create this situation.
The good news is that, if your jobs are idempotent, you can just unregister the "stale" workers, which will kick-off the
failed handling (which is hopefully to restart your jobs).
You need a means of identifying these workers, and then killing them.
```ruby
Resqutils::StaleWorkersKiller.kill_stale_workers
```
This will queue a `WorkerKillerJob` for each stale worker. This uses `Resqutils::StaleWorkers` under the covers to identify
which are stale. You can pass it in to `StaleWorkersKiller`'s constructor, or configure it using the environment. By
default, a worker running for more than an hour is considered stale.
Setting the `RESQUTILS_SECONDS_TO_BE_CONSIDERED_STALE` environment variable, you can override that.
The queue that `WorkerKillerJob` will queue to is `worker_killer_job` by default, but can be changed by setting the `RESQUTILS_WORKER_KILLER_JOB_QUEUE` environment variable.
`Resqutils::StaleWorkersKiller` is also itself a resque job, so you can use this class directly in your resque-scheduler
implementation to kill stale jobs on a schedule.
You can, of course, use these building blocks on your own for other purposes.
## Spec Helpers
```ruby
# in spec_helper.rb
require 'resqutils/spec'
# In one of your spec files
describe SomeProcess do
include Resqutils::Spec::ResqueHelpers
# ...
end
```
`require`ing the `resqutils/spec` will also set up the `have_job_queued` matcher, which is likely what you'll want to use.
### Clearing Jobs
The most important part of using Resque in tests as making sure the queue has what you
think it has in it. To that end, you'll likely need `clear_queue` in a `setup` or
`before` block.
```ruby
before do
clear_queue(MyImportantJob) # clears whatever queue this job is configured to use
clear_queue(:foobar) # clear the "foobar" queue
end
```
### Checking that Jobs Were Queued
```ruby
# foo_service.rb
class FooService
def doit(foo)
Resque.enqueue(:foo,FooJob,foo)
"bar"
end
end
# foo_service_spec.rb
describe FooService do
include Resqutils::Spec::ResqueHelpers
before do
clear_queue(FooJob) # Looks at what queue FooJob uses and clears before each test
end
it "queues a job" do
result = FooService.new.doit("blah")
expect(result).to eq("bar")
expect(:foo).to have_job_queued(class: FooJob, args: [ "blah" ])
end
end
```
This also works with the delayed queue as provided by resque-scheduler:
```ruby
# foo_service.rb
class FooService
def doit(foo)
Resque.enqueue_in(5.minutes,:foo,FooJob,foo)
"bar"
end
end
# foo_service_spec.rb
describe FooService do
include Resqutils::Spec::ResqueHelpers
before do
clear_queue(:delayed) # Clears all delayed/scheduled queues
end
it "queues a job" do
result = FooService.new.doit("blah")
expect(result).to eq("bar")
# :delayed is special and triggers logic to look into the various scheduled queues
expect(:delayed).to have_job_queued(class: FooJob, args: [ "blah" ])
end
end
```
### Executing Jobs
In an integration test, you may wish to execute a job that's on the queue, which will both assert that it's there and perform whatever function it performs.
```ruby
# foo_service.rb
class FooService
def doit(foo)
Resque.enqueue(:foo,FooJob,foo)
"bar"
end
end
class FooJob
def perform(some_value)
Foo.create!(value: some_value)
end
end
# the_foo_service_spec.rb
describe "the foo service" do
include Resqutils::Spec::ResqueHelpers
it "writes a Foo with the value" do
result = FooService.new.doit("blah")
process_resque_job(FooJob)
expect(Foo.last.value).to eq("blah")
end
end
```
The `ResqueHelpers` module has many more methods, if you need finer control over your tests with respect to resque.
### Exception Handling in your Worker
The built-in worker lets exceptions bubble up.
In a PaaS setup, or where your Redis is "over the internet", you'll get periodic connection issues from your worker.
These self-heal when your worker management system (e.g. monit) restarts the worker after it crashes.
Thus, these unhandled exceptions should just be ignored.
Since the built-in resque worker is a rake task, we provide a wrapper rake task to call it and log the exception:
```ruby
require 'resqutils/worker_task'
```
To run:
```
env TERM_CHILD=1 bundle exec rake environment resqutils:work QUEUE=file_uploads --trace
```
### Being clear about not retrying
Although you should design your jobs to automatically retry, some jobs simply should not be retried.
Instead of omitting the retry logic or dropping in a comment, you should use a marker interface to communicate intent via code:
```ruby
class DangerousJob
include Resqutils::DoNotAutoRetry
def perform
# ...
end
end
```
This is a more powerful statement that a comment, and communicates intent clearly.