activejob 5.0.0.beta3 → 5.0.0.beta4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activejob might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/lib/active_job.rb +0 -1
- data/lib/active_job/callbacks.rb +5 -0
- data/lib/active_job/core.rb +2 -2
- data/lib/active_job/enqueuing.rb +1 -1
- data/lib/active_job/execution.rb +4 -2
- data/lib/active_job/gem_version.rb +1 -1
- data/lib/active_job/queue_adapters.rb +2 -1
- data/lib/active_job/queue_adapters/async_adapter.rb +98 -7
- data/lib/active_job/railtie.rb +9 -0
- data/lib/active_job/test_helper.rb +276 -293
- data/lib/rails/generators/job/job_generator.rb +15 -0
- data/lib/rails/generators/job/templates/application_job.rb +4 -0
- metadata +7 -7
- data/lib/active_job/async_job.rb +0 -77
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2222c833aa10b4408013759ed7bd4f5a55f3f4c3
|
4
|
+
data.tar.gz: c659efca7e60db7b59168fd94b73d21402cb619c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be60b1de999875823e05c7b8b02145511d629370a100e2d5daf0a32d1bbe226b3f1eb270b41230b8fd901efa48e43061bf849ca474b1a7c7bf798cc631795ddd
|
7
|
+
data.tar.gz: 82449401bccd1b3ff8dda0004404f477e1953639849081613ce4fa4d39ca89a9bcd3899a04d2e04dcd0a36b23276cb18515c4774a2371ec61ec5714abc45761f
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
## Rails 5.0.0.beta4 (April 27, 2016) ##
|
2
|
+
|
3
|
+
* Enable class reloading prior to job dispatch, and ensure Active Record
|
4
|
+
connections are returned to the pool when jobs are run in separate threads.
|
5
|
+
|
6
|
+
*Matthew Draper*
|
7
|
+
|
8
|
+
* Tune the async adapter for low-footprint dev/test usage. Use a single
|
9
|
+
thread pool for all queues and limit to 0 to #CPU total threads, down from
|
10
|
+
2 to 10*#CPU per queue.
|
11
|
+
|
12
|
+
*Jeremy Daer*
|
13
|
+
|
14
|
+
|
1
15
|
## Rails 5.0.0.beta3 (February 24, 2016) ##
|
2
16
|
|
3
17
|
* Change the default adapter from inline to async. It's a better default as tests will then not mistakenly
|
data/lib/active_job.rb
CHANGED
data/lib/active_job/callbacks.rb
CHANGED
@@ -17,6 +17,11 @@ module ActiveJob
|
|
17
17
|
extend ActiveSupport::Concern
|
18
18
|
include ActiveSupport::Callbacks
|
19
19
|
|
20
|
+
class << self
|
21
|
+
include ActiveSupport::Callbacks
|
22
|
+
define_callbacks :execute
|
23
|
+
end
|
24
|
+
|
20
25
|
included do
|
21
26
|
define_callbacks :perform
|
22
27
|
define_callbacks :enqueue
|
data/lib/active_job/core.rb
CHANGED
@@ -79,7 +79,7 @@ module ActiveJob
|
|
79
79
|
'queue_name' => queue_name,
|
80
80
|
'priority' => priority,
|
81
81
|
'arguments' => serialize_arguments(arguments),
|
82
|
-
'locale' => I18n.locale
|
82
|
+
'locale' => I18n.locale.to_s
|
83
83
|
}
|
84
84
|
end
|
85
85
|
|
@@ -108,7 +108,7 @@ module ActiveJob
|
|
108
108
|
self.queue_name = job_data['queue_name']
|
109
109
|
self.priority = job_data['priority']
|
110
110
|
self.serialized_arguments = job_data['arguments']
|
111
|
-
self.locale = job_data['locale'] || I18n.locale
|
111
|
+
self.locale = job_data['locale'] || I18n.locale.to_s
|
112
112
|
end
|
113
113
|
|
114
114
|
private
|
data/lib/active_job/enqueuing.rb
CHANGED
data/lib/active_job/execution.rb
CHANGED
@@ -33,7 +33,8 @@ module ActiveJob
|
|
33
33
|
#
|
34
34
|
# ==== Async
|
35
35
|
#
|
36
|
-
# Yes: The Queue Adapter
|
36
|
+
# Yes: The Queue Adapter has the ability to run the job in a non-blocking manner.
|
37
|
+
# It either runs on a separate or forked process, or on a different thread.
|
37
38
|
#
|
38
39
|
# No: The job is run in the same process.
|
39
40
|
#
|
@@ -1,22 +1,113 @@
|
|
1
|
-
require '
|
1
|
+
require 'securerandom'
|
2
|
+
require 'concurrent/scheduled_task'
|
3
|
+
require 'concurrent/executor/thread_pool_executor'
|
4
|
+
require 'concurrent/utility/processor_counter'
|
2
5
|
|
3
6
|
module ActiveJob
|
4
7
|
module QueueAdapters
|
5
8
|
# == Active Job Async adapter
|
6
9
|
#
|
7
|
-
#
|
8
|
-
# asynchronously using {AsyncJob}[http://api.rubyonrails.org/classes/ActiveJob/AsyncJob.html].
|
10
|
+
# The Async adapter runs jobs with an in-process thread pool.
|
9
11
|
#
|
10
|
-
#
|
12
|
+
# This is the default queue adapter. It's well-suited for dev/test since
|
13
|
+
# it doesn't need an external infrastructure, but it's a poor fit for
|
14
|
+
# production since it drops pending jobs on restart.
|
11
15
|
#
|
12
|
-
#
|
16
|
+
# To use this adapter, set queue adapter to +:async+:
|
17
|
+
#
|
18
|
+
# config.active_job.queue_adapter = :async
|
19
|
+
#
|
20
|
+
# To configure the adapter's thread pool, instantiate the adapter and
|
21
|
+
# pass your own config:
|
22
|
+
#
|
23
|
+
# config.active_job.queue_adapter = ActiveJob::QueueAdapters::AsyncAdapter.new \
|
24
|
+
# min_threads: 1,
|
25
|
+
# max_threads: 2 * Concurrent.processor_count,
|
26
|
+
# idletime: 600.seconds
|
27
|
+
#
|
28
|
+
# The adapter uses a {Concurrent Ruby}[https://github.com/ruby-concurrency/concurrent-ruby] thread pool to schedule and execute
|
29
|
+
# jobs. Since jobs share a single thread pool, long-running jobs will block
|
30
|
+
# short-lived jobs. Fine for dev/test; bad for production.
|
13
31
|
class AsyncAdapter
|
32
|
+
# See {Concurrent::ThreadPoolExecutor}[http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadPoolExecutor.html] for executor options.
|
33
|
+
def initialize(**executor_options)
|
34
|
+
@scheduler = Scheduler.new(**executor_options)
|
35
|
+
end
|
36
|
+
|
14
37
|
def enqueue(job) #:nodoc:
|
15
|
-
|
38
|
+
@scheduler.enqueue JobWrapper.new(job), queue_name: job.queue_name
|
16
39
|
end
|
17
40
|
|
18
41
|
def enqueue_at(job, timestamp) #:nodoc:
|
19
|
-
|
42
|
+
@scheduler.enqueue_at JobWrapper.new(job), timestamp, queue_name: job.queue_name
|
43
|
+
end
|
44
|
+
|
45
|
+
# Gracefully stop processing jobs. Finishes in-progress work and handles
|
46
|
+
# any new jobs following the executor's fallback policy (`caller_runs`).
|
47
|
+
# Waits for termination by default. Pass `wait: false` to continue.
|
48
|
+
def shutdown(wait: true) #:nodoc:
|
49
|
+
@scheduler.shutdown wait: wait
|
50
|
+
end
|
51
|
+
|
52
|
+
# Used for our test suite.
|
53
|
+
def immediate=(immediate) #:nodoc:
|
54
|
+
@scheduler.immediate = immediate
|
55
|
+
end
|
56
|
+
|
57
|
+
# Note that we don't actually need to serialize the jobs since we're
|
58
|
+
# performing them in-process, but we do so anyway for parity with other
|
59
|
+
# adapters and deployment environments. Otherwise, serialization bugs
|
60
|
+
# may creep in undetected.
|
61
|
+
class JobWrapper #:nodoc:
|
62
|
+
def initialize(job)
|
63
|
+
job.provider_job_id = SecureRandom.uuid
|
64
|
+
@job_data = job.serialize
|
65
|
+
end
|
66
|
+
|
67
|
+
def perform
|
68
|
+
Base.execute @job_data
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class Scheduler #:nodoc:
|
73
|
+
DEFAULT_EXECUTOR_OPTIONS = {
|
74
|
+
min_threads: 0,
|
75
|
+
max_threads: Concurrent.processor_count,
|
76
|
+
auto_terminate: true,
|
77
|
+
idletime: 60, # 1 minute
|
78
|
+
max_queue: 0, # unlimited
|
79
|
+
fallback_policy: :caller_runs # shouldn't matter -- 0 max queue
|
80
|
+
}.freeze
|
81
|
+
|
82
|
+
attr_accessor :immediate
|
83
|
+
|
84
|
+
def initialize(**options)
|
85
|
+
self.immediate = false
|
86
|
+
@immediate_executor = Concurrent::ImmediateExecutor.new
|
87
|
+
@async_executor = Concurrent::ThreadPoolExecutor.new(DEFAULT_EXECUTOR_OPTIONS.merge(options))
|
88
|
+
end
|
89
|
+
|
90
|
+
def enqueue(job, queue_name:)
|
91
|
+
executor.post(job, &:perform)
|
92
|
+
end
|
93
|
+
|
94
|
+
def enqueue_at(job, timestamp, queue_name:)
|
95
|
+
delay = timestamp - Time.current.to_f
|
96
|
+
if delay > 0
|
97
|
+
Concurrent::ScheduledTask.execute(delay, args: [job], executor: executor, &:perform)
|
98
|
+
else
|
99
|
+
enqueue(job, queue_name: queue_name)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def shutdown(wait: true)
|
104
|
+
@async_executor.shutdown
|
105
|
+
@async_executor.wait_for_termination if wait
|
106
|
+
end
|
107
|
+
|
108
|
+
def executor
|
109
|
+
immediate ? @immediate_executor : @async_executor
|
110
|
+
end
|
20
111
|
end
|
21
112
|
end
|
22
113
|
end
|
data/lib/active_job/railtie.rb
CHANGED
@@ -19,5 +19,14 @@ module ActiveJob
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
+
initializer "active_job.set_reloader_hook" do |app|
|
23
|
+
ActiveSupport.on_load(:active_job) do
|
24
|
+
ActiveJob::Callbacks.singleton_class.set_callback(:execute, :around, prepend: true) do |_, inner|
|
25
|
+
app.reloader.wrap do
|
26
|
+
inner.call
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
22
31
|
end
|
23
32
|
end
|
@@ -4,325 +4,308 @@ require 'active_support/core_ext/hash/keys'
|
|
4
4
|
module ActiveJob
|
5
5
|
# Provides helper methods for testing Active Job
|
6
6
|
module TestHelper
|
7
|
-
|
7
|
+
delegate :enqueued_jobs, :enqueued_jobs=,
|
8
|
+
:performed_jobs, :performed_jobs=,
|
9
|
+
to: :queue_adapter
|
8
10
|
|
9
|
-
|
10
|
-
|
11
|
-
test_adapter = ActiveJob::QueueAdapters::TestAdapter.new
|
11
|
+
def before_setup # :nodoc:
|
12
|
+
test_adapter = ActiveJob::QueueAdapters::TestAdapter.new
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
end
|
14
|
+
@old_queue_adapters = (ActiveJob::Base.subclasses << ActiveJob::Base).select do |klass|
|
15
|
+
# only override explicitly set adapters, a quirk of `class_attribute`
|
16
|
+
klass.singleton_class.public_instance_methods(false).include?(:_queue_adapter)
|
17
|
+
end.map do |klass|
|
18
|
+
[klass, klass.queue_adapter].tap do
|
19
|
+
klass.queue_adapter = test_adapter
|
20
20
|
end
|
21
|
-
|
22
|
-
clear_enqueued_jobs
|
23
|
-
clear_performed_jobs
|
24
|
-
super
|
25
21
|
end
|
26
22
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
end
|
32
|
-
end
|
23
|
+
clear_enqueued_jobs
|
24
|
+
clear_performed_jobs
|
25
|
+
super
|
26
|
+
end
|
33
27
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
# HelloJob.perform_later('david')
|
39
|
-
# assert_enqueued_jobs 1
|
40
|
-
# HelloJob.perform_later('abdelkader')
|
41
|
-
# assert_enqueued_jobs 2
|
42
|
-
# end
|
43
|
-
#
|
44
|
-
# If a block is passed, that block should cause the specified number of
|
45
|
-
# jobs to be enqueued.
|
46
|
-
#
|
47
|
-
# def test_jobs_again
|
48
|
-
# assert_enqueued_jobs 1 do
|
49
|
-
# HelloJob.perform_later('cristian')
|
50
|
-
# end
|
51
|
-
#
|
52
|
-
# assert_enqueued_jobs 2 do
|
53
|
-
# HelloJob.perform_later('aaron')
|
54
|
-
# HelloJob.perform_later('rafael')
|
55
|
-
# end
|
56
|
-
# end
|
57
|
-
#
|
58
|
-
# The number of times a specific job is enqueued can be asserted.
|
59
|
-
#
|
60
|
-
# def test_logging_job
|
61
|
-
# assert_enqueued_jobs 1, only: LoggingJob do
|
62
|
-
# LoggingJob.perform_later
|
63
|
-
# HelloJob.perform_later('jeremy')
|
64
|
-
# end
|
65
|
-
# end
|
66
|
-
def assert_enqueued_jobs(number, only: nil)
|
67
|
-
if block_given?
|
68
|
-
original_count = enqueued_jobs_size(only: only)
|
69
|
-
yield
|
70
|
-
new_count = enqueued_jobs_size(only: only)
|
71
|
-
assert_equal number, new_count - original_count, "#{number} jobs expected, but #{new_count - original_count} were enqueued"
|
72
|
-
else
|
73
|
-
actual_count = enqueued_jobs_size(only: only)
|
74
|
-
assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued"
|
75
|
-
end
|
28
|
+
def after_teardown # :nodoc:
|
29
|
+
super
|
30
|
+
@old_queue_adapters.each do |(klass, adapter)|
|
31
|
+
klass.queue_adapter = adapter
|
76
32
|
end
|
33
|
+
end
|
77
34
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
35
|
+
# Asserts that the number of enqueued jobs matches the given number.
|
36
|
+
#
|
37
|
+
# def test_jobs
|
38
|
+
# assert_enqueued_jobs 0
|
39
|
+
# HelloJob.perform_later('david')
|
40
|
+
# assert_enqueued_jobs 1
|
41
|
+
# HelloJob.perform_later('abdelkader')
|
42
|
+
# assert_enqueued_jobs 2
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# If a block is passed, that block should cause the specified number of
|
46
|
+
# jobs to be enqueued.
|
47
|
+
#
|
48
|
+
# def test_jobs_again
|
49
|
+
# assert_enqueued_jobs 1 do
|
50
|
+
# HelloJob.perform_later('cristian')
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# assert_enqueued_jobs 2 do
|
54
|
+
# HelloJob.perform_later('aaron')
|
55
|
+
# HelloJob.perform_later('rafael')
|
56
|
+
# end
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# The number of times a specific job is enqueued can be asserted.
|
60
|
+
#
|
61
|
+
# def test_logging_job
|
62
|
+
# assert_enqueued_jobs 1, only: LoggingJob do
|
63
|
+
# LoggingJob.perform_later
|
64
|
+
# HelloJob.perform_later('jeremy')
|
65
|
+
# end
|
66
|
+
# end
|
67
|
+
def assert_enqueued_jobs(number, only: nil)
|
68
|
+
if block_given?
|
69
|
+
original_count = enqueued_jobs_size(only: only)
|
70
|
+
yield
|
71
|
+
new_count = enqueued_jobs_size(only: only)
|
72
|
+
assert_equal number, new_count - original_count, "#{number} jobs expected, but #{new_count - original_count} were enqueued"
|
73
|
+
else
|
74
|
+
actual_count = enqueued_jobs_size(only: only)
|
75
|
+
assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued"
|
107
76
|
end
|
77
|
+
end
|
108
78
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
# end
|
140
|
-
#
|
141
|
-
# The block form supports filtering. If the :only option is specified,
|
142
|
-
# then only the listed job(s) will be performed.
|
143
|
-
#
|
144
|
-
# def test_hello_job
|
145
|
-
# assert_performed_jobs 1, only: HelloJob do
|
146
|
-
# HelloJob.perform_later('jeremy')
|
147
|
-
# LoggingJob.perform_later
|
148
|
-
# end
|
149
|
-
# end
|
150
|
-
#
|
151
|
-
# An array may also be specified, to support testing multiple jobs.
|
152
|
-
#
|
153
|
-
# def test_hello_and_logging_jobs
|
154
|
-
# assert_nothing_raised do
|
155
|
-
# assert_performed_jobs 2, only: [HelloJob, LoggingJob] do
|
156
|
-
# HelloJob.perform_later('jeremy')
|
157
|
-
# LoggingJob.perform_later('stewie')
|
158
|
-
# RescueJob.perform_later('david')
|
159
|
-
# end
|
160
|
-
# end
|
161
|
-
# end
|
162
|
-
def assert_performed_jobs(number, only: nil)
|
163
|
-
if block_given?
|
164
|
-
original_count = performed_jobs.size
|
165
|
-
perform_enqueued_jobs(only: only) { yield }
|
166
|
-
new_count = performed_jobs.size
|
167
|
-
assert_equal number, new_count - original_count,
|
168
|
-
"#{number} jobs expected, but #{new_count - original_count} were performed"
|
169
|
-
else
|
170
|
-
performed_jobs_size = performed_jobs.size
|
171
|
-
assert_equal number, performed_jobs_size, "#{number} jobs expected, but #{performed_jobs_size} were performed"
|
172
|
-
end
|
173
|
-
end
|
79
|
+
# Asserts that no jobs have been enqueued.
|
80
|
+
#
|
81
|
+
# def test_jobs
|
82
|
+
# assert_no_enqueued_jobs
|
83
|
+
# HelloJob.perform_later('jeremy')
|
84
|
+
# assert_enqueued_jobs 1
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
# If a block is passed, that block should not cause any job to be enqueued.
|
88
|
+
#
|
89
|
+
# def test_jobs_again
|
90
|
+
# assert_no_enqueued_jobs do
|
91
|
+
# # No job should be enqueued from this block
|
92
|
+
# end
|
93
|
+
# end
|
94
|
+
#
|
95
|
+
# It can be asserted that no jobs of a specific kind are enqueued:
|
96
|
+
#
|
97
|
+
# def test_no_logging
|
98
|
+
# assert_no_enqueued_jobs only: LoggingJob do
|
99
|
+
# HelloJob.perform_later('jeremy')
|
100
|
+
# end
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# Note: This assertion is simply a shortcut for:
|
104
|
+
#
|
105
|
+
# assert_enqueued_jobs 0, &block
|
106
|
+
def assert_no_enqueued_jobs(only: nil, &block)
|
107
|
+
assert_enqueued_jobs 0, only: only, &block
|
108
|
+
end
|
174
109
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
110
|
+
# Asserts that the number of performed jobs matches the given number.
|
111
|
+
# If no block is passed, <tt>perform_enqueued_jobs</tt>
|
112
|
+
# must be called around the job call.
|
113
|
+
#
|
114
|
+
# def test_jobs
|
115
|
+
# assert_performed_jobs 0
|
116
|
+
#
|
117
|
+
# perform_enqueued_jobs do
|
118
|
+
# HelloJob.perform_later('xavier')
|
119
|
+
# end
|
120
|
+
# assert_performed_jobs 1
|
121
|
+
#
|
122
|
+
# perform_enqueued_jobs do
|
123
|
+
# HelloJob.perform_later('yves')
|
124
|
+
# assert_performed_jobs 2
|
125
|
+
# end
|
126
|
+
# end
|
127
|
+
#
|
128
|
+
# If a block is passed, that block should cause the specified number of
|
129
|
+
# jobs to be performed.
|
130
|
+
#
|
131
|
+
# def test_jobs_again
|
132
|
+
# assert_performed_jobs 1 do
|
133
|
+
# HelloJob.perform_later('robin')
|
134
|
+
# end
|
135
|
+
#
|
136
|
+
# assert_performed_jobs 2 do
|
137
|
+
# HelloJob.perform_later('carlos')
|
138
|
+
# HelloJob.perform_later('sean')
|
139
|
+
# end
|
140
|
+
# end
|
141
|
+
#
|
142
|
+
# The block form supports filtering. If the :only option is specified,
|
143
|
+
# then only the listed job(s) will be performed.
|
144
|
+
#
|
145
|
+
# def test_hello_job
|
146
|
+
# assert_performed_jobs 1, only: HelloJob do
|
147
|
+
# HelloJob.perform_later('jeremy')
|
148
|
+
# LoggingJob.perform_later
|
149
|
+
# end
|
150
|
+
# end
|
151
|
+
#
|
152
|
+
# An array may also be specified, to support testing multiple jobs.
|
153
|
+
#
|
154
|
+
# def test_hello_and_logging_jobs
|
155
|
+
# assert_nothing_raised do
|
156
|
+
# assert_performed_jobs 2, only: [HelloJob, LoggingJob] do
|
157
|
+
# HelloJob.perform_later('jeremy')
|
158
|
+
# LoggingJob.perform_later('stewie')
|
159
|
+
# RescueJob.perform_later('david')
|
160
|
+
# end
|
161
|
+
# end
|
162
|
+
# end
|
163
|
+
def assert_performed_jobs(number, only: nil)
|
164
|
+
if block_given?
|
165
|
+
original_count = performed_jobs.size
|
166
|
+
perform_enqueued_jobs(only: only) { yield }
|
167
|
+
new_count = performed_jobs.size
|
168
|
+
assert_equal number, new_count - original_count,
|
169
|
+
"#{number} jobs expected, but #{new_count - original_count} were performed"
|
170
|
+
else
|
171
|
+
performed_jobs_size = performed_jobs.size
|
172
|
+
assert_equal number, performed_jobs_size, "#{number} jobs expected, but #{performed_jobs_size} were performed"
|
221
173
|
end
|
174
|
+
end
|
222
175
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
176
|
+
# Asserts that no jobs have been performed.
|
177
|
+
#
|
178
|
+
# def test_jobs
|
179
|
+
# assert_no_performed_jobs
|
180
|
+
#
|
181
|
+
# perform_enqueued_jobs do
|
182
|
+
# HelloJob.perform_later('matthew')
|
183
|
+
# assert_performed_jobs 1
|
184
|
+
# end
|
185
|
+
# end
|
186
|
+
#
|
187
|
+
# If a block is passed, that block should not cause any job to be performed.
|
188
|
+
#
|
189
|
+
# def test_jobs_again
|
190
|
+
# assert_no_performed_jobs do
|
191
|
+
# # No job should be performed from this block
|
192
|
+
# end
|
193
|
+
# end
|
194
|
+
#
|
195
|
+
# The block form supports filtering. If the :only option is specified,
|
196
|
+
# then only the listed job(s) will not be performed.
|
197
|
+
#
|
198
|
+
# def test_no_logging
|
199
|
+
# assert_no_performed_jobs only: LoggingJob do
|
200
|
+
# HelloJob.perform_later('jeremy')
|
201
|
+
# end
|
202
|
+
# end
|
203
|
+
#
|
204
|
+
# Note: This assertion is simply a shortcut for:
|
205
|
+
#
|
206
|
+
# assert_performed_jobs 0, &block
|
207
|
+
def assert_no_performed_jobs(only: nil, &block)
|
208
|
+
assert_performed_jobs 0, only: only, &block
|
209
|
+
end
|
246
210
|
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
end
|
267
|
-
assert matching_job, "No performed job found with #{args}"
|
268
|
-
instantiate_job(matching_job)
|
211
|
+
# Asserts that the job passed in the block has been enqueued with the given arguments.
|
212
|
+
#
|
213
|
+
# def test_assert_enqueued_with
|
214
|
+
# assert_enqueued_with(job: MyJob, args: [1,2,3], queue: 'low') do
|
215
|
+
# MyJob.perform_later(1,2,3)
|
216
|
+
# end
|
217
|
+
#
|
218
|
+
# assert_enqueued_with(job: MyJob, at: Date.tomorrow.noon) do
|
219
|
+
# MyJob.set(wait_until: Date.tomorrow.noon).perform_later
|
220
|
+
# end
|
221
|
+
# end
|
222
|
+
def assert_enqueued_with(args = {})
|
223
|
+
original_enqueued_jobs_count = enqueued_jobs.count
|
224
|
+
args.assert_valid_keys(:job, :args, :at, :queue)
|
225
|
+
serialized_args = serialize_args_for_assertion(args)
|
226
|
+
yield
|
227
|
+
in_block_jobs = enqueued_jobs.drop(original_enqueued_jobs_count)
|
228
|
+
matching_job = in_block_jobs.find do |job|
|
229
|
+
serialized_args.all? { |key, value| value == job[key] }
|
269
230
|
end
|
231
|
+
assert matching_job, "No enqueued job found with #{args}"
|
232
|
+
instantiate_job(matching_job)
|
233
|
+
end
|
270
234
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
235
|
+
# Asserts that the job passed in the block has been performed with the given arguments.
|
236
|
+
#
|
237
|
+
# def test_assert_performed_with
|
238
|
+
# assert_performed_with(job: MyJob, args: [1,2,3], queue: 'high') do
|
239
|
+
# MyJob.perform_later(1,2,3)
|
240
|
+
# end
|
241
|
+
#
|
242
|
+
# assert_performed_with(job: MyJob, at: Date.tomorrow.noon) do
|
243
|
+
# MyJob.set(wait_until: Date.tomorrow.noon).perform_later
|
244
|
+
# end
|
245
|
+
# end
|
246
|
+
def assert_performed_with(args = {})
|
247
|
+
original_performed_jobs_count = performed_jobs.count
|
248
|
+
args.assert_valid_keys(:job, :args, :at, :queue)
|
249
|
+
serialized_args = serialize_args_for_assertion(args)
|
250
|
+
perform_enqueued_jobs { yield }
|
251
|
+
in_block_jobs = performed_jobs.drop(original_performed_jobs_count)
|
252
|
+
matching_job = in_block_jobs.find do |job|
|
253
|
+
serialized_args.all? { |key, value| value == job[key] }
|
286
254
|
end
|
255
|
+
assert matching_job, "No performed job found with #{args}"
|
256
|
+
instantiate_job(matching_job)
|
257
|
+
end
|
258
|
+
|
259
|
+
def perform_enqueued_jobs(only: nil)
|
260
|
+
old_perform_enqueued_jobs = queue_adapter.perform_enqueued_jobs
|
261
|
+
old_perform_enqueued_at_jobs = queue_adapter.perform_enqueued_at_jobs
|
262
|
+
old_filter = queue_adapter.filter
|
287
263
|
|
288
|
-
|
289
|
-
|
264
|
+
begin
|
265
|
+
queue_adapter.perform_enqueued_jobs = true
|
266
|
+
queue_adapter.perform_enqueued_at_jobs = true
|
267
|
+
queue_adapter.filter = only
|
268
|
+
yield
|
269
|
+
ensure
|
270
|
+
queue_adapter.perform_enqueued_jobs = old_perform_enqueued_jobs
|
271
|
+
queue_adapter.perform_enqueued_at_jobs = old_perform_enqueued_at_jobs
|
272
|
+
queue_adapter.filter = old_filter
|
290
273
|
end
|
274
|
+
end
|
291
275
|
|
292
|
-
|
293
|
-
|
294
|
-
|
276
|
+
def queue_adapter
|
277
|
+
ActiveJob::Base.queue_adapter
|
278
|
+
end
|
295
279
|
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
280
|
+
private
|
281
|
+
def clear_enqueued_jobs # :nodoc:
|
282
|
+
enqueued_jobs.clear
|
283
|
+
end
|
300
284
|
|
301
|
-
|
302
|
-
|
303
|
-
|
285
|
+
def clear_performed_jobs # :nodoc:
|
286
|
+
performed_jobs.clear
|
287
|
+
end
|
304
288
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
end
|
289
|
+
def enqueued_jobs_size(only: nil) # :nodoc:
|
290
|
+
if only
|
291
|
+
enqueued_jobs.count { |job| Array(only).include?(job.fetch(:job)) }
|
292
|
+
else
|
293
|
+
enqueued_jobs.count
|
311
294
|
end
|
295
|
+
end
|
312
296
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
end
|
297
|
+
def serialize_args_for_assertion(args) # :nodoc:
|
298
|
+
args.dup.tap do |serialized_args|
|
299
|
+
serialized_args[:args] = ActiveJob::Arguments.serialize(serialized_args[:args]) if serialized_args[:args]
|
300
|
+
serialized_args[:at] = serialized_args[:at].to_f if serialized_args[:at]
|
318
301
|
end
|
302
|
+
end
|
319
303
|
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
end
|
304
|
+
def instantiate_job(payload) # :nodoc:
|
305
|
+
job = payload[:job].new(*payload[:args])
|
306
|
+
job.scheduled_at = Time.at(payload[:at]) if payload.key?(:at)
|
307
|
+
job.queue_name = payload[:queue]
|
308
|
+
job
|
309
|
+
end
|
327
310
|
end
|
328
311
|
end
|
@@ -17,7 +17,22 @@ module Rails # :nodoc:
|
|
17
17
|
|
18
18
|
def create_job_file
|
19
19
|
template 'job.rb', File.join('app/jobs', class_path, "#{file_name}_job.rb")
|
20
|
+
|
21
|
+
in_root do
|
22
|
+
if self.behavior == :invoke && !File.exist?(application_job_file_name)
|
23
|
+
template 'application_job.rb', application_job_file_name
|
24
|
+
end
|
25
|
+
end
|
20
26
|
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def application_job_file_name
|
30
|
+
@application_job_file_name ||= if mountable_engine?
|
31
|
+
"app/jobs/#{namespaced_path}/application_job.rb"
|
32
|
+
else
|
33
|
+
'app/jobs/application_job.rb'
|
34
|
+
end
|
35
|
+
end
|
21
36
|
end
|
22
37
|
end
|
23
38
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activejob
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.0.0.
|
4
|
+
version: 5.0.0.beta4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Heinemeier Hansson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-04-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 5.0.0.
|
19
|
+
version: 5.0.0.beta4
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 5.0.0.
|
26
|
+
version: 5.0.0.beta4
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: globalid
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -49,7 +49,6 @@ files:
|
|
49
49
|
- README.md
|
50
50
|
- lib/active_job.rb
|
51
51
|
- lib/active_job/arguments.rb
|
52
|
-
- lib/active_job/async_job.rb
|
53
52
|
- lib/active_job/base.rb
|
54
53
|
- lib/active_job/callbacks.rb
|
55
54
|
- lib/active_job/configured_job.rb
|
@@ -80,8 +79,9 @@ files:
|
|
80
79
|
- lib/active_job/translation.rb
|
81
80
|
- lib/active_job/version.rb
|
82
81
|
- lib/rails/generators/job/job_generator.rb
|
82
|
+
- lib/rails/generators/job/templates/application_job.rb
|
83
83
|
- lib/rails/generators/job/templates/job.rb
|
84
|
-
homepage: http://
|
84
|
+
homepage: http://rubyonrails.org
|
85
85
|
licenses:
|
86
86
|
- MIT
|
87
87
|
metadata: {}
|
@@ -101,7 +101,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
101
101
|
version: 1.3.1
|
102
102
|
requirements: []
|
103
103
|
rubyforge_project:
|
104
|
-
rubygems_version: 2.
|
104
|
+
rubygems_version: 2.6.4
|
105
105
|
signing_key:
|
106
106
|
specification_version: 4
|
107
107
|
summary: Job framework with pluggable queues.
|
data/lib/active_job/async_job.rb
DELETED
@@ -1,77 +0,0 @@
|
|
1
|
-
require 'concurrent/map'
|
2
|
-
require 'concurrent/scheduled_task'
|
3
|
-
require 'concurrent/executor/thread_pool_executor'
|
4
|
-
require 'concurrent/utility/processor_counter'
|
5
|
-
|
6
|
-
module ActiveJob
|
7
|
-
# == Active Job Async Job
|
8
|
-
#
|
9
|
-
# When enqueuing jobs with Async Job each job will be executed asynchronously
|
10
|
-
# on a +concurrent-ruby+ thread pool. All job data is retained in memory.
|
11
|
-
# Because job data is not saved to a persistent datastore there is no
|
12
|
-
# additional infrastructure needed and jobs process quickly. The lack of
|
13
|
-
# persistence, however, means that all unprocessed jobs will be lost on
|
14
|
-
# application restart. Therefore in-memory queue adapters are unsuitable for
|
15
|
-
# most production environments but are excellent for development and testing.
|
16
|
-
#
|
17
|
-
# Read more about Concurrent Ruby {here}[https://github.com/ruby-concurrency/concurrent-ruby].
|
18
|
-
#
|
19
|
-
# To use Async Job set the queue_adapter config to +:async+.
|
20
|
-
#
|
21
|
-
# Rails.application.config.active_job.queue_adapter = :async
|
22
|
-
#
|
23
|
-
# Async Job supports job queues specified with +queue_as+. Queues are created
|
24
|
-
# automatically as needed and each has its own thread pool.
|
25
|
-
class AsyncJob
|
26
|
-
|
27
|
-
DEFAULT_EXECUTOR_OPTIONS = {
|
28
|
-
min_threads: [2, Concurrent.processor_count].max,
|
29
|
-
max_threads: Concurrent.processor_count * 10,
|
30
|
-
auto_terminate: true,
|
31
|
-
idletime: 60, # 1 minute
|
32
|
-
max_queue: 0, # unlimited
|
33
|
-
fallback_policy: :caller_runs # shouldn't matter -- 0 max queue
|
34
|
-
}.freeze
|
35
|
-
|
36
|
-
QUEUES = Concurrent::Map.new do |hash, queue_name| #:nodoc:
|
37
|
-
hash.compute_if_absent(queue_name) { ActiveJob::AsyncJob.create_thread_pool }
|
38
|
-
end
|
39
|
-
|
40
|
-
class << self
|
41
|
-
# Forces jobs to process immediately when testing the Active Job gem.
|
42
|
-
# This should only be called from within unit tests.
|
43
|
-
def perform_immediately! #:nodoc:
|
44
|
-
@perform_immediately = true
|
45
|
-
end
|
46
|
-
|
47
|
-
# Allows jobs to run asynchronously when testing the Active Job gem.
|
48
|
-
# This should only be called from within unit tests.
|
49
|
-
def perform_asynchronously! #:nodoc:
|
50
|
-
@perform_immediately = false
|
51
|
-
end
|
52
|
-
|
53
|
-
def create_thread_pool #:nodoc:
|
54
|
-
if @perform_immediately
|
55
|
-
Concurrent::ImmediateExecutor.new
|
56
|
-
else
|
57
|
-
Concurrent::ThreadPoolExecutor.new(DEFAULT_EXECUTOR_OPTIONS)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def enqueue(job_data, queue: 'default') #:nodoc:
|
62
|
-
QUEUES[queue].post(job_data) { |job| ActiveJob::Base.execute(job) }
|
63
|
-
end
|
64
|
-
|
65
|
-
def enqueue_at(job_data, timestamp, queue: 'default') #:nodoc:
|
66
|
-
delay = timestamp - Time.current.to_f
|
67
|
-
if delay > 0
|
68
|
-
Concurrent::ScheduledTask.execute(delay, args: [job_data], executor: QUEUES[queue]) do |job|
|
69
|
-
ActiveJob::Base.execute(job)
|
70
|
-
end
|
71
|
-
else
|
72
|
-
enqueue(job_data, queue: queue)
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|