sucker_punch 1.6.0 → 2.0.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +15 -3
- data/CHANGES.md +30 -0
- data/README.md +99 -96
- data/Rakefile +9 -6
- data/bin/load +23 -0
- data/bin/shutdown +20 -0
- data/lib/sucker_punch/async_syntax.rb +18 -0
- data/lib/sucker_punch/core_ext.rb +50 -8
- data/lib/sucker_punch/counter.rb +72 -0
- data/lib/sucker_punch/job.rb +53 -10
- data/lib/sucker_punch/queue.rb +159 -34
- data/lib/sucker_punch/testing/inline.rb +33 -7
- data/lib/sucker_punch/version.rb +1 -1
- data/lib/sucker_punch.rb +46 -9
- data/sucker_punch.gemspec +5 -3
- data/test/sucker_punch/async_syntax_test.rb +33 -0
- data/test/sucker_punch/counter_test.rb +83 -0
- data/test/sucker_punch/job_test.rb +157 -0
- data/test/sucker_punch/queue_test.rb +99 -0
- data/test/sucker_punch_test.rb +52 -0
- data/test/test_helper.rb +8 -0
- metadata +36 -29
- data/spec/spec_helper.rb +0 -9
- data/spec/sucker_punch/core_ext_spec.rb +0 -13
- data/spec/sucker_punch/job_spec.rb +0 -42
- data/spec/sucker_punch/queue_spec.rb +0 -69
- data/spec/sucker_punch/testing/inline_spec.rb +0 -18
- data/spec/sucker_punch_spec.rb +0 -14
data/lib/sucker_punch/job.rb
CHANGED
@@ -1,25 +1,68 @@
|
|
1
1
|
module SuckerPunch
|
2
|
+
# Include this module in your job class
|
3
|
+
# to create asynchronous jobs:
|
4
|
+
#
|
5
|
+
# class LogJob
|
6
|
+
# include SuckerPunch::Job
|
7
|
+
# workers 4
|
8
|
+
#
|
9
|
+
# def perform(*args)
|
10
|
+
# # log the things
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# To trigger asynchronous job:
|
15
|
+
#
|
16
|
+
# LogJob.perform_async(1, 2, 3)
|
17
|
+
# LogJob.perform_in(60, 1, 2, 3) # `perform` will be excuted 60 sec. later
|
18
|
+
#
|
19
|
+
# Note that perform_async is a class method, perform is an instance method.
|
2
20
|
module Job
|
3
21
|
def self.included(base)
|
4
|
-
base.send(:include, ::Celluloid)
|
5
22
|
base.extend(ClassMethods)
|
23
|
+
base.class_attribute :num_workers
|
6
24
|
|
7
|
-
base.
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
25
|
+
base.num_workers = 2
|
26
|
+
end
|
27
|
+
|
28
|
+
def logger
|
29
|
+
SuckerPunch.logger
|
12
30
|
end
|
13
31
|
|
14
32
|
module ClassMethods
|
33
|
+
def perform_async(*args)
|
34
|
+
return unless SuckerPunch::RUNNING.true?
|
35
|
+
queue = SuckerPunch::Queue.find_or_create(self.to_s, num_workers)
|
36
|
+
queue.post(args) { |args| __run_perform(*args) }
|
37
|
+
end
|
38
|
+
|
39
|
+
def perform_in(interval, *args)
|
40
|
+
return unless SuckerPunch::RUNNING.true?
|
41
|
+
queue = SuckerPunch::Queue.find_or_create(self.to_s, num_workers)
|
42
|
+
job = Concurrent::ScheduledTask.execute(interval.to_f, args: args, executor: queue) do
|
43
|
+
__run_perform(*args)
|
44
|
+
end
|
45
|
+
job.pending?
|
46
|
+
end
|
47
|
+
|
15
48
|
def workers(num)
|
16
|
-
|
49
|
+
self.num_workers = num
|
17
50
|
end
|
18
51
|
|
19
|
-
def
|
20
|
-
|
52
|
+
def __run_perform(*args)
|
53
|
+
# break if shutdown began while I was waiting in the queue
|
54
|
+
return unless SuckerPunch::RUNNING.true?
|
55
|
+
|
56
|
+
SuckerPunch::Counter::Busy.new(self.to_s).increment
|
57
|
+
result = self.new.perform(*args)
|
58
|
+
SuckerPunch::Counter::Processed.new(self.to_s).increment
|
59
|
+
result
|
60
|
+
rescue => ex
|
61
|
+
SuckerPunch::Counter::Failed.new(self.to_s).increment
|
62
|
+
SuckerPunch.exception_handler.call(ex, self, args)
|
63
|
+
ensure
|
64
|
+
SuckerPunch::Counter::Busy.new(self.to_s).decrement
|
21
65
|
end
|
22
66
|
end
|
23
|
-
|
24
67
|
end
|
25
68
|
end
|
data/lib/sucker_punch/queue.rb
CHANGED
@@ -1,57 +1,182 @@
|
|
1
|
-
require '
|
1
|
+
require 'forwardable'
|
2
2
|
|
3
3
|
module SuckerPunch
|
4
|
-
class Queue
|
5
|
-
|
4
|
+
class Queue < Concurrent::Synchronization::LockableObject
|
5
|
+
extend Forwardable
|
6
|
+
include Concurrent::ExecutorService
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
DEFAULT_EXECUTOR_OPTIONS = {
|
9
|
+
min_threads: 2,
|
10
|
+
max_threads: 2,
|
11
|
+
idletime: 60, # 1 minute
|
12
|
+
max_queue: 0, # unlimited
|
13
|
+
auto_terminate: false # Let shutdown modes handle thread termination
|
14
|
+
}.freeze
|
11
15
|
|
12
|
-
|
13
|
-
|
14
|
-
|
16
|
+
QUEUES = Concurrent::Map.new
|
17
|
+
|
18
|
+
def self.find_or_create(name, num_workers = 2)
|
19
|
+
pool = QUEUES.fetch_or_store(name) do
|
20
|
+
options = DEFAULT_EXECUTOR_OPTIONS.merge({
|
21
|
+
min_threads: num_workers,
|
22
|
+
max_threads: num_workers
|
23
|
+
})
|
24
|
+
Concurrent::ThreadPoolExecutor.new(options)
|
25
|
+
end
|
26
|
+
|
27
|
+
new(name, pool)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.all
|
31
|
+
queues = Concurrent::Array.new
|
32
|
+
QUEUES.each_pair do |name, pool|
|
33
|
+
queues.push new(name, pool)
|
34
|
+
end
|
35
|
+
queues
|
15
36
|
end
|
16
37
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
38
|
+
def self.clear
|
39
|
+
# susceptible to race conditions--only use in testing
|
40
|
+
old = all
|
41
|
+
QUEUES.clear
|
42
|
+
SuckerPunch::Counter::Busy.clear
|
43
|
+
SuckerPunch::Counter::Processed.clear
|
44
|
+
SuckerPunch::Counter::Failed.clear
|
45
|
+
old.each { |queue| queue.kill }
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.stats
|
49
|
+
queues = {}
|
50
|
+
|
51
|
+
all.each do |queue|
|
52
|
+
queues[queue.name] = {
|
53
|
+
"workers" => {
|
54
|
+
"total" => queue.total_workers,
|
55
|
+
"busy" => queue.busy_workers,
|
56
|
+
"idle" => queue.idle_workers,
|
57
|
+
},
|
58
|
+
"jobs" => {
|
59
|
+
"processed" => queue.processed_jobs,
|
60
|
+
"failed" => queue.failed_jobs,
|
61
|
+
"enqueued" => queue.enqueued_jobs,
|
62
|
+
}
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
queues
|
20
67
|
end
|
21
68
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
25
|
-
raise NotEnoughWorkers if num_workers < 1
|
69
|
+
def self.shutdown_all
|
70
|
+
if SuckerPunch::RUNNING.make_false
|
71
|
+
SuckerPunch.logger.info("Shutdown triggered...executing remaining in-process jobs")
|
26
72
|
|
27
|
-
|
28
|
-
|
29
|
-
|
73
|
+
queues = all
|
74
|
+
latch = Concurrent::CountDownLatch.new(queues.length)
|
75
|
+
|
76
|
+
queues.each do |queue|
|
77
|
+
queue.post(latch) { |l| l.count_down }
|
78
|
+
queue.shutdown
|
79
|
+
end
|
80
|
+
|
81
|
+
if latch.wait(SuckerPunch.shutdown_timeout)
|
82
|
+
SuckerPunch.logger.info("Remaining jobs have finished")
|
83
|
+
else
|
84
|
+
queues.each { |queue| queue.kill }
|
85
|
+
SuckerPunch.logger.info("Remaining jobs didn't finish in time...killing remaining jobs")
|
30
86
|
end
|
31
|
-
|
32
|
-
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
attr_reader :name
|
91
|
+
|
92
|
+
def_delegators :@pool,
|
93
|
+
:max_length,
|
94
|
+
:min_length,
|
95
|
+
:length,
|
96
|
+
:queue_length#,
|
97
|
+
#:idletime,
|
98
|
+
#:max_queue,
|
99
|
+
#:largest_length,
|
100
|
+
#:scheduled_task_count,
|
101
|
+
#:completed_task_count,
|
102
|
+
#:can_overflow?,
|
103
|
+
#:remaining_capacity,
|
104
|
+
#:running?,
|
105
|
+
#:shuttingdown?
|
106
|
+
|
107
|
+
alias_method :total_workers, :length
|
108
|
+
alias_method :enqueued_jobs, :queue_length
|
109
|
+
|
110
|
+
def initialize(name, pool)
|
111
|
+
super()
|
112
|
+
@running = true
|
113
|
+
@name, @pool = name, pool
|
114
|
+
end
|
115
|
+
|
116
|
+
def running?
|
117
|
+
synchronize { @running }
|
33
118
|
end
|
34
119
|
|
35
|
-
def
|
36
|
-
|
120
|
+
def ==(other)
|
121
|
+
pool == other.pool
|
37
122
|
end
|
38
123
|
|
39
|
-
def
|
40
|
-
|
41
|
-
|
124
|
+
def busy_workers
|
125
|
+
SuckerPunch::Counter::Busy.new(name).value
|
126
|
+
end
|
127
|
+
|
128
|
+
def idle_workers
|
129
|
+
total_workers - busy_workers
|
130
|
+
end
|
131
|
+
|
132
|
+
def processed_jobs
|
133
|
+
SuckerPunch::Counter::Processed.new(name).value
|
134
|
+
end
|
135
|
+
|
136
|
+
def failed_jobs
|
137
|
+
SuckerPunch::Counter::Failed.new(name).value
|
138
|
+
end
|
139
|
+
|
140
|
+
def post(*args, &block)
|
141
|
+
synchronize do
|
142
|
+
if @running
|
143
|
+
@pool.post(*args, &block)
|
144
|
+
else
|
145
|
+
false
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def kill
|
151
|
+
if can_initiate_shutdown?
|
152
|
+
@pool.kill
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def shutdown
|
157
|
+
if can_initiate_shutdown?
|
158
|
+
@pool.shutdown
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
protected
|
163
|
+
|
164
|
+
def pool
|
165
|
+
@pool
|
42
166
|
end
|
43
167
|
|
44
168
|
private
|
45
169
|
|
46
|
-
def
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
170
|
+
def can_initiate_shutdown?
|
171
|
+
synchronize do
|
172
|
+
if @running
|
173
|
+
@running = false
|
174
|
+
true
|
175
|
+
else
|
176
|
+
false
|
177
|
+
end
|
51
178
|
end
|
52
|
-
pool.run!
|
53
179
|
end
|
54
180
|
end
|
55
181
|
end
|
56
182
|
|
57
|
-
|
@@ -1,11 +1,37 @@
|
|
1
1
|
require 'sucker_punch'
|
2
|
-
require 'celluloid/proxy/abstract'
|
3
|
-
require 'celluloid/proxy/sync'
|
4
|
-
require 'celluloid/proxy/actor'
|
5
2
|
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
# Include this in your tests to simulate
|
4
|
+
# immediate execution of your asynchronous jobs
|
5
|
+
#
|
6
|
+
# class LogJob
|
7
|
+
# include SuckerPunch::Job
|
8
|
+
#
|
9
|
+
# def perform(*args)
|
10
|
+
# # log the things
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# To trigger asynchronous job:
|
15
|
+
#
|
16
|
+
# LogJob.perform_async(1, 2, 3)
|
17
|
+
#
|
18
|
+
# Include inline testing lib:
|
19
|
+
#
|
20
|
+
# require 'sucker_punch/testing/inline"
|
21
|
+
#
|
22
|
+
# LogJob.perform_async(1, 2, 3) is now synchronous
|
23
|
+
# LogJob.perform_in(1, 2, 3) is now synchronous
|
24
|
+
#
|
25
|
+
module SuckerPunch
|
26
|
+
module Job
|
27
|
+
module ClassMethods
|
28
|
+
def perform_async(*args)
|
29
|
+
self.new.perform(*args)
|
30
|
+
end
|
31
|
+
|
32
|
+
def perform_in(_, *args)
|
33
|
+
self.new.perform(*args)
|
34
|
+
end
|
35
|
+
end
|
9
36
|
end
|
10
37
|
end
|
11
|
-
|
data/lib/sucker_punch/version.rb
CHANGED
data/lib/sucker_punch.rb
CHANGED
@@ -1,21 +1,58 @@
|
|
1
|
-
require '
|
1
|
+
require 'concurrent'
|
2
2
|
require 'sucker_punch/core_ext'
|
3
|
+
require 'sucker_punch/counter'
|
3
4
|
require 'sucker_punch/job'
|
4
5
|
require 'sucker_punch/queue'
|
5
6
|
require 'sucker_punch/version'
|
7
|
+
require 'logger'
|
6
8
|
|
7
9
|
module SuckerPunch
|
8
|
-
|
9
|
-
Celluloid.logger
|
10
|
-
end
|
10
|
+
RUNNING = Concurrent::AtomicBoolean.new(true)
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
class << self
|
13
|
+
def exception_handler=(handler)
|
14
|
+
@exception_handler = handler
|
15
|
+
end
|
16
|
+
|
17
|
+
def exception_handler
|
18
|
+
@exception_handler || method(:default_exception_handler)
|
19
|
+
end
|
20
|
+
|
21
|
+
def default_exception_handler(ex, klass, args)
|
22
|
+
msg = "Sucker Punch job error for class: '#{klass}' args: #{args}\n"
|
23
|
+
msg += "#{ex.class} #{ex}\n"
|
24
|
+
msg += "#{ex.backtrace.nil? ? '' : ex.backtrace.join("\n")}"
|
25
|
+
logger.error msg
|
26
|
+
end
|
27
|
+
|
28
|
+
def logger
|
29
|
+
@logger || default_logger
|
30
|
+
end
|
15
31
|
|
16
|
-
|
17
|
-
|
32
|
+
def logger=(log)
|
33
|
+
@logger = (log ? log : Logger.new('/dev/null'))
|
34
|
+
end
|
35
|
+
|
36
|
+
def default_logger
|
37
|
+
l = Logger.new(STDOUT)
|
38
|
+
l.level = Logger::INFO
|
39
|
+
l
|
40
|
+
end
|
41
|
+
|
42
|
+
def shutdown_timeout
|
43
|
+
# 10 seconds on heroku, minus a grace period
|
44
|
+
@shutdown_timeout || 8
|
45
|
+
end
|
46
|
+
|
47
|
+
def shutdown_timeout=(timeout)
|
48
|
+
@shutdown_timeout = timeout
|
49
|
+
end
|
18
50
|
end
|
19
51
|
end
|
20
52
|
|
53
|
+
at_exit do
|
54
|
+
SuckerPunch::Queue.shutdown_all
|
55
|
+
SuckerPunch.logger.info("All is quiet...byebye")
|
56
|
+
end
|
57
|
+
|
21
58
|
require 'sucker_punch/railtie' if defined?(::Rails)
|
data/sucker_punch.gemspec
CHANGED
@@ -18,9 +18,11 @@ Gem::Specification.new do |gem|
|
|
18
18
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
19
|
gem.require_paths = ["lib"]
|
20
20
|
|
21
|
-
gem.
|
22
|
-
|
21
|
+
gem.post_install_message = "Sucker Punch Version 2.0 introduces backwards-incompatible changes. Please see https://github.com/brandonhilkert/sucker_punch/blob/master/CHANGES.md#20 for details."
|
22
|
+
|
23
|
+
gem.add_development_dependency "rake", "~> 10.0"
|
24
|
+
gem.add_development_dependency "minitest"
|
23
25
|
gem.add_development_dependency "pry"
|
24
26
|
|
25
|
-
gem.add_dependency "
|
27
|
+
gem.add_dependency "concurrent-ruby", "~> 1.0.0"
|
26
28
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module SuckerPunch
|
4
|
+
class AsyncSyntaxTest < Minitest::Test
|
5
|
+
def setup
|
6
|
+
require 'sucker_punch/async_syntax'
|
7
|
+
SuckerPunch::Queue.clear
|
8
|
+
end
|
9
|
+
|
10
|
+
def teardown
|
11
|
+
SuckerPunch::Queue.clear
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_perform_async_runs_job_asynchronously
|
15
|
+
arr = Concurrent::Array.new
|
16
|
+
latch = Concurrent::CountDownLatch.new
|
17
|
+
FakeLatchJob.new.async.perform(arr, latch)
|
18
|
+
latch.wait(0.2)
|
19
|
+
assert_equal 1, arr.size
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
class FakeLatchJob
|
25
|
+
include SuckerPunch::Job
|
26
|
+
|
27
|
+
def perform(arr, latch)
|
28
|
+
arr.push true
|
29
|
+
latch.count_down
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module SuckerPunch
|
4
|
+
class CounterTest < Minitest::Test
|
5
|
+
def setup
|
6
|
+
@queue = "fake"
|
7
|
+
SuckerPunch::Queue.clear
|
8
|
+
end
|
9
|
+
|
10
|
+
def teardown
|
11
|
+
SuckerPunch::Queue.clear
|
12
|
+
SuckerPunch::Counter::Busy.clear
|
13
|
+
SuckerPunch::Counter::Failed.clear
|
14
|
+
SuckerPunch::Counter::Processed.clear
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_busy_counter_can_be_cleared
|
18
|
+
SuckerPunch::Counter::Busy.new(@queue).increment
|
19
|
+
SuckerPunch::Counter::Busy.clear
|
20
|
+
assert_equal 0, SuckerPunch::Counter::Busy.new(@queue).value
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_same_busy_counter_is_returned
|
24
|
+
c = SuckerPunch::Counter::Busy.new(@queue)
|
25
|
+
assert_equal c.counter, SuckerPunch::Counter::Busy::COUNTER[@queue]
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_busy_counter_default_is_0
|
29
|
+
c = SuckerPunch::Counter::Busy.new(@queue)
|
30
|
+
assert_equal 0, c.value
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_busy_counter_supports_incrementing_and_decrementing
|
34
|
+
c = SuckerPunch::Counter::Busy.new(@queue)
|
35
|
+
assert_equal 1, c.increment
|
36
|
+
assert_equal 0, c.decrement
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_processed_counter_can_be_cleared
|
40
|
+
SuckerPunch::Counter::Processed.new(@queue).increment
|
41
|
+
SuckerPunch::Counter::Processed.clear
|
42
|
+
assert_equal 0, SuckerPunch::Counter::Processed.new(@queue).value
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_same_counter_is_returned_for_processed
|
46
|
+
c = SuckerPunch::Counter::Processed.new(@queue)
|
47
|
+
assert_equal c.counter, SuckerPunch::Counter::Processed::COUNTER[@queue]
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_processed_counter_default_is_0
|
51
|
+
c = SuckerPunch::Counter::Processed.new(@queue)
|
52
|
+
assert_equal 0, c.value
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_processed_counter_supports_incrementing_and_decrementing
|
56
|
+
c = SuckerPunch::Counter::Processed.new(@queue)
|
57
|
+
assert_equal 1, c.increment
|
58
|
+
assert_equal 0, c.decrement
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_failed_counter_can_be_cleared
|
62
|
+
SuckerPunch::Counter::Failed.new(@queue).increment
|
63
|
+
SuckerPunch::Counter::Failed.clear
|
64
|
+
assert_equal 0, SuckerPunch::Counter::Failed.new(@queue).value
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_same_counter_is_returned_for_failed
|
68
|
+
c = SuckerPunch::Counter::Failed.new(@queue)
|
69
|
+
assert_equal c.counter, SuckerPunch::Counter::Failed::COUNTER[@queue]
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_failed_counter_default_is_0
|
73
|
+
c = SuckerPunch::Counter::Failed.new(@queue)
|
74
|
+
assert_equal 0, c.value
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_failed_counter_supports_incrementing_and_decrementing
|
78
|
+
c = SuckerPunch::Counter::Failed.new(@queue)
|
79
|
+
assert_equal 1, c.increment
|
80
|
+
assert_equal 0, c.decrement
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module SuckerPunch
|
4
|
+
class JobTest < Minitest::Test
|
5
|
+
def setup
|
6
|
+
SuckerPunch::Queue.clear
|
7
|
+
end
|
8
|
+
|
9
|
+
def teardown
|
10
|
+
SuckerPunch::Queue.clear
|
11
|
+
SuckerPunch::RUNNING.make_true
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_perform_async_runs_job_asynchronously
|
15
|
+
arr = Concurrent::Array.new
|
16
|
+
latch = Concurrent::CountDownLatch.new
|
17
|
+
FakeLatchJob.perform_async(arr, latch)
|
18
|
+
latch.wait(0.2)
|
19
|
+
assert_equal 1, arr.size
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_job_isnt_run_with_perform_async_if_sucker_punch_is_shutdown
|
23
|
+
SuckerPunch::RUNNING.make_false
|
24
|
+
arr = Concurrent::Array.new
|
25
|
+
latch = Concurrent::CountDownLatch.new
|
26
|
+
FakeLatchJob.perform_async(arr, latch)
|
27
|
+
latch.wait(0.2)
|
28
|
+
assert_equal 0, arr.size
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_perform_in_runs_job_in_future
|
32
|
+
arr = Concurrent::Array.new
|
33
|
+
latch = Concurrent::CountDownLatch.new
|
34
|
+
FakeLatchJob.perform_in(0.1, arr, latch)
|
35
|
+
latch.wait(0.2)
|
36
|
+
assert_equal 1, arr.size
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_job_isnt_run_with_perform_in_if_sucker_punch_is_shutdown
|
40
|
+
SuckerPunch::RUNNING.make_false
|
41
|
+
arr = Concurrent::Array.new
|
42
|
+
latch = Concurrent::CountDownLatch.new
|
43
|
+
FakeLatchJob.perform_in(0.1, arr, latch)
|
44
|
+
latch.wait(0.2)
|
45
|
+
assert_equal 0, arr.size
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_default_workers_is_2
|
49
|
+
assert_equal 2, FakeLogJob.num_workers
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_can_set_workers_count
|
53
|
+
FakeLogJob.workers(4)
|
54
|
+
assert_equal 4, FakeLogJob.num_workers
|
55
|
+
FakeLogJob.workers(2)
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_logger_is_accessible_from_instance
|
59
|
+
SuckerPunch.logger = SuckerPunch.default_logger
|
60
|
+
assert_equal SuckerPunch.logger, FakeLogJob.new.logger
|
61
|
+
SuckerPunch.logger = nil
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_num_workers_can_be_set_by_worker_method
|
65
|
+
assert_equal 4, FakeWorkerJob.num_workers
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_num_workers_is_set_when_enqueueing_job_immediately
|
69
|
+
FakeWorkerJob.perform_async
|
70
|
+
pool = SuckerPunch::Queue::QUEUES[FakeWorkerJob.to_s]
|
71
|
+
assert_equal 4, pool.max_length
|
72
|
+
assert_equal 4, pool.min_length
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_num_workers_is_set_when_enqueueing_job_in_future
|
76
|
+
FakeWorkerJob.perform_in(30)
|
77
|
+
pool = SuckerPunch::Queue::QUEUES[FakeWorkerJob.to_s]
|
78
|
+
assert_equal 4, pool.max_length
|
79
|
+
assert_equal 4, pool.min_length
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_run_perform_delegates_to_instance_perform
|
83
|
+
assert_equal "fake", FakeLogJob.__run_perform
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_busy_workers_is_incremented_during_job_execution
|
87
|
+
FakeSlowJob.perform_async
|
88
|
+
sleep 0.1
|
89
|
+
assert SuckerPunch::Counter::Busy.new(FakeSlowJob.to_s).value > 0
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_processed_jobs_is_incremented_on_successful_completion
|
93
|
+
latch = Concurrent::CountDownLatch.new
|
94
|
+
2.times{ FakeLogJob.perform_async }
|
95
|
+
queue = SuckerPunch::Queue.find_or_create(FakeLogJob.to_s)
|
96
|
+
queue.post { latch.count_down }
|
97
|
+
latch.wait(0.2)
|
98
|
+
assert SuckerPunch::Counter::Processed.new(FakeLogJob.to_s).value > 0
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_processed_jobs_is_incremented_when_enqueued_with_perform_in
|
102
|
+
latch = Concurrent::CountDownLatch.new
|
103
|
+
FakeLatchJob.perform_in(0.1, [], latch)
|
104
|
+
latch.wait(0.2)
|
105
|
+
assert SuckerPunch::Counter::Processed.new(FakeLatchJob.to_s).value > 0
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_failed_jobs_is_incremented_when_job_raises
|
109
|
+
latch = Concurrent::CountDownLatch.new
|
110
|
+
2.times{ FakeErrorJob.perform_async }
|
111
|
+
queue = SuckerPunch::Queue.find_or_create(FakeErrorJob.to_s)
|
112
|
+
queue.post { latch.count_down }
|
113
|
+
latch.wait(0.2)
|
114
|
+
assert SuckerPunch::Counter::Failed.new(FakeErrorJob.to_s).value > 0
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
class FakeLatchJob
|
120
|
+
include SuckerPunch::Job
|
121
|
+
def perform(arr, latch)
|
122
|
+
arr.push true
|
123
|
+
latch.count_down
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
class FakeSlowJob
|
128
|
+
include SuckerPunch::Job
|
129
|
+
def perform
|
130
|
+
sleep 0.3
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
class FakeLogJob
|
135
|
+
include SuckerPunch::Job
|
136
|
+
def perform
|
137
|
+
"fake"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
class FakeWorkerJob
|
142
|
+
include SuckerPunch::Job
|
143
|
+
workers 4
|
144
|
+
def perform
|
145
|
+
"fake"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
class FakeErrorJob
|
150
|
+
include SuckerPunch::Job
|
151
|
+
def perform
|
152
|
+
raise "error"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|