sucker_punch 2.1.2 → 3.1.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 +4 -4
- data/.github/workflows/build.yml +34 -0
- data/CHANGES.md +12 -0
- data/README.md +25 -3
- data/lib/sucker_punch/async_syntax.rb +1 -1
- data/lib/sucker_punch/job.rb +7 -2
- data/lib/sucker_punch/queue.rb +15 -2
- data/lib/sucker_punch/testing/inline.rb +3 -1
- data/lib/sucker_punch/testing.rb +1 -1
- data/lib/sucker_punch/version.rb +1 -1
- data/sucker_punch.gemspec +23 -23
- data/test/sucker_punch/async_syntax_test.rb +17 -0
- data/test/sucker_punch/job_test.rb +24 -1
- data/test/sucker_punch/queue_test.rb +8 -1
- data/test/sucker_punch_test.rb +1 -1
- metadata +8 -10
- data/.travis.yml +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0d35871de54ea5289b5ab0fe2fe36765ca86832dd94d5d698e210978f5d67523
|
4
|
+
data.tar.gz: 1ada244fd85ae889316949e711762a9f95fce4518c903f710ec1d7170a900409
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8354e67033ac6730b2ea8a3ee7d3f4a0de96787a422707c7ab1ddfff5d04808eae96bb6d220d79cfa9a1c315350cead21cb677a1c34f65034aa6d4a89b8a3d79
|
7
|
+
data.tar.gz: cc05894c3e572180f115d8100ab7c4a6992dc399400ee9a47ebbc66c79ddc987ddc81bd74ae7ff81063fa020ed5f940f08d31939ebc36e14376ccdbb7de8334d
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# This workflow uses actions that are not certified by GitHub.
|
2
|
+
# They are provided by a third-party and are governed by
|
3
|
+
# separate terms of service, privacy policy, and support
|
4
|
+
# documentation.
|
5
|
+
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
|
6
|
+
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
|
7
|
+
|
8
|
+
name: Build
|
9
|
+
|
10
|
+
on:
|
11
|
+
push:
|
12
|
+
branches: [ master ]
|
13
|
+
pull_request:
|
14
|
+
branches: [ master ]
|
15
|
+
|
16
|
+
jobs:
|
17
|
+
test:
|
18
|
+
|
19
|
+
runs-on: ubuntu-latest
|
20
|
+
strategy:
|
21
|
+
matrix:
|
22
|
+
ruby-version: ['2.4', '2.5', '2.6', '2.7', '3.0', '3.1', 'jruby', 'truffleruby', 'truffleruby-head']
|
23
|
+
|
24
|
+
steps:
|
25
|
+
- uses: actions/checkout@v2
|
26
|
+
|
27
|
+
- name: Set up Ruby ${{ matrix.ruby-version }}
|
28
|
+
uses: ruby/setup-ruby@v1
|
29
|
+
with:
|
30
|
+
ruby-version: ${{ matrix.ruby-version }}
|
31
|
+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
32
|
+
|
33
|
+
- name: Run tests
|
34
|
+
run: bundle exec rake
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
3.1.0
|
2
|
+
-------
|
3
|
+
- Add a blocking `SuckerPunch::Queue.wait` which waits for all queues to become idle.
|
4
|
+
|
5
|
+
3.0.1
|
6
|
+
-------
|
7
|
+
- Opt for keyword parsing using `ruby2_keywords` for compatibility.
|
8
|
+
|
9
|
+
3.0.0
|
10
|
+
-------
|
11
|
+
- 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/).
|
12
|
+
|
1
13
|
2.1.1
|
2
14
|
-------
|
3
15
|
- Relax versioning constraint of `concurrent-ruby` to make way for 1.1 release
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Sucker Punch
|
2
2
|
|
3
|
-
[](https://github.com/brandonhilkert/sucker_punch/actions/workflows/build.yml)
|
4
4
|
[](https://codeclimate.com/github/brandonhilkert/sucker_punch)
|
5
5
|
|
6
6
|
Sucker Punch is a single-process Ruby asynchronous processing library.
|
@@ -28,19 +28,25 @@ etc.).
|
|
28
28
|
|
29
29
|
Add this line to your application's Gemfile:
|
30
30
|
|
31
|
-
gem 'sucker_punch', '~>
|
31
|
+
gem 'sucker_punch', '~> 3.0'
|
32
32
|
|
33
33
|
And then execute:
|
34
34
|
|
35
35
|
$ bundle
|
36
36
|
|
37
|
+
You can also run (same as two steps above):
|
38
|
+
|
39
|
+
bundle add sucker_punch
|
40
|
+
|
37
41
|
Or install it yourself as:
|
38
42
|
|
39
43
|
$ gem install sucker_punch
|
40
44
|
|
41
45
|
## Backwards Compatibility
|
42
46
|
|
43
|
-
|
47
|
+
No breaking changes were introduced in version 3.x.
|
48
|
+
|
49
|
+
In version `~> 2.0.0`, the syntax to enqueue an asynchronous background job was changed from:
|
44
50
|
|
45
51
|
```ruby
|
46
52
|
LogJob.new.async.perform(...)
|
@@ -237,6 +243,15 @@ To configure something other than the default 8 sec.:
|
|
237
243
|
SuckerPunch.shutdown_timeout = 15 # # of sec. to wait before killing threads
|
238
244
|
```
|
239
245
|
|
246
|
+
#### Queue Class Methods
|
247
|
+
|
248
|
+
Sucker Punch leverages concurrent-friendly queues per-process. These class methods operating on all queues might be helpful in some edge cases.
|
249
|
+
|
250
|
+
- `SuckerPunch::Queue.stats` - Returns a hash keyed by each queue's name with total, busy, and timeout statistics for each queue's `workers` and `jobs`.
|
251
|
+
- `SuckerPunch::Queue.clear` - Calls `clear` for each queue. Susceptible to race conditions. Only use in testing.
|
252
|
+
- `SuckerPunch::Queue.shutdown_all` - Used with SuckerPunch's `at_exit` hook. Waits for all queues to be idle using the [shutdown timeout](#shutdown-timeout) configuration above.
|
253
|
+
- `SuckerPunch::Queue.wait` - Waits for all queues to become idle with no timeout.
|
254
|
+
|
240
255
|
#### Timeouts
|
241
256
|
|
242
257
|
Using `Timeout` causes persistent connections to
|
@@ -315,6 +330,13 @@ to include the backwards compatibility module in an initializer:
|
|
315
330
|
require 'sucker_punch/async_syntax'
|
316
331
|
```
|
317
332
|
|
333
|
+
## FAQ
|
334
|
+
|
335
|
+
**What is the difference between `sucker_punch` and `ActiveJob::QueueAdapters::AsyncAdapter`?**
|
336
|
+
|
337
|
+
Not much at this point. SuckerPunch existed before ActiveJob, but ultimately uses similar code under the covers. I'd recommend using `AsyncAdapter` if you're using Rails.
|
338
|
+
|
339
|
+
|
318
340
|
## Troubleshooting
|
319
341
|
|
320
342
|
### Initializers for forking servers (Unicorn, Passenger, etc.)
|
data/lib/sucker_punch/job.rb
CHANGED
@@ -35,8 +35,9 @@ module SuckerPunch
|
|
35
35
|
def perform_async(*args)
|
36
36
|
return unless SuckerPunch::RUNNING.true?
|
37
37
|
queue = SuckerPunch::Queue.find_or_create(self.to_s, num_workers, num_jobs_max)
|
38
|
-
queue.post
|
38
|
+
queue.post { __run_perform(*args) }
|
39
39
|
end
|
40
|
+
ruby2_keywords(:perform_async) if respond_to?(:ruby2_keywords, true)
|
40
41
|
|
41
42
|
def perform_in(interval, *args)
|
42
43
|
return unless SuckerPunch::RUNNING.true?
|
@@ -46,6 +47,7 @@ module SuckerPunch
|
|
46
47
|
end
|
47
48
|
job.pending?
|
48
49
|
end
|
50
|
+
ruby2_keywords(:perform_in) if respond_to?(:ruby2_keywords, true)
|
49
51
|
|
50
52
|
def workers(num)
|
51
53
|
self.num_workers = num
|
@@ -57,15 +59,18 @@ module SuckerPunch
|
|
57
59
|
|
58
60
|
def __run_perform(*args)
|
59
61
|
SuckerPunch::Counter::Busy.new(self.to_s).increment
|
62
|
+
|
60
63
|
result = self.new.perform(*args)
|
64
|
+
|
61
65
|
SuckerPunch::Counter::Processed.new(self.to_s).increment
|
62
66
|
result
|
63
67
|
rescue => ex
|
64
68
|
SuckerPunch::Counter::Failed.new(self.to_s).increment
|
65
|
-
SuckerPunch.exception_handler.call(ex, self, args)
|
69
|
+
SuckerPunch.exception_handler.call(ex, self, [*args])
|
66
70
|
ensure
|
67
71
|
SuckerPunch::Counter::Busy.new(self.to_s).decrement
|
68
72
|
end
|
73
|
+
ruby2_keywords(:__run_perform) if respond_to?(:ruby2_keywords, true)
|
69
74
|
end
|
70
75
|
end
|
71
76
|
end
|
data/lib/sucker_punch/queue.rb
CHANGED
@@ -24,7 +24,7 @@ module SuckerPunch
|
|
24
24
|
max_threads: num_workers,
|
25
25
|
max_queue: num_jobs_max || DEFAULT_MAX_QUEUE_SIZE
|
26
26
|
)
|
27
|
-
Concurrent::ThreadPoolExecutor.new(options)
|
27
|
+
Concurrent::ThreadPoolExecutor.new(**options)
|
28
28
|
end
|
29
29
|
|
30
30
|
new(name, pool)
|
@@ -108,6 +108,19 @@ module SuckerPunch
|
|
108
108
|
queues.each { |queue| queue.kill }
|
109
109
|
end
|
110
110
|
end
|
111
|
+
|
112
|
+
def self.wait
|
113
|
+
queues = all
|
114
|
+
|
115
|
+
# return if every queue is empty and workers in every queue are idle
|
116
|
+
return if queues.all? { |queue| queue.idle? }
|
117
|
+
|
118
|
+
SuckerPunch.logger.info("Pausing to allow workers to finish...")
|
119
|
+
|
120
|
+
while queues.any? { |queue| !queue.idle? }
|
121
|
+
sleep PAUSE_TIME
|
122
|
+
end
|
123
|
+
end
|
111
124
|
|
112
125
|
attr_reader :name
|
113
126
|
|
@@ -174,6 +187,7 @@ module SuckerPunch
|
|
174
187
|
end
|
175
188
|
end
|
176
189
|
end
|
190
|
+
ruby2_keywords(:post) if respond_to?(:ruby2_keywords, true)
|
177
191
|
|
178
192
|
def kill
|
179
193
|
@pool.kill
|
@@ -191,4 +205,3 @@ module SuckerPunch
|
|
191
205
|
end
|
192
206
|
end
|
193
207
|
end
|
194
|
-
|
@@ -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
|
@@ -28,10 +28,12 @@ module SuckerPunch
|
|
28
28
|
def perform_async(*args)
|
29
29
|
self.new.perform(*args)
|
30
30
|
end
|
31
|
+
ruby2_keywords(:perform_async) if respond_to?(:ruby2_keywords, true)
|
31
32
|
|
32
33
|
def perform_in(_, *args)
|
33
34
|
self.new.perform(*args)
|
34
35
|
end
|
36
|
+
ruby2_keywords(:perform_in) if respond_to?(:ruby2_keywords, true)
|
35
37
|
end
|
36
38
|
end
|
37
39
|
end
|
data/lib/sucker_punch/testing.rb
CHANGED
data/lib/sucker_punch/version.rb
CHANGED
data/sucker_punch.gemspec
CHANGED
@@ -3,32 +3,32 @@ lib = File.expand_path('../lib', __FILE__)
|
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
4
|
require 'sucker_punch/version'
|
5
5
|
|
6
|
-
Gem::Specification.new do |
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "sucker_punch"
|
8
|
+
spec.version = SuckerPunch::VERSION
|
9
|
+
spec.authors = ["Brandon Hilkert"]
|
10
|
+
spec.email = ["brandonhilkert@gmail.com"]
|
11
|
+
spec.description = %q{Asynchronous processing library for Ruby}
|
12
|
+
spec.summary = %q{Sucker Punch is a Ruby asynchronous processing using concurrent-ruby, heavily influenced by Sidekiq and girl_friday.}
|
13
|
+
spec.homepage = "https://github.com/brandonhilkert/sucker_punch"
|
14
|
+
spec.license = "MIT"
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
|
21
|
+
spec.required_ruby_version = '>= 2.0.0'
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
spec.add_development_dependency "minitest"
|
25
|
+
spec.add_development_dependency "pry"
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
if
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
spec.add_dependency "concurrent-ruby", "~> 1.0"
|
28
|
+
|
29
|
+
if spec.respond_to?(:metadata)
|
30
|
+
spec.metadata['changelog_uri'] = 'https://github.com/brandonhilkert/sucker_punch/blob/master/CHANGES.md'
|
31
|
+
spec.metadata['source_code_uri'] = 'https://github.com/brandonhilkert/sucker_punch'
|
32
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/brandonhilkert/sucker_punch/issues'
|
33
33
|
end
|
34
34
|
end
|
@@ -19,6 +19,14 @@ module SuckerPunch
|
|
19
19
|
assert_equal 1, arr.size
|
20
20
|
end
|
21
21
|
|
22
|
+
def test_perform_async_works_with_keywords
|
23
|
+
arr = Concurrent::Array.new
|
24
|
+
latch = Concurrent::CountDownLatch.new
|
25
|
+
FakeKeywordLatchJob.new.async.perform(arr: arr, latch: latch)
|
26
|
+
latch.wait(0.2)
|
27
|
+
assert_equal 1, arr.size
|
28
|
+
end
|
29
|
+
|
22
30
|
private
|
23
31
|
|
24
32
|
class FakeLatchJob
|
@@ -29,5 +37,14 @@ module SuckerPunch
|
|
29
37
|
latch.count_down
|
30
38
|
end
|
31
39
|
end
|
40
|
+
|
41
|
+
class FakeKeywordLatchJob
|
42
|
+
include SuckerPunch::Job
|
43
|
+
|
44
|
+
def perform(arr: Concurrent::Array.new, latch: Concurrent::CountDownLatch.new)
|
45
|
+
arr.push true
|
46
|
+
latch.count_down
|
47
|
+
end
|
48
|
+
end
|
32
49
|
end
|
33
50
|
end
|
@@ -122,6 +122,22 @@ module SuckerPunch
|
|
122
122
|
assert SuckerPunch::Counter::Processed.new(job_class.to_s).value == 1
|
123
123
|
end
|
124
124
|
|
125
|
+
def test_perform_async_handles_keyword_arguments
|
126
|
+
arr = Concurrent::Array.new
|
127
|
+
latch = Concurrent::CountDownLatch.new
|
128
|
+
FakeKeywordLatchJob.perform_async(arr: arr, latch: latch)
|
129
|
+
latch.wait(1)
|
130
|
+
assert_equal 1, arr.size
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_perform_in_handles_keyword_arguments
|
134
|
+
arr = Concurrent::Array.new
|
135
|
+
latch = Concurrent::CountDownLatch.new
|
136
|
+
FakeKeywordLatchJob.perform_in(0.1, arr: arr, latch: latch)
|
137
|
+
latch.wait(1)
|
138
|
+
assert_equal 1, arr.size
|
139
|
+
end
|
140
|
+
|
125
141
|
def test_failed_jobs_is_incremented_when_job_raises
|
126
142
|
job_class = Class.new(FakeErrorJob)
|
127
143
|
jobs = 3
|
@@ -142,6 +158,14 @@ module SuckerPunch
|
|
142
158
|
end
|
143
159
|
end
|
144
160
|
|
161
|
+
class FakeKeywordLatchJob
|
162
|
+
include SuckerPunch::Job
|
163
|
+
def perform(arr: Concurrent::Array.new, latch: Concurrent::CountDownLatch.new)
|
164
|
+
arr.push true
|
165
|
+
latch.count_down
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
145
169
|
class FakeBusyJob
|
146
170
|
include SuckerPunch::Job
|
147
171
|
def perform(latch1, latch2)
|
@@ -182,4 +206,3 @@ module SuckerPunch
|
|
182
206
|
end
|
183
207
|
end
|
184
208
|
end
|
185
|
-
|
@@ -70,7 +70,6 @@ module SuckerPunch
|
|
70
70
|
all_stats = SuckerPunch::Queue.stats
|
71
71
|
stats = all_stats[FakeLatchJob.to_s]
|
72
72
|
assert stats["workers"]["total"] > 0
|
73
|
-
assert stats["workers"]["busy"] == 0
|
74
73
|
assert stats["workers"]["idle"] > 0
|
75
74
|
assert stats["jobs"]["processed"] > 0
|
76
75
|
assert stats["jobs"]["failed"] == 0
|
@@ -100,6 +99,14 @@ module SuckerPunch
|
|
100
99
|
assert_equal [1, 2], arr.first
|
101
100
|
end
|
102
101
|
|
102
|
+
def test_jobs_can_be_posted_to_pool_with_keyword_args
|
103
|
+
arr = []
|
104
|
+
fake_pool = FakePool.new
|
105
|
+
queue = SuckerPunch::Queue.new "fake", fake_pool
|
106
|
+
queue.post(a: 1, b: 2) { |args| arr.push args }
|
107
|
+
assert_equal [{a: 1, b: 2}], arr.first
|
108
|
+
end
|
109
|
+
|
103
110
|
def test_kill_sends_kill_to_pool
|
104
111
|
fake_pool = FakePool.new
|
105
112
|
queue = SuckerPunch::Queue.new "fake", fake_pool
|
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.1.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: 2022-09-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -73,8 +73,8 @@ executables: []
|
|
73
73
|
extensions: []
|
74
74
|
extra_rdoc_files: []
|
75
75
|
files:
|
76
|
+
- ".github/workflows/build.yml"
|
76
77
|
- ".gitignore"
|
77
|
-
- ".travis.yml"
|
78
78
|
- CHANGES.md
|
79
79
|
- Gemfile
|
80
80
|
- LICENSE.txt
|
@@ -106,9 +106,7 @@ metadata:
|
|
106
106
|
changelog_uri: https://github.com/brandonhilkert/sucker_punch/blob/master/CHANGES.md
|
107
107
|
source_code_uri: https://github.com/brandonhilkert/sucker_punch
|
108
108
|
bug_tracker_uri: https://github.com/brandonhilkert/sucker_punch/issues
|
109
|
-
post_install_message:
|
110
|
-
Sucker Punch v2.0 introduces backwards-incompatible changes.
|
111
|
-
Please see https://github.com/brandonhilkert/sucker_punch/blob/master/CHANGES.md#200 for details.
|
109
|
+
post_install_message:
|
112
110
|
rdoc_options: []
|
113
111
|
require_paths:
|
114
112
|
- lib
|
@@ -116,15 +114,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
116
114
|
requirements:
|
117
115
|
- - ">="
|
118
116
|
- !ruby/object:Gem::Version
|
119
|
-
version:
|
117
|
+
version: 2.0.0
|
120
118
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
119
|
requirements:
|
122
120
|
- - ">="
|
123
121
|
- !ruby/object:Gem::Version
|
124
122
|
version: '0'
|
125
123
|
requirements: []
|
126
|
-
rubygems_version: 3.
|
127
|
-
signing_key:
|
124
|
+
rubygems_version: 3.1.2
|
125
|
+
signing_key:
|
128
126
|
specification_version: 4
|
129
127
|
summary: Sucker Punch is a Ruby asynchronous processing using concurrent-ruby, heavily
|
130
128
|
influenced by Sidekiq and girl_friday.
|