sucker_punch 1.6.0 → 2.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|