sucker_punch 2.0.3 → 3.0.0
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 +5 -5
- data/CHANGES.md +35 -0
- data/README.md +32 -3
- data/lib/sucker_punch.rb +10 -10
- data/lib/sucker_punch/async_syntax.rb +2 -3
- data/lib/sucker_punch/job.rb +22 -10
- data/lib/sucker_punch/queue.rb +13 -10
- data/lib/sucker_punch/testing.rb +44 -0
- data/lib/sucker_punch/testing/inline.rb +5 -5
- data/lib/sucker_punch/version.rb +1 -1
- data/sucker_punch.gemspec +7 -1
- data/test/sucker_punch/job_test.rb +5 -1
- data/test/sucker_punch/queue_test.rb +7 -2
- data/test/sucker_punch_test.rb +1 -1
- metadata +12 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e1edad787473a077f23f2fae95150cdb92e3834a12150fbd4d910fade5692516
|
4
|
+
data.tar.gz: 4ba7b7fa6cf3a1c99706a3bda43b0fd2fc291ae1825a23466b00a5b7a3354003
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a8184cafe29937ad818920951c3c760e8435b4e35a348e27629053bae1b56e2f44ab412154fb3769e6878034a9351ad5e60fb44d9385bf5bb4f2bf58cc91c71c
|
7
|
+
data.tar.gz: fde16057612312085f59a955e783007b6a0cf0b4ff7d08312963bc0c76b3be2e5d254e5eb1df1625db3c554403485e5df8a1c995ab50b71dda4e1358b00e8d23
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,38 @@
|
|
1
|
+
3.0.0
|
2
|
+
-------
|
3
|
+
- Add support for keyword arguments in ruby `>= 3.0`. More details in [release notes](https://www.ruby-lang.org/en/news/2020/12/25/ruby-3-0-0-released/).
|
4
|
+
|
5
|
+
2.1.1
|
6
|
+
-------
|
7
|
+
- Relax versioning constraint of `concurrent-ruby` to make way for 1.1 release
|
8
|
+
|
9
|
+
2.1.0
|
10
|
+
-------
|
11
|
+
- Add `max_jobs` configuration option to set the maximum number of tasks that
|
12
|
+
may be waiting in the work queue
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
class JobWithLimit
|
16
|
+
include SuckerPunch::Job
|
17
|
+
max_jobs 2
|
18
|
+
|
19
|
+
def perform(data)
|
20
|
+
# work...
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
10.times do
|
25
|
+
begin
|
26
|
+
JobWithLimit.perform_async('work') }
|
27
|
+
rescue Concurrent::RejectedExecutionError => e
|
28
|
+
# Queue maxed out, this job didn't get queued
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
2.0.4
|
33
|
+
-------
|
34
|
+
- Better initialization of variables and names to remove warnings
|
35
|
+
|
1
36
|
2.0.3
|
2
37
|
-------
|
3
38
|
- Rewrite shutdown strategy to fix bug related to premature shutdown when only
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Sucker Punch
|
2
2
|
|
3
|
-
[](https://travis-ci.org/brandonhilkert/sucker_punch)
|
4
|
+
[](https://codeclimate.com/github/brandonhilkert/sucker_punch)
|
5
5
|
|
6
6
|
Sucker Punch is a single-process Ruby asynchronous processing library.
|
7
7
|
This reduces costs
|
@@ -114,6 +114,22 @@ class LogJob
|
|
114
114
|
end
|
115
115
|
```
|
116
116
|
|
117
|
+
#### Configure the Queue Size
|
118
|
+
|
119
|
+
The default number of jobs that can be queued is unlimited. If you wish to restrict this you can set
|
120
|
+
max\_jobs as follows:
|
121
|
+
|
122
|
+
```Ruby
|
123
|
+
class LogJob
|
124
|
+
include SuckerPunch::Job
|
125
|
+
max_jobs 10
|
126
|
+
|
127
|
+
def perform(event)
|
128
|
+
Log.new(event).track
|
129
|
+
end
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
117
133
|
#### Executing Jobs in the Future
|
118
134
|
|
119
135
|
Many background processing libraries have methods to perform operations after a
|
@@ -131,7 +147,7 @@ class DataJob
|
|
131
147
|
end
|
132
148
|
|
133
149
|
DataJob.perform_async("asdf") # immediately perform asynchronously
|
134
|
-
DataJob.perform_in(60, "asdf") # `perform` will be
|
150
|
+
DataJob.perform_in(60, "asdf") # `perform` will be executed 60 sec. later
|
135
151
|
```
|
136
152
|
|
137
153
|
#### `ActiveRecord` Connection Pool Connections
|
@@ -254,6 +270,19 @@ $ rails g sucker_punch:job logger
|
|
254
270
|
would create the file `app/jobs/logger_job.rb` with a unimplemented `#perform`
|
255
271
|
method.
|
256
272
|
|
273
|
+
## Sinatra
|
274
|
+
|
275
|
+
If you're using Sucker Punch with Sinatra, you must require Sucker Punch before Sinatra:
|
276
|
+
|
277
|
+
```ruby
|
278
|
+
# app.rb
|
279
|
+
|
280
|
+
require 'sucker_punch'
|
281
|
+
require 'sinatra'
|
282
|
+
```
|
283
|
+
|
284
|
+
This will ensure Sucker Punch's `at_exit()` handler to clean up and shutdown queues does not happen **before** Sinatra *starts up* via its own `at_exit()` handler.
|
285
|
+
|
257
286
|
## Active Job
|
258
287
|
|
259
288
|
Sucker Punch has been added as an Active Job adapter in Rails 4.2.
|
data/lib/sucker_punch.rb
CHANGED
@@ -10,12 +10,12 @@ module SuckerPunch
|
|
10
10
|
RUNNING = Concurrent::AtomicBoolean.new(true)
|
11
11
|
|
12
12
|
class << self
|
13
|
-
def exception_handler
|
14
|
-
@exception_handler
|
13
|
+
def exception_handler
|
14
|
+
@exception_handler ||= method(:default_exception_handler)
|
15
15
|
end
|
16
16
|
|
17
|
-
def exception_handler
|
18
|
-
@exception_handler
|
17
|
+
def exception_handler=(handler)
|
18
|
+
@exception_handler = handler
|
19
19
|
end
|
20
20
|
|
21
21
|
def default_exception_handler(ex, klass, args)
|
@@ -26,11 +26,7 @@ module SuckerPunch
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def logger
|
29
|
-
@logger
|
30
|
-
end
|
31
|
-
|
32
|
-
def logger=(log)
|
33
|
-
@logger = (log ? log : Logger.new('/dev/null'))
|
29
|
+
@logger ||= default_logger
|
34
30
|
end
|
35
31
|
|
36
32
|
def default_logger
|
@@ -39,9 +35,13 @@ module SuckerPunch
|
|
39
35
|
l
|
40
36
|
end
|
41
37
|
|
38
|
+
def logger=(log)
|
39
|
+
@logger = (log ? log : Logger.new('/dev/null'))
|
40
|
+
end
|
41
|
+
|
42
42
|
def shutdown_timeout
|
43
43
|
# 10 seconds on heroku, minus a grace period
|
44
|
-
@shutdown_timeout
|
44
|
+
@shutdown_timeout ||= 8
|
45
45
|
end
|
46
46
|
|
47
47
|
def shutdown_timeout=(timeout)
|
data/lib/sucker_punch/job.rb
CHANGED
@@ -14,15 +14,17 @@ module SuckerPunch
|
|
14
14
|
# To trigger asynchronous job:
|
15
15
|
#
|
16
16
|
# LogJob.perform_async(1, 2, 3)
|
17
|
-
# LogJob.perform_in(60, 1, 2, 3) # `perform` will be
|
17
|
+
# LogJob.perform_in(60, 1, 2, 3) # `perform` will be executed 60 sec. later
|
18
18
|
#
|
19
19
|
# Note that perform_async is a class method, perform is an instance method.
|
20
20
|
module Job
|
21
21
|
def self.included(base)
|
22
22
|
base.extend(ClassMethods)
|
23
23
|
base.class_attribute :num_workers
|
24
|
+
base.class_attribute :num_jobs_max
|
24
25
|
|
25
26
|
base.num_workers = 2
|
27
|
+
base.num_jobs_max = nil
|
26
28
|
end
|
27
29
|
|
28
30
|
def logger
|
@@ -30,17 +32,17 @@ module SuckerPunch
|
|
30
32
|
end
|
31
33
|
|
32
34
|
module ClassMethods
|
33
|
-
def perform_async(*args)
|
35
|
+
def perform_async(*args, **kwargs)
|
34
36
|
return unless SuckerPunch::RUNNING.true?
|
35
|
-
queue = SuckerPunch::Queue.find_or_create(self.to_s, num_workers)
|
36
|
-
queue.post
|
37
|
+
queue = SuckerPunch::Queue.find_or_create(self.to_s, num_workers, num_jobs_max)
|
38
|
+
queue.post { __run_perform(*args, **kwargs) }
|
37
39
|
end
|
38
40
|
|
39
|
-
def perform_in(interval, *args)
|
41
|
+
def perform_in(interval, *args, **kwargs)
|
40
42
|
return unless SuckerPunch::RUNNING.true?
|
41
|
-
queue = SuckerPunch::Queue.find_or_create(self.to_s, num_workers)
|
43
|
+
queue = SuckerPunch::Queue.find_or_create(self.to_s, num_workers, num_jobs_max)
|
42
44
|
job = Concurrent::ScheduledTask.execute(interval.to_f, args: args, executor: queue) do
|
43
|
-
__run_perform(*args)
|
45
|
+
__run_perform(*args, **kwargs)
|
44
46
|
end
|
45
47
|
job.pending?
|
46
48
|
end
|
@@ -49,14 +51,24 @@ module SuckerPunch
|
|
49
51
|
self.num_workers = num
|
50
52
|
end
|
51
53
|
|
52
|
-
def
|
54
|
+
def max_jobs(num)
|
55
|
+
self.num_jobs_max = num
|
56
|
+
end
|
57
|
+
|
58
|
+
def __run_perform(*args, **kwargs)
|
53
59
|
SuckerPunch::Counter::Busy.new(self.to_s).increment
|
54
|
-
|
60
|
+
|
61
|
+
result = if RUBY_VERSION >= '2.5'
|
62
|
+
self.new.perform(*args, **kwargs)
|
63
|
+
else
|
64
|
+
self.new.perform(*args, *(kwargs.any? ? [kwargs] : nil))
|
65
|
+
end
|
66
|
+
|
55
67
|
SuckerPunch::Counter::Processed.new(self.to_s).increment
|
56
68
|
result
|
57
69
|
rescue => ex
|
58
70
|
SuckerPunch::Counter::Failed.new(self.to_s).increment
|
59
|
-
SuckerPunch.exception_handler.call(ex, self, args)
|
71
|
+
SuckerPunch.exception_handler.call(ex, self, [*args, **kwargs])
|
60
72
|
ensure
|
61
73
|
SuckerPunch::Counter::Busy.new(self.to_s).decrement
|
62
74
|
end
|
data/lib/sucker_punch/queue.rb
CHANGED
@@ -5,23 +5,26 @@ module SuckerPunch
|
|
5
5
|
extend Forwardable
|
6
6
|
include Concurrent::ExecutorService
|
7
7
|
|
8
|
+
DEFAULT_MAX_QUEUE_SIZE = 0 # Unlimited
|
9
|
+
|
8
10
|
DEFAULT_EXECUTOR_OPTIONS = {
|
9
11
|
min_threads: 2,
|
10
12
|
max_threads: 2,
|
11
13
|
idletime: 60, # 1 minute
|
12
|
-
max_queue: 0, # unlimited
|
13
14
|
auto_terminate: false # Let shutdown modes handle thread termination
|
14
15
|
}.freeze
|
15
16
|
|
16
17
|
QUEUES = Concurrent::Map.new
|
17
18
|
|
18
|
-
def self.find_or_create(name, num_workers = 2)
|
19
|
+
def self.find_or_create(name, num_workers = 2, num_jobs_max = nil)
|
19
20
|
pool = QUEUES.fetch_or_store(name) do
|
20
|
-
options = DEFAULT_EXECUTOR_OPTIONS
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
options = DEFAULT_EXECUTOR_OPTIONS
|
22
|
+
.merge(
|
23
|
+
min_threads: num_workers,
|
24
|
+
max_threads: num_workers,
|
25
|
+
max_queue: num_jobs_max || DEFAULT_MAX_QUEUE_SIZE
|
26
|
+
)
|
27
|
+
Concurrent::ThreadPoolExecutor.new(**options)
|
25
28
|
end
|
26
29
|
|
27
30
|
new(name, pool)
|
@@ -111,6 +114,7 @@ module SuckerPunch
|
|
111
114
|
def_delegators :@pool,
|
112
115
|
:max_length,
|
113
116
|
:min_length,
|
117
|
+
:max_queue,
|
114
118
|
:length,
|
115
119
|
:queue_length,
|
116
120
|
:wait_for_termination#,
|
@@ -161,10 +165,10 @@ module SuckerPunch
|
|
161
165
|
SuckerPunch::Counter::Failed.new(name).value
|
162
166
|
end
|
163
167
|
|
164
|
-
def post(*args, &block)
|
168
|
+
def post(*args, **kwargs, &block)
|
165
169
|
synchronize do
|
166
170
|
if @running
|
167
|
-
@pool.post(*args, &block)
|
171
|
+
@pool.post(*args, **kwargs, &block)
|
168
172
|
else
|
169
173
|
false
|
170
174
|
end
|
@@ -187,4 +191,3 @@ module SuckerPunch
|
|
187
191
|
end
|
188
192
|
end
|
189
193
|
end
|
190
|
-
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'sucker_punch'
|
2
|
+
|
3
|
+
# Include this in your tests to simulate
|
4
|
+
# a fake job queue. Jobs won't be executed
|
5
|
+
# as they normal would be the thread pool.
|
6
|
+
# They'll instead be pushed to a fake queue
|
7
|
+
# to be checked in a test environment.
|
8
|
+
#
|
9
|
+
# Include in your test_helper.rb:
|
10
|
+
#
|
11
|
+
# require 'sucker_punch/testing'
|
12
|
+
#
|
13
|
+
# In your application code:
|
14
|
+
#
|
15
|
+
# LogJob.perform_async(1, 2, 3)
|
16
|
+
#
|
17
|
+
# In your tests:
|
18
|
+
#
|
19
|
+
# LogJob.jobs => [{ "args" => [1, 2, 3]]
|
20
|
+
|
21
|
+
module SuckerPunch
|
22
|
+
module Job
|
23
|
+
def self.jobs
|
24
|
+
SuckerPunch::Queue.find_or_create(self.to_s)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.clear_all
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Queue
|
33
|
+
def self.find_or_create(name, _number_workers = 2, _num_jobs_max = nil)
|
34
|
+
QUEUES.fetch_or_store(name) do
|
35
|
+
[]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
def kill
|
41
|
+
true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -17,7 +17,7 @@ require 'sucker_punch'
|
|
17
17
|
#
|
18
18
|
# Include inline testing lib:
|
19
19
|
#
|
20
|
-
# require 'sucker_punch/testing/inline
|
20
|
+
# require 'sucker_punch/testing/inline'
|
21
21
|
#
|
22
22
|
# LogJob.perform_async(1, 2, 3) is now synchronous
|
23
23
|
# LogJob.perform_in(1, 2, 3) is now synchronous
|
@@ -25,12 +25,12 @@ require 'sucker_punch'
|
|
25
25
|
module SuckerPunch
|
26
26
|
module Job
|
27
27
|
module ClassMethods
|
28
|
-
def perform_async(*args)
|
29
|
-
self.new.perform(*args)
|
28
|
+
def perform_async(*args, **kwargs)
|
29
|
+
self.new.perform(*args, **kwargs)
|
30
30
|
end
|
31
31
|
|
32
|
-
def perform_in(_, *args)
|
33
|
-
self.new.perform(*args)
|
32
|
+
def perform_in(_, *args, **kwargs)
|
33
|
+
self.new.perform(*args, **kwargs)
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
data/lib/sucker_punch/version.rb
CHANGED
data/sucker_punch.gemspec
CHANGED
@@ -24,5 +24,11 @@ Gem::Specification.new do |gem|
|
|
24
24
|
gem.add_development_dependency "minitest"
|
25
25
|
gem.add_development_dependency "pry"
|
26
26
|
|
27
|
-
gem.add_dependency "concurrent-ruby", "~> 1.0
|
27
|
+
gem.add_dependency "concurrent-ruby", "~> 1.0"
|
28
|
+
|
29
|
+
if gem.respond_to?(:metadata)
|
30
|
+
gem.metadata['changelog_uri'] = 'https://github.com/brandonhilkert/sucker_punch/blob/master/CHANGES.md'
|
31
|
+
gem.metadata['source_code_uri'] = 'https://github.com/brandonhilkert/sucker_punch'
|
32
|
+
gem.metadata['bug_tracker_uri'] = 'https://github.com/brandonhilkert/sucker_punch/issues'
|
33
|
+
end
|
28
34
|
end
|
@@ -55,6 +55,11 @@ module SuckerPunch
|
|
55
55
|
FakeLogJob.workers(2)
|
56
56
|
end
|
57
57
|
|
58
|
+
def test_can_set_max_jobs
|
59
|
+
FakeLogJob.max_jobs(10)
|
60
|
+
assert_equal 10, FakeLogJob.num_jobs_max
|
61
|
+
end
|
62
|
+
|
58
63
|
def test_logger_is_accessible_from_instance
|
59
64
|
SuckerPunch.logger = SuckerPunch.default_logger
|
60
65
|
assert_equal SuckerPunch.logger, FakeLogJob.new.logger
|
@@ -177,4 +182,3 @@ module SuckerPunch
|
|
177
182
|
end
|
178
183
|
end
|
179
184
|
end
|
180
|
-
|
@@ -29,6 +29,11 @@ module SuckerPunch
|
|
29
29
|
assert_equal 4, queue.min_length
|
30
30
|
end
|
31
31
|
|
32
|
+
def test_queue_max_queue_can_be_set
|
33
|
+
queue = SuckerPunch::Queue.find_or_create(@queue, 4, 10)
|
34
|
+
assert_equal(10, queue.max_queue)
|
35
|
+
end
|
36
|
+
|
32
37
|
def test_same_queue_is_returned_on_subsequent_queries
|
33
38
|
queue = SuckerPunch::Queue.find_or_create(@queue)
|
34
39
|
assert_equal queue, SuckerPunch::Queue.find_or_create(@queue)
|
@@ -131,9 +136,9 @@ module SuckerPunch
|
|
131
136
|
@signals = []
|
132
137
|
end
|
133
138
|
|
134
|
-
def post(*args, &block)
|
139
|
+
def post(*args, **kwargs, &block)
|
135
140
|
if block_given?
|
136
|
-
block.call(args)
|
141
|
+
block.call(args, **kwargs)
|
137
142
|
end
|
138
143
|
end
|
139
144
|
|
data/test/sucker_punch_test.rb
CHANGED
@@ -41,7 +41,7 @@ class SuckerPunchTest < Minitest::Test
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def test_exception_handler_can_be_set
|
44
|
-
SuckerPunch.exception_handler = -> (
|
44
|
+
SuckerPunch.exception_handler = -> (_ex, _, _) { raise "bad stuff" }
|
45
45
|
assert_raises(::RuntimeError) { SuckerPunch.exception_handler.call(StandardError.new("bad"), nil, nil) }
|
46
46
|
end
|
47
47
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sucker_punch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brandon Hilkert
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-02-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -58,14 +58,14 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 1.0
|
61
|
+
version: '1.0'
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: 1.0
|
68
|
+
version: '1.0'
|
69
69
|
description: Asynchronous processing library for Ruby
|
70
70
|
email:
|
71
71
|
- brandonhilkert@gmail.com
|
@@ -89,6 +89,7 @@ files:
|
|
89
89
|
- lib/sucker_punch/job.rb
|
90
90
|
- lib/sucker_punch/queue.rb
|
91
91
|
- lib/sucker_punch/railtie.rb
|
92
|
+
- lib/sucker_punch/testing.rb
|
92
93
|
- lib/sucker_punch/testing/inline.rb
|
93
94
|
- lib/sucker_punch/version.rb
|
94
95
|
- sucker_punch.gemspec
|
@@ -101,7 +102,10 @@ files:
|
|
101
102
|
homepage: https://github.com/brandonhilkert/sucker_punch
|
102
103
|
licenses:
|
103
104
|
- MIT
|
104
|
-
metadata:
|
105
|
+
metadata:
|
106
|
+
changelog_uri: https://github.com/brandonhilkert/sucker_punch/blob/master/CHANGES.md
|
107
|
+
source_code_uri: https://github.com/brandonhilkert/sucker_punch
|
108
|
+
bug_tracker_uri: https://github.com/brandonhilkert/sucker_punch/issues
|
105
109
|
post_install_message: |-
|
106
110
|
Sucker Punch v2.0 introduces backwards-incompatible changes.
|
107
111
|
Please see https://github.com/brandonhilkert/sucker_punch/blob/master/CHANGES.md#200 for details.
|
@@ -119,9 +123,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
119
123
|
- !ruby/object:Gem::Version
|
120
124
|
version: '0'
|
121
125
|
requirements: []
|
122
|
-
|
123
|
-
|
124
|
-
signing_key:
|
126
|
+
rubygems_version: 3.1.2
|
127
|
+
signing_key:
|
125
128
|
specification_version: 4
|
126
129
|
summary: Sucker Punch is a Ruby asynchronous processing using concurrent-ruby, heavily
|
127
130
|
influenced by Sidekiq and girl_friday.
|