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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 526b7c0ad7e2e92215cbb0bf362ef9113e248cd2
4
- data.tar.gz: 3f66e41cd86278974127b13e08a28b81a9c68c0e
3
+ metadata.gz: 2222c833aa10b4408013759ed7bd4f5a55f3f4c3
4
+ data.tar.gz: c659efca7e60db7b59168fd94b73d21402cb619c
5
5
  SHA512:
6
- metadata.gz: eb8349f3129814464cfe53b3c9ec2f4d9a1a900482ca510093ba66b23ee8f8246356772070e0f6baf62a9afc297fb8f6b5cc03284ad81c6946782187243cf05e
7
- data.tar.gz: 01908295426a77f5dfedad73c7e10afe59c5066eb8687800c28e5f4411bc93206cbbcf3ca8dd6db34ee7def299c56e04f46dae88c8724cdae47eb83b82b610b0
6
+ metadata.gz: be60b1de999875823e05c7b8b02145511d629370a100e2d5daf0a32d1bbe226b3f1eb270b41230b8fd901efa48e43061bf849ca474b1a7c7bf798cc631795ddd
7
+ data.tar.gz: 82449401bccd1b3ff8dda0004404f477e1953639849081613ce4fa4d39ca89a9bcd3899a04d2e04dcd0a36b23276cb18515c4774a2371ec61ec5714abc45761f
@@ -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
@@ -32,7 +32,6 @@ module ActiveJob
32
32
  autoload :Base
33
33
  autoload :QueueAdapters
34
34
  autoload :ConfiguredJob
35
- autoload :AsyncJob
36
35
  autoload :TestCase
37
36
  autoload :TestHelper
38
37
  end
@@ -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
@@ -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
@@ -36,7 +36,7 @@ module ActiveJob
36
36
  #
37
37
  # ==== Examples
38
38
  #
39
- # class SiteScrapperJob < ActiveJob::Base
39
+ # class SiteScraperJob < ActiveJob::Base
40
40
  # rescue_from(ErrorLoadingSite) do
41
41
  # retry_job queue: :low_priority
42
42
  # end
@@ -17,8 +17,10 @@ module ActiveJob
17
17
  end
18
18
 
19
19
  def execute(job_data) #:nodoc:
20
- job = deserialize(job_data)
21
- job.perform_now
20
+ ActiveJob::Callbacks.run_callbacks(:execute) do
21
+ job = deserialize(job_data)
22
+ job.perform_now
23
+ end
22
24
  end
23
25
  end
24
26
 
@@ -8,7 +8,7 @@ module ActiveJob
8
8
  MAJOR = 5
9
9
  MINOR = 0
10
10
  TINY = 0
11
- PRE = "beta3"
11
+ PRE = "beta4"
12
12
 
13
13
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
14
14
  end
@@ -33,7 +33,8 @@ module ActiveJob
33
33
  #
34
34
  # ==== Async
35
35
  #
36
- # Yes: The Queue Adapter runs the jobs in a separate or forked process.
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 'active_job/async_job'
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
- # When enqueuing jobs with the Async adapter the job will be executed
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
- # To use +AsyncJob+ set the queue_adapter config to +:async+.
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
- # Rails.application.config.active_job.queue_adapter = :async
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
- ActiveJob::AsyncJob.enqueue(job.serialize, queue: job.queue_name)
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
- ActiveJob::AsyncJob.enqueue_at(job.serialize, timestamp, queue: job.queue_name)
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
@@ -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
- extend ActiveSupport::Concern
7
+ delegate :enqueued_jobs, :enqueued_jobs=,
8
+ :performed_jobs, :performed_jobs=,
9
+ to: :queue_adapter
8
10
 
9
- included do
10
- def before_setup # :nodoc:
11
- test_adapter = ActiveJob::QueueAdapters::TestAdapter.new
11
+ def before_setup # :nodoc:
12
+ test_adapter = ActiveJob::QueueAdapters::TestAdapter.new
12
13
 
13
- @old_queue_adapters = (ActiveJob::Base.subclasses << ActiveJob::Base).select do |klass|
14
- # only override explicitly set adapters, a quirk of `class_attribute`
15
- klass.singleton_class.public_instance_methods(false).include?(:_queue_adapter)
16
- end.map do |klass|
17
- [klass, klass.queue_adapter].tap do
18
- klass.queue_adapter = test_adapter
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
- def after_teardown # :nodoc:
28
- super
29
- @old_queue_adapters.each do |(klass, adapter)|
30
- klass.queue_adapter = adapter
31
- end
32
- end
23
+ clear_enqueued_jobs
24
+ clear_performed_jobs
25
+ super
26
+ end
33
27
 
34
- # Asserts that the number of enqueued jobs matches the given number.
35
- #
36
- # def test_jobs
37
- # assert_enqueued_jobs 0
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
- # Asserts that no jobs have been enqueued.
79
- #
80
- # def test_jobs
81
- # assert_no_enqueued_jobs
82
- # HelloJob.perform_later('jeremy')
83
- # assert_enqueued_jobs 1
84
- # end
85
- #
86
- # If a block is passed, that block should not cause any job to be enqueued.
87
- #
88
- # def test_jobs_again
89
- # assert_no_enqueued_jobs do
90
- # # No job should be enqueued from this block
91
- # end
92
- # end
93
- #
94
- # It can be asserted that no jobs of a specific kind are enqueued:
95
- #
96
- # def test_no_logging
97
- # assert_no_enqueued_jobs only: LoggingJob do
98
- # HelloJob.perform_later('jeremy')
99
- # end
100
- # end
101
- #
102
- # Note: This assertion is simply a shortcut for:
103
- #
104
- # assert_enqueued_jobs 0, &block
105
- def assert_no_enqueued_jobs(only: nil, &block)
106
- assert_enqueued_jobs 0, only: only, &block
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
- # Asserts that the number of performed jobs matches the given number.
110
- # If no block is passed, <tt>perform_enqueued_jobs</tt>
111
- # must be called around the job call.
112
- #
113
- # def test_jobs
114
- # assert_performed_jobs 0
115
- #
116
- # perform_enqueued_jobs do
117
- # HelloJob.perform_later('xavier')
118
- # end
119
- # assert_performed_jobs 1
120
- #
121
- # perform_enqueued_jobs do
122
- # HelloJob.perform_later('yves')
123
- # assert_performed_jobs 2
124
- # end
125
- # end
126
- #
127
- # If a block is passed, that block should cause the specified number of
128
- # jobs to be performed.
129
- #
130
- # def test_jobs_again
131
- # assert_performed_jobs 1 do
132
- # HelloJob.perform_later('robin')
133
- # end
134
- #
135
- # assert_performed_jobs 2 do
136
- # HelloJob.perform_later('carlos')
137
- # HelloJob.perform_later('sean')
138
- # end
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
- # Asserts that no jobs have been performed.
176
- #
177
- # def test_jobs
178
- # assert_no_performed_jobs
179
- #
180
- # perform_enqueued_jobs do
181
- # HelloJob.perform_later('matthew')
182
- # assert_performed_jobs 1
183
- # end
184
- # end
185
- #
186
- # If a block is passed, that block should not cause any job to be performed.
187
- #
188
- # def test_jobs_again
189
- # assert_no_performed_jobs do
190
- # # No job should be performed from this block
191
- # end
192
- # end
193
- #
194
- # The block form supports filtering. If the :only option is specified,
195
- # then only the listed job(s) will be performed.
196
- #
197
- # def test_hello_job
198
- # assert_performed_jobs 1, only: HelloJob do
199
- # HelloJob.perform_later('jeremy')
200
- # LoggingJob.perform_later
201
- # end
202
- # end
203
- #
204
- # An array may also be specified, to support testing multiple jobs.
205
- #
206
- # def test_hello_and_logging_jobs
207
- # assert_nothing_raised do
208
- # assert_performed_jobs 2, only: [HelloJob, LoggingJob] do
209
- # HelloJob.perform_later('jeremy')
210
- # LoggingJob.perform_later('stewie')
211
- # RescueJob.perform_later('david')
212
- # end
213
- # end
214
- # end
215
- #
216
- # Note: This assertion is simply a shortcut for:
217
- #
218
- # assert_performed_jobs 0, &block
219
- def assert_no_performed_jobs(only: nil, &block)
220
- assert_performed_jobs 0, only: only, &block
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
- # Asserts that the job passed in the block has been enqueued with the given arguments.
224
- #
225
- # def test_assert_enqueued_with
226
- # assert_enqueued_with(job: MyJob, args: [1,2,3], queue: 'low') do
227
- # MyJob.perform_later(1,2,3)
228
- # end
229
- #
230
- # assert_enqueued_with(job: MyJob, at: Date.tomorrow.noon) do
231
- # MyJob.set(wait_until: Date.tomorrow.noon).perform_later
232
- # end
233
- # end
234
- def assert_enqueued_with(args = {})
235
- original_enqueued_jobs_count = enqueued_jobs.count
236
- args.assert_valid_keys(:job, :args, :at, :queue)
237
- serialized_args = serialize_args_for_assertion(args)
238
- yield
239
- in_block_jobs = enqueued_jobs.drop(original_enqueued_jobs_count)
240
- matching_job = in_block_jobs.find do |job|
241
- serialized_args.all? { |key, value| value == job[key] }
242
- end
243
- assert matching_job, "No enqueued job found with #{args}"
244
- instantiate_job(matching_job)
245
- end
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
- # Asserts that the job passed in the block has been performed with the given arguments.
248
- #
249
- # def test_assert_performed_with
250
- # assert_performed_with(job: MyJob, args: [1,2,3], queue: 'high') do
251
- # MyJob.perform_later(1,2,3)
252
- # end
253
- #
254
- # assert_performed_with(job: MyJob, at: Date.tomorrow.noon) do
255
- # MyJob.set(wait_until: Date.tomorrow.noon).perform_later
256
- # end
257
- # end
258
- def assert_performed_with(args = {})
259
- original_performed_jobs_count = performed_jobs.count
260
- args.assert_valid_keys(:job, :args, :at, :queue)
261
- serialized_args = serialize_args_for_assertion(args)
262
- perform_enqueued_jobs { yield }
263
- in_block_jobs = performed_jobs.drop(original_performed_jobs_count)
264
- matching_job = in_block_jobs.find do |job|
265
- serialized_args.all? { |key, value| value == job[key] }
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
- def perform_enqueued_jobs(only: nil)
272
- old_perform_enqueued_jobs = queue_adapter.perform_enqueued_jobs
273
- old_perform_enqueued_at_jobs = queue_adapter.perform_enqueued_at_jobs
274
- old_filter = queue_adapter.filter
275
-
276
- begin
277
- queue_adapter.perform_enqueued_jobs = true
278
- queue_adapter.perform_enqueued_at_jobs = true
279
- queue_adapter.filter = only
280
- yield
281
- ensure
282
- queue_adapter.perform_enqueued_jobs = old_perform_enqueued_jobs
283
- queue_adapter.perform_enqueued_at_jobs = old_perform_enqueued_at_jobs
284
- queue_adapter.filter = old_filter
285
- end
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
- def queue_adapter
289
- ActiveJob::Base.queue_adapter
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
- delegate :enqueued_jobs, :enqueued_jobs=,
293
- :performed_jobs, :performed_jobs=,
294
- to: :queue_adapter
276
+ def queue_adapter
277
+ ActiveJob::Base.queue_adapter
278
+ end
295
279
 
296
- private
297
- def clear_enqueued_jobs # :nodoc:
298
- enqueued_jobs.clear
299
- end
280
+ private
281
+ def clear_enqueued_jobs # :nodoc:
282
+ enqueued_jobs.clear
283
+ end
300
284
 
301
- def clear_performed_jobs # :nodoc:
302
- performed_jobs.clear
303
- end
285
+ def clear_performed_jobs # :nodoc:
286
+ performed_jobs.clear
287
+ end
304
288
 
305
- def enqueued_jobs_size(only: nil) # :nodoc:
306
- if only
307
- enqueued_jobs.count { |job| Array(only).include?(job.fetch(:job)) }
308
- else
309
- enqueued_jobs.count
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
- def serialize_args_for_assertion(args) # :nodoc:
314
- args.dup.tap do |serialized_args|
315
- serialized_args[:args] = ActiveJob::Arguments.serialize(serialized_args[:args]) if serialized_args[:args]
316
- serialized_args[:at] = serialized_args[:at].to_f if serialized_args[:at]
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
- def instantiate_job(payload) # :nodoc:
321
- job = payload[:job].new(*payload[:args])
322
- job.scheduled_at = Time.at(payload[:at]) if payload.key?(:at)
323
- job.queue_name = payload[:queue]
324
- job
325
- end
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
@@ -0,0 +1,4 @@
1
+ <% module_namespacing do -%>
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ <% 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.beta3
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-02-24 00:00:00.000000000 Z
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.beta3
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.beta3
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://www.rubyonrails.org
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.5.1
104
+ rubygems_version: 2.6.4
105
105
  signing_key:
106
106
  specification_version: 4
107
107
  summary: Job framework with pluggable queues.
@@ -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