1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Add 'activejob/' from commit '14f74a8331f94150dfee653224de8fc837797709'

git-subtree-dir: activejob
git-subtree-mainline: b45b99894a
git-subtree-split: 14f74a8331
This commit is contained in:
Abdelkader Boudih 2014-08-12 09:17:19 +00:00
commit a75f085941
104 changed files with 2276 additions and 0 deletions

2
activejob/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/test/dummy/log/*
/test/dummy/tmp/*

39
activejob/.travis.yml Normal file
View file

@ -0,0 +1,39 @@
before_install:
- travis_retry gem install bundler
- sudo apt-get update -qq
- sudo apt-get install beanstalkd
- echo "START=yes" | sudo tee -a /etc/default/beanstalkd
- sudo /etc/init.d/beanstalkd start
rvm:
- 1.9.3
- 2.0.0
- 2.1
- ruby-head
- rbx-2
- jruby
env:
- QC_DATABASE_URL="postgres://postgres@localhost/active_jobs_qc_int_test" QUE_DATABASE_URL="postgres://postgres@localhost/active_jobs_qc_int_test"
matrix:
allow_failures:
- rvm: rbx-2
- rvm: jruby
- rvm: ruby-head
fast_finish: true
notifications:
email: false
irc:
on_success: change
on_failure: always
channels:
- irc.freenode.org#rails-contrib
campfire:
on_success: change
on_failure: always
rooms:
- secure: AgZwJA+9VdnWAw7QN9Z5s6RpQIzsEB0q7V+p3pCzXY45156WocL8iNQx+KnyOQ8jbRUt4L/XIOiZl5xHf4pHjXytHWHNhetAlVQP/hPeDcCSk/h0g5gqgf6QABdp38tBNaUq866bXHgCOZYPwwP9bypcmuv2SLyfIO+b/PBgqN0=
services:
- redis
- rabbitmq
addons:
postgresql: "9.3"

1
activejob/CHANGELOG.md Normal file
View file

@ -0,0 +1 @@
* Started project.

25
activejob/Gemfile Normal file
View file

@ -0,0 +1,25 @@
source 'https://rubygems.org'
gemspec
gem 'rake'
gem 'resque'
gem 'resque-scheduler'
gem 'sidekiq'
gem 'sucker_punch'
gem 'delayed_job'
gem 'queue_classic'
gem 'sneakers', '0.1.1.pre'
gem 'que'
gem 'backburner'
gem 'qu-rails', github: "bkeepers/qu", branch: "master"
gem 'qu-redis'
#for integration testing
gem 'arel', github: 'rails/arel'
gem 'rack', github: 'rack/rack'
gem 'i18n', github: 'svenfuchs/i18n'
gem 'rails', github: 'rails/rails'
gem 'sqlite3'
gem 'delayed_job_active_record'
gem 'sequel'

206
activejob/Gemfile.lock Normal file
View file

@ -0,0 +1,206 @@
GIT
remote: git://github.com/bkeepers/qu.git
revision: 50f3788f2b55ddd4dc939767fb35aebefa260322
branch: master
specs:
qu (0.2.0)
qu-rails (0.2.0)
qu (= 0.2.0)
railties (>= 3.2, < 5)
qu-redis (0.2.0)
qu (= 0.2.0)
redis-namespace
GIT
remote: git://github.com/rack/rack.git
revision: e98a9f7ef0ddd9589145ea953948c73a8ce3caa9
specs:
rack (1.6.0.alpha)
GIT
remote: git://github.com/rails/arel.git
revision: 66cee768bc163537087037a583f60639eae49fc3
specs:
arel (6.0.0.20140505020427)
GIT
remote: git://github.com/rails/rails.git
revision: b2e88043b52a8f83820a0f4e8a65aa42fd40c544
specs:
actionmailer (4.2.0.alpha)
actionpack (= 4.2.0.alpha)
actionview (= 4.2.0.alpha)
mail (~> 2.5, >= 2.5.4)
actionpack (4.2.0.alpha)
actionview (= 4.2.0.alpha)
activesupport (= 4.2.0.alpha)
rack (~> 1.6.0.alpha)
rack-test (~> 0.6.2)
actionview (4.2.0.alpha)
activesupport (= 4.2.0.alpha)
builder (~> 3.1)
erubis (~> 2.7.0)
activemodel (4.2.0.alpha)
activesupport (= 4.2.0.alpha)
builder (~> 3.1)
activerecord (4.2.0.alpha)
activemodel (= 4.2.0.alpha)
activesupport (= 4.2.0.alpha)
arel (~> 6.0.0)
activesupport (4.2.0.alpha)
i18n (>= 0.7.0.dev, < 0.8)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
thread_safe (~> 0.1)
tzinfo (~> 1.1)
rails (4.2.0.alpha)
actionmailer (= 4.2.0.alpha)
actionpack (= 4.2.0.alpha)
actionview (= 4.2.0.alpha)
activemodel (= 4.2.0.alpha)
activerecord (= 4.2.0.alpha)
activesupport (= 4.2.0.alpha)
bundler (>= 1.3.0, < 2.0)
railties (= 4.2.0.alpha)
sprockets-rails (~> 2.1)
railties (4.2.0.alpha)
actionpack (= 4.2.0.alpha)
activesupport (= 4.2.0.alpha)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
GIT
remote: git://github.com/svenfuchs/i18n.git
revision: cb679b8cdbab675703a3f88de4d48a48f7b50e06
specs:
i18n (0.7.0.dev)
PATH
remote: .
specs:
activejob (4.2.0.alpha)
activemodel-globalid
activesupport (>= 4.1.0)
GEM
remote: https://rubygems.org/
specs:
activemodel-globalid (0.1.1)
activemodel (>= 4.1.0)
activesupport (>= 4.1.0)
amq-protocol (1.9.2)
backburner (0.4.5)
beaneater (~> 0.3.1)
dante (~> 0.1.5)
beaneater (0.3.2)
builder (3.2.2)
bunny (1.1.9)
amq-protocol (>= 1.9.2)
celluloid (0.15.2)
timers (~> 1.1.0)
connection_pool (2.0.0)
dante (0.1.5)
delayed_job (4.0.2)
activesupport (>= 3.0, < 4.2)
delayed_job_active_record (4.0.1)
activerecord (>= 3.0, < 4.2)
delayed_job (>= 3.0, < 4.1)
erubis (2.7.0)
hike (1.2.3)
json (1.8.1)
mail (2.6.1)
mime-types (>= 1.16, < 3)
mime-types (2.3)
minitest (5.4.0)
mono_logger (1.1.0)
multi_json (1.10.1)
pg (0.17.1)
que (0.8.1)
queue_classic (2.2.3)
pg (~> 0.17.0)
rack-protection (1.5.3)
rack
rack-test (0.6.2)
rack (>= 1.0)
rake (10.3.2)
redis (3.1.0)
redis-namespace (1.5.1)
redis (~> 3.0, >= 3.0.4)
resque (1.25.2)
mono_logger (~> 1.0)
multi_json (~> 1.0)
redis-namespace (~> 1.3)
sinatra (>= 0.9.2)
vegas (~> 0.1.2)
resque-scheduler (3.0.0)
mono_logger (~> 1.0)
redis (~> 3.0)
resque (~> 1.25)
rufus-scheduler (~> 2.0)
rufus-scheduler (2.0.24)
tzinfo (>= 0.3.22)
sequel (4.8.0)
serverengine (1.5.9)
sigdump (~> 0.2.2)
sidekiq (3.2.1)
celluloid (>= 0.15.2)
connection_pool (>= 2.0.0)
json
redis (>= 3.0.6)
redis-namespace (>= 1.3.1)
sigdump (0.2.2)
sinatra (1.4.5)
rack (~> 1.4)
rack-protection (~> 1.4)
tilt (~> 1.3, >= 1.3.4)
sneakers (0.1.1.pre)
bunny (~> 1.1.3)
serverengine
thor
thread
sprockets (2.12.1)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sprockets-rails (2.1.3)
actionpack (>= 3.0)
activesupport (>= 3.0)
sprockets (~> 2.8)
sqlite3 (1.3.9)
sucker_punch (1.1)
celluloid (~> 0.15.2)
thor (0.19.1)
thread (0.1.4)
thread_safe (0.3.4)
tilt (1.4.1)
timers (1.1.0)
tzinfo (1.2.1)
thread_safe (~> 0.1)
vegas (0.1.11)
rack (>= 1.0.0)
PLATFORMS
ruby
DEPENDENCIES
activejob!
arel!
backburner
delayed_job
delayed_job_active_record
i18n!
qu-rails!
qu-redis
que
queue_classic
rack!
rails!
rake
resque
resque-scheduler
sequel
sidekiq
sneakers (= 0.1.1.pre)
sqlite3
sucker_punch

21
activejob/MIT-LICENSE Normal file
View file

@ -0,0 +1,21 @@
Copyright (c) 2014 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

120
activejob/README.md Normal file
View file

@ -0,0 +1,120 @@
# Active Job -- Make work happen later
Active Job is a framework for declaring jobs and making them run on a variety
of queueing backends. These jobs can be everything from regularly scheduled
clean-ups, billing charges, or mailings. Anything that can be chopped up into
small units of work and run in parallel, really.
It also serves as the backend for [ActionMailer's #deliver_later functionality](https://github.com/rails/activejob/issues/13)
that makes it easy to turn any mailing into a job for running later. That's
one of the most common jobs in a modern web application: Sending emails outside
of the request-response cycle, so the user doesn't have to wait on it.
The main point is to ensure that all Rails apps will have a job infrastructure
in place, even if it's in the form of an "immediate runner". We can then have
framework features and other gems build on top of that, without having to worry
about API differences between Delayed Job and Resque. Picking your queuing
backend becomes more of an operational concern, then. And you'll be able to
switch between them without having to rewrite your jobs.
## Usage
Set the queue adapter for Active Job:
``` ruby
ActiveJob::Base.queue_adapter = :inline # default queue adapter
# Adapters currently supported: :backburner, :delayed_job, :qu, :que, :queue_classic,
# :resque, :sidekiq, :sneakers, :sucker_punch
```
Declare a job like so:
```ruby
class MyJob < ActiveJob::Base
queue_as :my_jobs
def perform(record)
record.do_work
end
end
```
Enqueue a job like so:
```ruby
MyJob.enqueue record # Enqueue a job to be performed as soon the queueing system is free.
```
```ruby
MyJob.enqueue_at Date.tomorrow.noon, record # Enqueue a job to be performed tomorrow at noon.
```
```ruby
MyJob.enqueue_in 1.week, record # Enqueue a job to be performed 1 week from now.
```
That's it!
## GlobalID support
Active Job supports [GlobalID serialization](https://github.com/rails/activemodel-globalid/) for parameters. This makes it possible
to pass live Active Record objects to your job instead of class/id pairs, which
you then have to manually deserialize. Before, jobs would look like this:
```ruby
class TrashableCleanupJob
def perform(trashable_class, trashable_id, depth)
trashable = trashable_class.constantize.find(trashable_id)
trashable.cleanup(depth)
end
end
```
Now you can simply do:
```ruby
class TrashableCleanupJob
def perform(trashable, depth)
trashable.cleanup(depth)
end
end
```
This works with any class that mixes in ActiveModel::GlobalIdentification, which
by default has been mixed into Active Record classes.
## Supported queueing systems
We currently have adapters for:
* [Backburner](https://github.com/nesquena/backburner)
* [Delayed Job](https://github.com/collectiveidea/delayed_job)
* [Qu](https://github.com/bkeepers/qu)
* [Que](https://github.com/chanks/que)
* [QueueClassic](https://github.com/ryandotsmith/queue_classic)
* [Resque 1.x](https://github.com/resque/resque)
* [Sidekiq](https://github.com/mperham/sidekiq)
* [Sneakers](https://github.com/jondot/sneakers)
* [Sucker Punch](https://github.com/brandonhilkert/sucker_punch)
## Auxiliary gems
* [activejob-stats](https://github.com/seuros/activejob-stats)
## Under development as a gem, targeted for Rails inclusion
Active Job is currently being developed in a separate repository until it's
ready to be merged in with Rails. The current plan is to have Active Job
be part of the Rails 4.2 release, but plans may change depending on when
this framework stabilizes and feels ready.
## License
Active Job is released under the MIT license:
* http://www.opensource.org/licenses/MIT

68
activejob/Rakefile Normal file
View file

@ -0,0 +1,68 @@
require 'bundler/gem_tasks'
require 'rake/testtask'
def run_without_aborting(*tasks)
errors = []
tasks.each do |task|
begin
Rake::Task[task].invoke
rescue Exception
errors << task
end
end
abort "Errors running #{errors.join(', ')}" if errors.any?
end
task default: :test
ADAPTERS = %w(inline delayed_job qu que queue_classic resque sidekiq sneakers sucker_punch backburner)
desc 'Run all adapter tests'
task :test do
tasks = ADAPTERS.map{|a| "test_#{a}" }+["integration_test"]
run_without_aborting(*tasks)
end
ADAPTERS.each do |adapter|
Rake::TestTask.new("test_#{adapter}") do |t|
t.libs << 'test'
t.test_files = FileList['test/cases/**/*_test.rb']
t.verbose = true
end
namespace adapter do
task test: "test_#{adapter}"
task(:env) { ENV['AJADAPTER'] = adapter }
end
task "test_#{adapter}" => "#{adapter}:env"
end
desc 'Run all adapter integration tests'
task :integration_test do
tasks = (ADAPTERS-['inline']).map{|a| "integration_test_#{a}" }
run_without_aborting(*tasks)
end
(ADAPTERS-['inline']).each do |adapter|
Rake::TestTask.new("integration_test_#{adapter}") do |t|
t.libs << 'test'
t.test_files = FileList['test/integration/**/*_test.rb']
t.verbose = true
end
namespace "integration_#{adapter}" do
task test: "integration_test_#{adapter}"
task(:env) do
ENV['AJADAPTER'] = adapter
ENV['AJ_INTEGRATION_TESTS'] = "1"
end
end
task "integration_test_#{adapter}" => "integration_#{adapter}:env"
end

View file

@ -0,0 +1,21 @@
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
s.name = 'activejob'
s.version = '4.2.0.alpha'
s.summary = 'Job framework with pluggable queues (will be part of Rails).'
s.description = 'Declare job classes that can be run by a variety of queueing backends.'
s.required_ruby_version = '>= 1.9.3'
s.license = 'MIT'
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
s.homepage = 'http://www.rubyonrails.org'
s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.rdoc', 'lib/**/*']
s.require_path = 'lib'
s.add_dependency 'activesupport', '>= 4.1.0'
s.add_dependency 'activemodel-globalid'
end

View file

@ -0,0 +1,34 @@
#--
# Copyright (c) 2014 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
require 'active_support'
require 'active_support/rails'
require 'active_job/railtie' if defined?(Rails)
require 'active_job/version'
module ActiveJob
extend ActiveSupport::Autoload
autoload :Base
end

View file

@ -0,0 +1,52 @@
require 'active_model/global_locator'
require 'active_model/global_identification'
module ActiveJob
class Arguments
TYPE_WHITELIST = [ NilClass, Fixnum, Float, String, TrueClass, FalseClass, Bignum ]
def self.serialize(arguments)
arguments.map { |argument| serialize_argument(argument) }
end
def self.deserialize(arguments)
arguments.map { |argument| deserialize_argument(argument) }
end
private
def self.serialize_argument(argument)
case argument
when ActiveModel::GlobalIdentification
argument.global_id.to_s
when *TYPE_WHITELIST
argument
when Array
serialize(argument)
when Hash
Hash[ argument.map { |key, value| [ serialize_hash_key(key), serialize_argument(value) ] } ]
else
raise "Unsupported argument type: #{argument.class.name}"
end
end
def self.deserialize_argument(argument)
case argument
when Array
deserialize(argument)
when Hash
Hash[ argument.map { |key, value| [ key, deserialize_argument(value) ] } ].with_indifferent_access
else
ActiveModel::GlobalLocator.locate(argument) || argument
end
end
def self.serialize_hash_key(key)
case key
when String, Symbol
key.to_s
else
raise "Unsupported hash key type: #{key.class.name}"
end
end
end
end

View file

@ -0,0 +1,22 @@
require 'active_job/queue_adapter'
require 'active_job/queue_name'
require 'active_job/enqueuing'
require 'active_job/execution'
require 'active_job/callbacks'
require 'active_job/identifier'
require 'active_job/logging'
module ActiveJob
class Base
extend QueueAdapter
include QueueName
include Enqueuing
include Execution
include Callbacks
include Identifier
include Logging
ActiveSupport.run_load_hooks(:active_job, self)
end
end

View file

@ -0,0 +1,40 @@
require 'active_support/callbacks'
module ActiveJob
module Callbacks
extend ActiveSupport::Concern
include ActiveSupport::Callbacks
included do
define_callbacks :perform
define_callbacks :enqueue
end
module ClassMethods
def before_perform(*filters, &blk)
set_callback(:perform, :before, *filters, &blk)
end
def after_perform(*filters, &blk)
set_callback(:perform, :after, *filters, &blk)
end
def around_perform(*filters, &blk)
set_callback(:perform, :around, *filters, &blk)
end
def before_enqueue(*filters, &blk)
set_callback(:enqueue, :before, *filters, &blk)
end
def after_enqueue(*filters, &blk)
set_callback(:enqueue, :after, *filters, &blk)
end
def around_enqueue(*filters, &blk)
set_callback(:enqueue, :around, *filters, &blk)
end
end
end
end

View file

@ -0,0 +1,71 @@
require 'active_job/arguments'
module ActiveJob
module Enqueuing
extend ActiveSupport::Concern
module ClassMethods
# Push a job onto the queue. The arguments must be legal JSON types
# (string, int, float, nil, true, false, hash or array) or
# ActiveModel::GlobalIdentication instances. Arbitrary Ruby objects
# are not supported.
#
# Returns an instance of the job class queued with args available in
# Job#arguments.
def enqueue(*args)
new(args).tap do |job|
job.run_callbacks :enqueue do
queue_adapter.enqueue self, job.job_id, *Arguments.serialize(args)
end
end
end
# Enqueue a job to be performed at +interval+ from now.
#
# enqueue_in(1.week, "mike")
#
# Returns an instance of the job class queued with args available in
# Job#arguments and the timestamp in Job#enqueue_at.
def enqueue_in(interval, *args)
enqueue_at interval.seconds.from_now, *args
end
# Enqueue a job to be performed at an explicit point in time.
#
# enqueue_at(Date.tomorrow.midnight, "mike")
#
# Returns an instance of the job class queued with args available in
# Job#arguments and the timestamp in Job#enqueue_at.
def enqueue_at(timestamp, *args)
new(args).tap do |job|
job.enqueued_at = timestamp
job.run_callbacks :enqueue do
queue_adapter.enqueue_at self, timestamp.to_f, job.job_id, *Arguments.serialize(args)
end
end
end
end
included do
attr_accessor :arguments
attr_accessor :enqueued_at
end
def initialize(arguments = nil)
@arguments = arguments
end
def retry_now
self.class.enqueue *arguments
end
def retry_in(interval)
self.class.enqueue_in interval, *arguments
end
def retry_at(timestamp)
self.class.enqueue_at timestamp, *arguments
end
end
end

View file

@ -0,0 +1,27 @@
require 'active_support/rescuable'
require 'active_job/arguments'
module ActiveJob
module Execution
extend ActiveSupport::Concern
included do
include ActiveSupport::Rescuable
end
def execute(job_id, *serialized_args)
self.job_id = job_id
self.arguments = Arguments.deserialize(serialized_args)
run_callbacks :perform do
perform *arguments
end
rescue => exception
rescue_with_handler(exception) || raise(exception)
end
def perform(*)
raise NotImplementedError
end
end
end

View file

@ -0,0 +1,15 @@
module ActiveJob
# Returns the version of the currently loaded ActiveJob as a <tt>Gem::Version</tt>
def self.gem_version
Gem::Version.new VERSION::STRING
end
module VERSION
MAJOR = 4
MINOR = 2
TINY = 0
PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
end

View file

@ -0,0 +1,15 @@
require 'active_job/arguments'
module ActiveJob
module Identifier
extend ActiveSupport::Concern
included do
attr_writer :job_id
end
def job_id
@job_id ||= SecureRandom.uuid
end
end
end

View file

@ -0,0 +1,88 @@
require 'active_support/core_ext/string/filters'
module ActiveJob
module Logging
extend ActiveSupport::Concern
included do
cattr_accessor(:logger) { ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT)) }
around_enqueue do |job, block, _|
tag_logger do
block.call
end
end
around_perform do |job, block, _|
tag_logger(job.class.name, job.job_id) do
payload = {adapter: job.class.queue_adapter, job: job.class, args: job.arguments}
ActiveSupport::Notifications.instrument("perform_start.active_job", payload.dup)
ActiveSupport::Notifications.instrument("perform.active_job", payload) do |payload|
block.call
end
end
end
before_enqueue do |job|
if job.enqueued_at
ActiveSupport::Notifications.instrument "enqueue_at.active_job",
adapter: job.class.queue_adapter, job: job.class, job_id: job.job_id, args: job.arguments, timestamp: job.enqueued_at
else
ActiveSupport::Notifications.instrument "enqueue.active_job",
adapter: job.class.queue_adapter, job: job.class, job_id: job.job_id, args: job.arguments
end
end
end
private
def tag_logger(*tags)
if logger.respond_to?(:tagged)
tags.unshift "ActiveJob" unless logger_tagged_by_active_job?
ActiveJob::Base.logger.tagged(*tags){ yield }
else
yield
end
end
def logger_tagged_by_active_job?
logger.formatter.current_tags.include?("ActiveJob")
end
class LogSubscriber < ActiveSupport::LogSubscriber
def enqueue(event)
info "Enqueued #{event.payload[:job].name} (Job ID: #{event.payload[:job_id]}) to #{queue_name(event)}" + args_info(event)
end
def enqueue_at(event)
info "Enqueued #{event.payload[:job].name} (Job ID: #{event.payload[:job_id]}) to #{queue_name(event)} at #{enqueued_at(event)}" + args_info(event)
end
def perform_start(event)
info "Performing #{event.payload[:job].name} from #{queue_name(event)}" + args_info(event)
end
def perform(event)
info "Performed #{event.payload[:job].name} from #{queue_name(event)} in #{event.duration.round(2).to_s}ms"
end
private
def queue_name(event)
event.payload[:adapter].name.demodulize.remove('Adapter') + "(#{event.payload[:job].queue_name})"
end
def args_info(event)
event.payload[:args].any? ? " with arguments: #{event.payload[:args].map(&:inspect).join(", ")}" : ""
end
def enqueued_at(event)
Time.at(event.payload[:timestamp]).utc
end
def logger
ActiveJob::Base.logger
end
end
end
end
ActiveJob::Logging::LogSubscriber.attach_to :active_job

View file

@ -0,0 +1,24 @@
require 'active_job/queue_adapters/inline_adapter'
require 'active_support/core_ext/string/inflections'
module ActiveJob
module QueueAdapter
mattr_reader(:queue_adapter) { ActiveJob::QueueAdapters::InlineAdapter }
def queue_adapter=(name_or_adapter)
@@queue_adapter = \
case name_or_adapter
when Symbol, String
load_adapter(name_or_adapter)
when Class
name_or_adapter
end
end
private
def load_adapter(name)
require "active_job/queue_adapters/#{name}_adapter"
"ActiveJob::QueueAdapters::#{name.to_s.camelize}Adapter".constantize
end
end
end

View file

@ -0,0 +1,25 @@
require 'backburner'
module ActiveJob
module QueueAdapters
class BackburnerAdapter
class << self
def enqueue(job, *args)
Backburner::Worker.enqueue JobWrapper, [ job.name, *args ], queue: job.queue_name
end
def enqueue_at(job, timestamp, *args)
raise NotImplementedError
end
end
class JobWrapper
class << self
def perform(job_name, *args)
job_name.constantize.new.execute *args
end
end
end
end
end
end

View file

@ -0,0 +1,23 @@
require 'delayed_job'
module ActiveJob
module QueueAdapters
class DelayedJobAdapter
class << self
def enqueue(job, *args)
JobWrapper.new.delay(queue: job.queue_name).perform(job, *args)
end
def enqueue_at(job, timestamp, *args)
JobWrapper.new.delay(queue: job.queue_name, run_at: Time.at(timestamp)).perform(job, *args)
end
end
class JobWrapper
def perform(job, *args)
job.new.execute *args
end
end
end
end
end

View file

@ -0,0 +1,15 @@
module ActiveJob
module QueueAdapters
class InlineAdapter
class << self
def enqueue(job, *args)
job.new.execute *args
end
def enqueue_at(*)
raise NotImplementedError.new("Use a queueing backend to enqueue jobs in the future. Read more at https://github.com/rails/activejob")
end
end
end
end
end

View file

@ -0,0 +1,30 @@
require 'qu'
module ActiveJob
module QueueAdapters
class QuAdapter
class << self
def enqueue(job, *args)
Qu::Payload.new(klass: JobWrapper, args: [job.name, *args]).tap do |payload|
payload.instance_variable_set(:@queue, job.queue_name)
end.push
end
def enqueue_at(job, timestamp, *args)
raise NotImplementedError
end
end
class JobWrapper < Qu::Job
def initialize(job_name, *args)
@job = job_name.constantize
@args = args
end
def perform
@job.new.execute *@args
end
end
end
end
end

View file

@ -0,0 +1,23 @@
require 'que'
module ActiveJob
module QueueAdapters
class QueAdapter
class << self
def enqueue(job, *args)
JobWrapper.enqueue job.name, *args, queue: job.queue_name
end
def enqueue_at(job, timestamp, *args)
raise NotImplementedError
end
end
class JobWrapper < Que::Job
def run(job_name, *args)
job_name.constantize.new.execute *args
end
end
end
end
end

View file

@ -0,0 +1,23 @@
require 'queue_classic'
module ActiveJob
module QueueAdapters
class QueueClassicAdapter
class << self
def enqueue(job, *args)
QC::Queue.new(job.queue_name).enqueue("#{JobWrapper.name}.perform", job.name, *args)
end
def enqueue_at(job, timestamp, *args)
raise NotImplementedError
end
end
class JobWrapper
def self.perform(job_name, *args)
job_name.constantize.new.execute *args
end
end
end
end
end

View file

@ -0,0 +1,38 @@
require 'resque'
require 'active_support/core_ext/enumerable'
require 'active_support/core_ext/array/access'
begin
require 'resque-scheduler'
rescue LoadError
begin
require 'resque_scheduler'
rescue LoadError
$stderr.puts 'The ActiveJob resque adapter requires resque-scheduler. Please add it to your Gemfile and run bundle install'
raise e
end
end
module ActiveJob
module QueueAdapters
class ResqueAdapter
class << self
def enqueue(job, *args)
Resque.enqueue_to job.queue_name, JobWrapper, job.name, *args
end
def enqueue_at(job, timestamp, *args)
Resque.enqueue_at_with_queue job.queue_name, timestamp, JobWrapper, job.name, *args
end
end
class JobWrapper
class << self
def perform(job_name, *args)
job_name.constantize.new.execute *args
end
end
end
end
end
end

View file

@ -0,0 +1,35 @@
require 'sidekiq'
module ActiveJob
module QueueAdapters
class SidekiqAdapter
class << self
def enqueue(job, *args)
#Sidekiq::Client does not support symbols as keys
Sidekiq::Client.push \
'class' => JobWrapper,
'queue' => job.queue_name,
'args' => [ job, *args ],
'retry' => true
end
def enqueue_at(job, timestamp, *args)
Sidekiq::Client.push \
'class' => JobWrapper,
'queue' => job.queue_name,
'args' => [ job, *args ],
'retry' => true,
'at' => timestamp
end
end
class JobWrapper
include Sidekiq::Worker
def perform(job_name, *args)
job_name.constantize.new.execute *args
end
end
end
end
end

View file

@ -0,0 +1,34 @@
require 'sneakers'
require 'thread'
module ActiveJob
module QueueAdapters
class SneakersAdapter
@monitor = Monitor.new
class << self
def enqueue(job, *args)
@monitor.synchronize do
JobWrapper.from_queue job.queue_name
JobWrapper.enqueue ActiveSupport::JSON.encode([ job.name, *args ])
end
end
def enqueue_at(job, timestamp, *args)
raise NotImplementedError
end
end
class JobWrapper
include Sneakers::Worker
from_queue 'active_jobs_default'
def work(msg)
job_name, *args = ActiveSupport::JSON.decode(msg)
job_name.constantize.new.execute *args
ack!
end
end
end
end
end

View file

@ -0,0 +1,25 @@
require 'sucker_punch'
module ActiveJob
module QueueAdapters
class SuckerPunchAdapter
class << self
def enqueue(job, *args)
JobWrapper.new.async.perform job, *args
end
def enqueue_at(job, timestamp, *args)
raise NotImplementedError
end
end
class JobWrapper
include SuckerPunch::Job
def perform(job, *args)
job.new.execute *args
end
end
end
end
end

View file

@ -0,0 +1,18 @@
module ActiveJob
module QueueName
extend ActiveSupport::Concern
module ClassMethods
mattr_accessor(:queue_base_name) { "active_jobs" }
def queue_as(part_name)
self.queue_name = "#{queue_base_name}_#{part_name}"
end
end
included do
class_attribute :queue_name
self.queue_name = queue_base_name
end
end
end

View file

@ -0,0 +1,10 @@
require 'active_model/railtie'
module ActiveJob
# = Active Job Railtie
class Railtie < Rails::Railtie # :nodoc:
initializer 'active_job' do
ActiveSupport.on_load(:active_job) { self.logger = ::Rails.logger }
end
end
end

View file

@ -0,0 +1,8 @@
require_relative 'gem_version'
module ActiveJob
# Returns the version of the currently loaded ActiveJob as a <tt>Gem::Version</tt>
def self.version
gem_version
end
end

View file

@ -0,0 +1 @@
require 'active_job'

View file

@ -0,0 +1,22 @@
require 'rails/generators/named_base'
module ActiveJob
module Generators # :nodoc:
class JobGenerator < Rails::Generators::NamedBase # :nodoc:
desc 'This generator creates an active job file at app/jobs'
class_option :queue, type: :string, default: 'default', desc: 'The queue name for the generated job'
def self.default_generator_root
File.dirname(__FILE__)
end
check_class_collision suffix: 'Job'
def create_job_file
template 'job.rb', File.join('app/jobs', class_path, "#{file_name}_job.rb")
end
end
end
end

View file

@ -0,0 +1,9 @@
<% module_namespacing do -%>
class <%= class_name %>Job < ActiveJob::Base
queue_as :<%= options[:queue] %>
def perform
# Do something later
end
end
<% end -%>

View file

@ -0,0 +1,3 @@
require 'support/backburner/inline'
ActiveJob::Base.queue_adapter = :backburner

View file

@ -0,0 +1,7 @@
ActiveJob::Base.queue_adapter = :delayed_job
$LOAD_PATH << File.dirname(__FILE__) + "/../support/delayed_job"
Delayed::Worker.delay_jobs = false
Delayed::Worker.backend = :test

View file

@ -0,0 +1 @@
ActiveJob::Base.queue_adapter = :inline

View file

@ -0,0 +1,3 @@
require 'qu-immediate'
ActiveJob::Base.queue_adapter = :qu

View file

@ -0,0 +1,2 @@
ActiveJob::Base.queue_adapter = :que
Que.mode = :sync

View file

@ -0,0 +1,2 @@
require 'support/queue_classic/inline'
ActiveJob::Base.queue_adapter = :queue_classic

View file

@ -0,0 +1,2 @@
ActiveJob::Base.queue_adapter = :resque
Resque.inline = true

View file

@ -0,0 +1,2 @@
require 'sidekiq/testing/inline'
ActiveJob::Base.queue_adapter = :sidekiq

View file

@ -0,0 +1,2 @@
require 'support/sneakers/inline'
ActiveJob::Base.queue_adapter = :sneakers

View file

@ -0,0 +1,2 @@
require 'sucker_punch/testing/inline'
ActiveJob::Base.queue_adapter = :sucker_punch

View file

@ -0,0 +1,56 @@
require 'helper'
class AdapterTest < ActiveSupport::TestCase
setup { @old_adapter = ActiveJob::Base.queue_adapter }
teardown { ActiveJob::Base.queue_adapter = @old_adapter }
test 'should load inline adapter' do
ActiveJob::Base.queue_adapter = :inline
assert_equal ActiveJob::QueueAdapters::InlineAdapter, ActiveJob::Base.queue_adapter
end
test 'should load Delayed Job adapter' do
ActiveJob::Base.queue_adapter = :delayed_job
assert_equal ActiveJob::QueueAdapters::DelayedJobAdapter, ActiveJob::Base.queue_adapter
end
test 'should load Qu adapter' do
ActiveJob::Base.queue_adapter = :qu
assert_equal ActiveJob::QueueAdapters::QuAdapter, ActiveJob::Base.queue_adapter
end
test 'should load Que adapter' do
ActiveJob::Base.queue_adapter = :que
assert_equal ActiveJob::QueueAdapters::QueAdapter, ActiveJob::Base.queue_adapter
end
test 'should load Queue Classic adapter' do
ActiveJob::Base.queue_adapter = :queue_classic
assert_equal ActiveJob::QueueAdapters::QueueClassicAdapter, ActiveJob::Base.queue_adapter
end
test 'should load Resque adapter' do
ActiveJob::Base.queue_adapter = :resque
assert_equal ActiveJob::QueueAdapters::ResqueAdapter, ActiveJob::Base.queue_adapter
end
test 'should load Sidekiq adapter' do
ActiveJob::Base.queue_adapter = :sidekiq
assert_equal ActiveJob::QueueAdapters::SidekiqAdapter, ActiveJob::Base.queue_adapter
end
test 'should load Sucker Punch adapter' do
ActiveJob::Base.queue_adapter = :sucker_punch
assert_equal ActiveJob::QueueAdapters::SuckerPunchAdapter, ActiveJob::Base.queue_adapter
end
test 'should load Sneakers adapter' do
ActiveJob::Base.queue_adapter = :sneakers
assert_equal ActiveJob::QueueAdapters::SneakersAdapter, ActiveJob::Base.queue_adapter
end
test 'should load Backburner adapter' do
ActiveJob::Base.queue_adapter = :backburner
assert_equal ActiveJob::QueueAdapters::BackburnerAdapter, ActiveJob::Base.queue_adapter
end
end

View file

@ -0,0 +1,22 @@
require 'helper'
require 'jobs/callback_job'
require 'active_support/core_ext/object/inclusion'
class CallbacksTest < ActiveSupport::TestCase
test 'perform callbacks' do
performed_callback_job = CallbackJob.new.tap { |j| j.execute("A-JOB-ID") }
assert "CallbackJob ran before_perform".in? performed_callback_job.history
assert "CallbackJob ran after_perform".in? performed_callback_job.history
assert "CallbackJob ran around_perform_start".in? performed_callback_job.history
assert "CallbackJob ran around_perform_stop".in? performed_callback_job.history
end
test 'enqueue callbacks' do
enqueued_callback_job = CallbackJob.enqueue
assert "CallbackJob ran before_enqueue".in? enqueued_callback_job.history
assert "CallbackJob ran after_enqueue".in? enqueued_callback_job.history
assert "CallbackJob ran around_enqueue_start".in? enqueued_callback_job.history
assert "CallbackJob ran around_enqueue_stop".in? enqueued_callback_job.history
end
end

View file

@ -0,0 +1,15 @@
require 'helper'
require 'jobs/gid_job'
require 'models/person'
class JobSerializationTest < ActiveSupport::TestCase
setup do
$BUFFER = []
@person = Person.find(5)
end
test 'serialize job with gid' do
GidJob.enqueue @person
assert_equal "Person with ID: 5", $BUFFER.pop
end
end

View file

@ -0,0 +1,94 @@
require 'helper'
require "active_support/log_subscriber/test_helper"
require 'jobs/logging_job'
require 'jobs/nested_job'
class AdapterTest < ActiveSupport::TestCase
include ActiveSupport::LogSubscriber::TestHelper
include ActiveSupport::Logger::Severity
class TestLogger < ActiveSupport::Logger
def initialize
@file = StringIO.new
super(@file)
end
def messages
@file.rewind
@file.read
end
end
def setup
super
$BUFFER = []
@old_logger = ActiveJob::Base.logger
@logger = ActiveSupport::TaggedLogging.new(TestLogger.new)
set_logger @logger
ActiveJob::Logging::LogSubscriber.attach_to :active_job
end
def teardown
super
ActiveJob::Logging::LogSubscriber.log_subscribers.pop
ActiveJob::Base.logger = @old_logger
end
def set_logger(logger)
ActiveJob::Base.logger = logger
end
def test_uses_active_job_as_tag
HelloJob.enqueue "Cristian"
assert_match(/\[ActiveJob\]/, @logger.messages)
end
def test_enqueue_job_logging
HelloJob.enqueue "Cristian"
assert_match(/Enqueued HelloJob \(Job ID: .*?\) to .*?:.*Cristian/, @logger.messages)
end
def test_perform_job_logging
LoggingJob.enqueue "Dummy"
assert_match(/Performing LoggingJob from .*? with arguments:.*Dummy/, @logger.messages)
assert_match(/Dummy, here is it: Dummy/, @logger.messages)
assert_match(/Performed LoggingJob from .*? in .*ms/, @logger.messages)
end
def test_perform_uses_job_name_job_logging
LoggingJob.enqueue "Dummy"
assert_match(/\[LoggingJob\]/, @logger.messages)
end
def test_perform_uses_job_id_job_logging
LoggingJob.enqueue "Dummy"
assert_match(/\[LOGGING-JOB-ID\]/, @logger.messages)
end
def test_perform_nested_jobs_logging
NestedJob.enqueue
assert_match(/\[LoggingJob\] \[.*?\]/, @logger.messages)
assert_match(/\[ActiveJob\] Enqueued NestedJob \(Job ID: .*\) to/, @logger.messages)
assert_match(/\[ActiveJob\] \[NestedJob\] \[NESTED-JOB-ID\] Performing NestedJob from/, @logger.messages)
assert_match(/\[ActiveJob\] \[NestedJob\] \[NESTED-JOB-ID\] Enqueued LoggingJob \(Job ID: .*?\) to .* with arguments: "NestedJob"/, @logger.messages)
assert_match(/\[ActiveJob\].*\[LoggingJob\] \[LOGGING-JOB-ID\] Performing LoggingJob from .* with arguments: "NestedJob"/, @logger.messages)
assert_match(/\[ActiveJob\].*\[LoggingJob\] \[LOGGING-JOB-ID\] Dummy, here is it: NestedJob/, @logger.messages)
assert_match(/\[ActiveJob\].*\[LoggingJob\] \[LOGGING-JOB-ID\] Performed LoggingJob from .* in/, @logger.messages)
assert_match(/\[ActiveJob\] \[NestedJob\] \[NESTED-JOB-ID\] Performed NestedJob from .* in/, @logger.messages)
end
def test_enqueue_at_job_logging
HelloJob.enqueue_at 1, "Cristian"
assert_match(/Enqueued HelloJob \(Job ID: .*\) to .*? at.*Cristian/, @logger.messages)
rescue NotImplementedError
skip
end
def test_enqueue_in_job_logging
HelloJob.enqueue_in 2, "Cristian"
assert_match(/Enqueued HelloJob \(Job ID: .*\) to .*? at.*Cristian/, @logger.messages)
rescue NotImplementedError
skip
end
end

View file

@ -0,0 +1,77 @@
require 'helper'
require 'active_job/arguments'
require 'models/person'
require 'active_support/core_ext/hash/indifferent_access'
class ParameterSerializationTest < ActiveSupport::TestCase
test 'should make no change to regular values' do
assert_equal [ 1, "something" ], ActiveJob::Arguments.serialize([ 1, "something" ])
end
test 'should not allow complex objects' do
assert_equal [ nil ], ActiveJob::Arguments.serialize([ nil ])
assert_equal [ 1 ], ActiveJob::Arguments.serialize([ 1 ])
assert_equal [ 1.0 ], ActiveJob::Arguments.serialize([ 1.0 ])
assert_equal [ 'a' ], ActiveJob::Arguments.serialize([ 'a' ])
assert_equal [ true ], ActiveJob::Arguments.serialize([ true ])
assert_equal [ false ], ActiveJob::Arguments.serialize([ false ])
assert_equal [ { "a" => 1, "b" => 2 } ], ActiveJob::Arguments.serialize([ { a: 1, "b" => 2 } ])
assert_equal [ [ 1 ] ], ActiveJob::Arguments.serialize([ [ 1 ] ])
assert_equal [ 1_000_000_000_000_000_000_000 ], ActiveJob::Arguments.serialize([ 1_000_000_000_000_000_000_000 ])
err = assert_raises RuntimeError do
ActiveJob::Arguments.serialize([ 1, self ])
end
assert_equal "Unsupported argument type: #{self.class.name}", err.message
end
test 'should dive deep into arrays or hashes' do
assert_equal [ { "a" => Person.find(5).gid.to_s }.with_indifferent_access ], ActiveJob::Arguments.serialize([ { a: Person.find(5) } ])
assert_equal [ [ Person.find(5).gid.to_s ] ], ActiveJob::Arguments.serialize([ [ Person.find(5) ] ])
end
test 'should dive deep into arrays or hashes and raise exception on complex objects' do
err = assert_raises RuntimeError do
ActiveJob::Arguments.serialize([ 1, [self] ])
end
assert_equal "Unsupported argument type: #{self.class.name}", err.message
end
test 'shoud dive deep into hashes and allow raise exception on not string/symbol keys' do
err = assert_raises RuntimeError do
ActiveJob::Arguments.serialize([ [ { 1 => 2 } ] ])
end
assert_equal "Unsupported hash key type: Fixnum", err.message
end
test 'should serialize records with global id' do
assert_equal [ Person.find(5).gid.to_s ], ActiveJob::Arguments.serialize([ Person.find(5) ])
end
test 'should serialize values and records together' do
assert_equal [ 3, Person.find(5).gid.to_s ], ActiveJob::Arguments.serialize([ 3, Person.find(5) ])
end
end
class ParameterDeserializationTest < ActiveSupport::TestCase
test 'should make no change to regular values' do
assert_equal [ 1, "something" ], ActiveJob::Arguments.deserialize([ 1, "something" ])
end
test 'should deserialize records with global id' do
assert_equal [ Person.find(5) ], ActiveJob::Arguments.deserialize([ Person.find(5).gid ])
end
test 'should serialize values and records together' do
assert_equal [ 3, Person.find(5) ], ActiveJob::Arguments.deserialize([ 3, Person.find(5).gid ])
end
test 'should dive deep when deserialising arrays' do
assert_equal [ [ 3, Person.find(5) ] ], ActiveJob::Arguments.deserialize([ [ 3, Person.find(5).gid ] ])
end
test 'should dive deep when deserialising hashes' do
assert_equal [ { "5" => Person.find(5) } ], ActiveJob::Arguments.deserialize([ { "5" => Person.find(5).gid } ])
end
end

View file

@ -0,0 +1,21 @@
require 'helper'
require 'jobs/hello_job'
class QueueNamingTest < ActiveSupport::TestCase
test 'name derived from base' do
assert_equal "active_jobs", HelloJob.queue_name
end
test 'name appended in job' do
begin
HelloJob.queue_as :greetings
LoggingJob.queue_as :bookkeeping
assert_equal "active_jobs", NestedJob.queue_name
assert_equal "active_jobs_greetings", HelloJob.queue_name
assert_equal "active_jobs_bookkeeping", LoggingJob.queue_name
ensure
HelloJob.queue_name = LoggingJob.queue_name = ActiveJob::Base.queue_base_name
end
end
end

View file

@ -0,0 +1,44 @@
require 'helper'
require 'jobs/hello_job'
require 'active_support/core_ext/numeric/time'
class QueuingTest < ActiveSupport::TestCase
setup do
$BUFFER = []
end
test 'run queued job' do
HelloJob.enqueue
assert_equal "David says hello", $BUFFER.pop
end
test 'run queued job with arguments' do
HelloJob.enqueue "Jamie"
assert_equal "Jamie says hello", $BUFFER.pop
end
test 'run queued job later' do
begin
result = HelloJob.enqueue_at 1.second.ago, "Jamie"
assert result
rescue NotImplementedError
skip
end
end
test 'job returned by enqueue has the arguments available' do
job = HelloJob.enqueue "Jamie"
assert_equal [ "Jamie" ], job.arguments
end
test 'job returned by enqueue_at has the timestamp available' do
begin
job = HelloJob.enqueue_at Time.utc(2014, 1, 1)
assert_equal Time.utc(2014, 1, 1), job.enqueued_at
rescue NotImplementedError
skip
end
end
end

View file

@ -0,0 +1,23 @@
require 'helper'
require 'jobs/rescue_job'
require 'active_support/core_ext/object/inclusion'
class RescueTest < ActiveSupport::TestCase
setup do
$BUFFER = []
end
test 'rescue perform exception with retry' do
job = RescueJob.new
job.execute(SecureRandom.uuid, "david")
assert_equal [ "rescued from ArgumentError", "performed beautifully" ], $BUFFER
end
test 'let through unhandled perform exception' do
job = RescueJob.new
assert_raises(RescueJob::OtherError) do
job.execute(SecureRandom.uuid, "other")
end
end
end

View file

@ -0,0 +1,3 @@
require 'sneakers/tasks'
require File.expand_path('../config/application', __FILE__)
Rails.application.load_tasks

View file

@ -0,0 +1,3 @@
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
end

View file

@ -0,0 +1,2 @@
module ApplicationHelper
end

View file

@ -0,0 +1,9 @@
class TestJob < ActiveJob::Base
queue_as :default
def perform(x)
File.open(Rails.root.join("tmp/#{x}"), "w+") do |f|
f.write x
end
end
end

View file

View file

View file

@ -0,0 +1,4 @@
# This file is used by Rack-based servers to start the application.
require ::File.expand_path('../config/environment', __FILE__)
run Rails.application

View file

@ -0,0 +1,9 @@
require File.expand_path('../boot', __FILE__)
require 'rails/all'
Bundler.require(*Rails.groups)
module Dummy
class Application < Rails::Application
end
end

View file

@ -0,0 +1,5 @@
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__)
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
$LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__)

View file

@ -0,0 +1,3 @@
test:
adapter: sqlite3
database: "db/test.sqlite3"

View file

@ -0,0 +1,2 @@
require File.expand_path('../application', __FILE__)
Rails.application.initialize!

View file

@ -0,0 +1,13 @@
Rails.application.configure do
config.cache_classes = true
config.eager_load = false
config.serve_static_assets = true
config.static_cache_control = 'public, max-age=3600'
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
config.action_dispatch.show_exceptions = false
config.action_controller.allow_forgery_protection = false
config.action_mailer.delivery_method = :test
config.active_support.deprecation = :stderr
config.log_level = :debug
end

View file

@ -0,0 +1,65 @@
case ENV['AJADAPTER']
when "delayed_job"
ActiveJob::Base.queue_adapter = :delayed_job
when "sidekiq"
ActiveJob::Base.queue_adapter = :sidekiq
when "resque"
ActiveJob::Base.queue_adapter = :resque
Resque.redis = Redis::Namespace.new 'active_jobs_int_test', redis: Redis.connect(url: "tcp://127.0.0.1:6379/12", :thread_safe => true)
Resque.logger = Rails.logger
when 'qu'
ActiveJob::Base.queue_adapter = :qu
ENV['REDISTOGO_URL'] = "tcp://127.0.0.1:6379/12"
backend = Qu::Backend::Redis.new
backend.namespace = "active_jobs_int_test"
Qu.backend = backend
Qu.logger = Rails.logger
Qu.interval = 0.5
when 'que'
ActiveJob::Base.queue_adapter = :que
QUE_URL = ENV['QUE_DATABASE_URL'] || 'postgres://localhost/active_jobs_que_int_test'
uri = URI.parse(QUE_URL)
user = uri.user||ENV['USER']
pass = uri.password
db = uri.path[1..-1]
%x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'drop database "#{db}"' -U #{user} -t template1}
%x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'create database "#{db}"' -U #{user} -t template1}
Que.connection = Sequel.connect(QUE_URL)
Que.migrate!
Que.mode = :off
Que.worker_count = 1
when 'queue_classic'
ENV['QC_DATABASE_URL'] ||= 'postgres://localhost/active_jobs_qc_int_test'
ENV['QC_LISTEN_TIME'] = "0.5"
ActiveJob::Base.queue_adapter = :queue_classic
uri = URI.parse(ENV['QC_DATABASE_URL'])
user = uri.user||ENV['USER']
pass = uri.password
db = uri.path[1..-1]
%x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'drop database "#{db}"' -U #{user} -t template1}
%x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'create database "#{db}"' -U #{user} -t template1}
QC::Setup.create
when 'sidekiq'
ActiveJob::Base.queue_adapter = :sidekiq
when 'sneakers'
ActiveJob::Base.queue_adapter = :sneakers
Sneakers.configure :heartbeat => 2,
:amqp => 'amqp://guest:guest@localhost:5672',
:vhost => '/',
:exchange => 'active_jobs_sneakers_int_test',
:exchange_type => :direct,
:daemonize => true,
:threads => 1,
:workers => 1,
:pid_path => Rails.root.join("tmp/sneakers.pid").to_s,
:log => Rails.root.join("log/sneakers.log").to_s
when 'sucker_punch'
ActiveJob::Base.queue_adapter = :sucker_punch
when 'backburner'
ActiveJob::Base.queue_adapter = :backburner
Backburner.configure do |config|
config.logger = Rails.logger
end
else
ActiveJob::Base.queue_adapter = nil
end

View file

@ -0,0 +1 @@
Rails.application.config.session_store :cookie_store, key: '_dummy_session'

View file

@ -0,0 +1,2 @@
Rails.application.routes.draw do
end

View file

@ -0,0 +1,2 @@
test:
secret_key_base: b83ee5aeada663bc4270a1817d0ca43b2784017cc77dc8afcd60967cc968d4ce30caff9eb682766129e18a4048c4d5ebf14eabf463fc37ad67c18934f4345545

View file

@ -0,0 +1,22 @@
class CreateDelayedJobs < ActiveRecord::Migration
def self.up
create_table :delayed_jobs, :force => true do |table|
table.integer :priority, :default => 0, :null => false # Allows some jobs to jump to the front of the queue
table.integer :attempts, :default => 0, :null => false # Provides for retries, but still fail eventually.
table.text :handler, :null => false # YAML-encoded string of the object that will do work
table.text :last_error # reason for last failure (See Note below)
table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future.
table.datetime :locked_at # Set when a client is working on this object
table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
table.string :locked_by # Who is working on this object (if locked)
table.string :queue # The name of the queue this job is in
table.timestamps
end
add_index :delayed_jobs, [:priority, :run_at], :name => 'delayed_jobs_priority'
end
def self.down
drop_table :delayed_jobs
end
end

View file

@ -0,0 +1,32 @@
# encoding: UTF-8
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using db:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20140804200445) do
create_table "delayed_jobs", force: true do |t|
t.integer "priority", default: 0, null: false
t.integer "attempts", default: 0, null: false
t.text "handler", null: false
t.text "last_error"
t.datetime "run_at"
t.datetime "locked_at"
t.datetime "failed_at"
t.string "locked_by"
t.string "queue"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "delayed_jobs", ["priority", "run_at"], name: "delayed_jobs_priority"
end

Binary file not shown.

View file

View file

View file

19
activejob/test/helper.rb Normal file
View file

@ -0,0 +1,19 @@
require 'bundler'
Bundler.setup
$LOAD_PATH << File.dirname(__FILE__) + "/../lib"
require 'active_job'
adapter = ENV['AJADAPTER'] || 'inline'
puts "Testing#{" integration" if ENV['AJ_INTEGRATION_TESTS']} using #{adapter}"
if ENV['AJ_INTEGRATION_TESTS']
require 'support/integration/helper'
else
require "adapters/#{adapter}"
end
require 'active_support/testing/autorun'
ActiveJob::Base.logger.level = Logger::DEBUG

View file

@ -0,0 +1,18 @@
require 'helper'
require 'jobs/logging_job'
require 'active_support/core_ext/numeric/time'
class QueuingTest < ActiveSupport::TestCase
setup do
end
test 'run queued job' do
id = "AJ-#{SecureRandom.uuid}"
TestJob.enqueue id
sleep 2
assert Dummy::Application.root.join("tmp/#{id}").exist?
end
end

View file

@ -0,0 +1,32 @@
class CallbackJob < ActiveJob::Base
before_perform ->(job) { job.history << "CallbackJob ran before_perform" }
after_perform ->(job) { job.history << "CallbackJob ran after_perform" }
before_enqueue ->(job) { job.history << "CallbackJob ran before_enqueue" }
after_enqueue ->(job) { job.history << "CallbackJob ran after_enqueue" }
around_perform :around_perform
around_enqueue :around_enqueue
def perform(person = "david")
# NOTHING!
end
def history
@history ||= []
end
# FIXME: Not sure why these can't be declared inline like before/after
def around_perform
history << "CallbackJob ran around_perform_start"
yield
history << "CallbackJob ran around_perform_stop"
end
def around_enqueue
history << "CallbackJob ran around_enqueue_start"
yield
history << "CallbackJob ran around_enqueue_stop"
end
end

View file

@ -0,0 +1,6 @@
class GidJob < ActiveJob::Base
def perform(person)
$BUFFER << "Person with ID: #{person.id}"
end
end

View file

@ -0,0 +1,5 @@
class HelloJob < ActiveJob::Base
def perform(greeter = "David")
$BUFFER << "#{greeter} says hello"
end
end

View file

@ -0,0 +1,10 @@
class LoggingJob < ActiveJob::Base
def perform(dummy)
logger.info "Dummy, here is it: #{dummy}"
end
def job_id
"LOGGING-JOB-ID"
end
end

View file

@ -0,0 +1,10 @@
class NestedJob < ActiveJob::Base
def perform
LoggingJob.enqueue "NestedJob"
end
def job_id
"NESTED-JOB-ID"
end
end

View file

@ -0,0 +1,20 @@
class RescueJob < ActiveJob::Base
class OtherError < StandardError; end
rescue_from(ArgumentError) do
$BUFFER << "rescued from ArgumentError"
arguments[0] = "DIFFERENT!"
retry_now
end
def perform(person = "david")
case person
when "david"
raise ArgumentError, "Hair too good"
when "other"
raise OtherError
else
$BUFFER << "performed beautifully"
end
end
end

View file

@ -0,0 +1,19 @@
require 'active_model/global_identification'
class Person
include ActiveModel::GlobalIdentification
attr_reader :id
def self.find(id)
new(id)
end
def initialize(id)
@id = id
end
def ==(other_person)
other_person.is_a?(Person) && id.to_s == other_person.id.to_s
end
end

View file

@ -0,0 +1,8 @@
require 'backburner'
Backburner::Worker.class_eval do
class << self; alias_method :original_enqueue, :enqueue; end
def self.enqueue(job_class, args=[], opts={})
job_class.perform(*args)
end
end

View file

@ -0,0 +1,113 @@
#copied from https://github.com/collectiveidea/delayed_job/blob/master/spec/delayed/backend/test.rb
require 'ostruct'
# An in-memory backend suitable only for testing. Tries to behave as if it were an ORM.
module Delayed
module Backend
module Test
class Job
attr_accessor :id
attr_accessor :priority
attr_accessor :attempts
attr_accessor :handler
attr_accessor :last_error
attr_accessor :run_at
attr_accessor :locked_at
attr_accessor :locked_by
attr_accessor :failed_at
attr_accessor :queue
include Delayed::Backend::Base
cattr_accessor :id
self.id = 0
def initialize(hash = {})
self.attempts = 0
self.priority = 0
self.id = (self.class.id += 1)
hash.each{|k,v| send(:"#{k}=", v)}
end
@jobs = []
def self.all
@jobs
end
def self.count
all.size
end
def self.delete_all
all.clear
end
def self.create(attrs = {})
new(attrs).tap do |o|
o.save
end
end
def self.create!(*args); create(*args); end
def self.clear_locks!(worker_name)
all.select{|j| j.locked_by == worker_name}.each {|j| j.locked_by = nil; j.locked_at = nil}
end
# Find a few candidate jobs to run (in case some immediately get locked by others).
def self.find_available(worker_name, limit = 5, max_run_time = Worker.max_run_time)
jobs = all.select do |j|
j.run_at <= db_time_now &&
(j.locked_at.nil? || j.locked_at < db_time_now - max_run_time || j.locked_by == worker_name) &&
!j.failed?
end
jobs = jobs.select{|j| Worker.queues.include?(j.queue)} if Worker.queues.any?
jobs = jobs.select{|j| j.priority >= Worker.min_priority} if Worker.min_priority
jobs = jobs.select{|j| j.priority <= Worker.max_priority} if Worker.max_priority
jobs.sort_by{|j| [j.priority, j.run_at]}[0..limit-1]
end
# Lock this job for this worker.
# Returns true if we have the lock, false otherwise.
def lock_exclusively!(max_run_time, worker)
now = self.class.db_time_now
if locked_by != worker
# We don't own this job so we will update the locked_by name and the locked_at
self.locked_at = now
self.locked_by = worker
end
return true
end
def self.db_time_now
Time.current
end
def update_attributes(attrs = {})
attrs.each{|k,v| send(:"#{k}=", v)}
save
end
def destroy
self.class.all.delete(self)
end
def save
self.run_at ||= Time.current
self.class.all << self unless self.class.all.include?(self)
true
end
def save!; save; end
def reload
reset
self
end
end
end
end
end

View file

@ -0,0 +1,15 @@
module BackburnerJobsManager
def clear_jobs
Backburner::Worker.connection.tubes.all.map &:clear
end
def start_workers
@thread = Thread.new { Backburner.work "active-jobs-default" }
end
def stop_workers
@thread.kill
end
end

View file

@ -0,0 +1,14 @@
module DelayedJobJobsManager
def clear_jobs
Delayed::Job.delete_all
end
def start_workers
@worker = Delayed::Worker.new(quiet: false, sleep_delay: 0.5)
@thread = Thread.new { @worker.start }
end
def stop_workers
@worker.stop
end
end

View file

@ -0,0 +1,14 @@
module QuJobsManager
def clear_jobs
Qu.clear "active_jobs_default"
end
def start_workers
@thread = Thread.new { Qu::Worker.new("active_jobs_default").start }
end
def stop_workers
@thread.kill
end
end

View file

@ -0,0 +1,19 @@
module QueJobsManager
def clear_jobs
Que.clear!
end
def start_workers
@thread = Thread.new do
loop do
Que::Job.work("active_jobs_default")
sleep 0.5
end
end
end
def stop_workers
@thread.kill
end
end

View file

@ -0,0 +1,21 @@
module QC; WAIT_TIME = 0.5; end
module QueueClassicJobsManager
def clear_jobs
# disabling this as it locks
# QC::Queue.new("active_jobs_default").delete_all
end
def start_workers
@pid = fork do
QC::Conn.connection = QC::Conn.connect
worker = QC::Worker.new(q_name: 'active_jobs_default')
worker.start
end
end
def stop_workers
Process.kill 'HUP', @pid
end
end

View file

@ -0,0 +1,18 @@
module ResqueJobsManager
def clear_jobs
Resque.queues.each { |queue_name| Resque.redis.del "queue:#{queue_name}" }
Resque.redis.keys("delayed:*").each { |key| Resque.redis.del "#{key}" }
Resque.redis.del "delayed_queue_schedule"
end
def start_workers
@thread = Thread.new do
Resque::Worker.new("*").work(0.5)
end
end
def stop_workers
@thread.kill
end
end

View file

@ -0,0 +1,19 @@
require 'sidekiq/launcher'
require 'sidekiq/api'
module SidekiqJobsManager
def clear_jobs
Sidekiq::Queue.new("active_jobs_default").clear
end
def start_workers
options = {:queues=>["active_jobs_default"], :concurrency=>1, :environment=>"test", :timeout=>8, :daemon=>true, :strict=>true}
@launcher = Sidekiq::Launcher.new(options)
@launcher.run
end
def stop_workers
@launcher.stop
end
end

View file

@ -0,0 +1,18 @@
require 'sneakers/runner'
module SneakersJobsManager
def clear_jobs
end
def start_workers
cmd = %{cd #{Rails.root.to_s} && (RAILS_ENV=test AJADAPTER=sneakers WORKERS=ActiveJob::QueueAdapters::SneakersAdapter::JobWrapper bundle exec rake --trace sneakers:run)}
`#{cmd}`
while !Rails.root.join("tmp/sneakers.pid").exist? do
sleep 0.5
end
end
def stop_workers
Process.kill 'TERM', File.open(Rails.root.join("tmp/sneakers.pid").to_s).read.to_i
end
end

View file

@ -0,0 +1,5 @@
module SuckerPunchJobsManager
def clear_jobs
end
end

View file

@ -0,0 +1,12 @@
ENV["RAILS_ENV"] = "test"
require File.expand_path("../../../dummy/config/environment.rb", __FILE__)
require "rails/test_help"
Rails.backtrace_cleaner.remove_silencers!
require_relative 'test_case_helpers'
ActiveSupport::TestCase.send(:include, TestCaseHelpers)
JobsManager.current_manager.setup
JobsManager.current_manager.start_workers
Minitest.after_run { JobsManager.current_manager.stop_workers }

Some files were not shown because too many files have changed in this diff Show more