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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4b584388415071f977d554aa3d9e8128521c08a3b4299492e349bfbc5dc865f5
4
- data.tar.gz: c77e1a8b773e712f2ec67c311e3f00bfe16fbcd8c79afc028c44b879d93c943d
3
+ metadata.gz: 0d35871de54ea5289b5ab0fe2fe36765ca86832dd94d5d698e210978f5d67523
4
+ data.tar.gz: 1ada244fd85ae889316949e711762a9f95fce4518c903f710ec1d7170a900409
5
5
  SHA512:
6
- metadata.gz: 67bb1e1d9b05432f8e868451cfbd846ea29871887ee807fc398067e10e9db2305abdf891c44e9453e822063f7599bb2f7f45535b3440287ecbdf0e7e8e688b9a
7
- data.tar.gz: e6e86dccf04e992c61369369e78e6ff7a7ab1239e9713800c7fcda1f62ed7996e451ed0053bfe1b595e6d1dd268c7853cf6aa5da8efcd8e852e2319e4a36c57c
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
- [![Build Status](https://travis-ci.org/brandonhilkert/sucker_punch.svg?branch=master)](https://travis-ci.org/brandonhilkert/sucker_punch)
3
+ [![Build](https://github.com/brandonhilkert/sucker_punch/actions/workflows/build.yml/badge.svg)](https://github.com/brandonhilkert/sucker_punch/actions/workflows/build.yml)
4
4
  [![Code Climate](https://codeclimate.com/github/brandonhilkert/sucker_punch.svg)](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', '~> 2.0'
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
- In version `~> 2.0.0`, the syntax to enqueue an asynchronous background job has changed from:
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.)
@@ -13,6 +13,6 @@ module SuckerPunch
13
13
  def perform(*args)
14
14
  @job.class.perform_async(*args)
15
15
  end
16
+ ruby2_keywords(:perform) if respond_to?(:ruby2_keywords, true)
16
17
  end
17
18
  end
18
-
@@ -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(args) { |job_args| __run_perform(*job_args) }
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
@@ -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
@@ -30,7 +30,7 @@ module SuckerPunch
30
30
  end
31
31
 
32
32
  class Queue
33
- def self.find_or_create(name, number_workers = 2, num_jobs_max = nil)
33
+ def self.find_or_create(name, _number_workers = 2, _num_jobs_max = nil)
34
34
  QUEUES.fetch_or_store(name) do
35
35
  []
36
36
  end
@@ -1,3 +1,3 @@
1
1
  module SuckerPunch
2
- VERSION = "2.1.2"
2
+ VERSION = "3.1.0"
3
3
  end
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 |gem|
7
- gem.name = "sucker_punch"
8
- gem.version = SuckerPunch::VERSION
9
- gem.authors = ["Brandon Hilkert"]
10
- gem.email = ["brandonhilkert@gmail.com"]
11
- gem.description = %q{Asynchronous processing library for Ruby}
12
- gem.summary = %q{Sucker Punch is a Ruby asynchronous processing using concurrent-ruby, heavily influenced by Sidekiq and girl_friday.}
13
- gem.homepage = "https://github.com/brandonhilkert/sucker_punch"
14
- gem.license = "MIT"
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
- gem.files = `git ls-files`.split($/)
17
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
- gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
- gem.require_paths = ["lib"]
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
- gem.post_install_message = "Sucker Punch v2.0 introduces backwards-incompatible changes.\nPlease see https://github.com/brandonhilkert/sucker_punch/blob/master/CHANGES.md#200 for details."
21
+ spec.required_ruby_version = '>= 2.0.0'
22
22
 
23
- gem.add_development_dependency "rake", "~> 10.0"
24
- gem.add_development_dependency "minitest"
25
- gem.add_development_dependency "pry"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "minitest"
25
+ spec.add_development_dependency "pry"
26
26
 
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'
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
@@ -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 = -> (ex, _, _) { raise "bad stuff" }
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: 2.1.2
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: 2019-05-16 00:00:00.000000000 Z
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: '0'
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.0.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.
data/.travis.yml DELETED
@@ -1,13 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - rbx-2
4
- - 2.0.0
5
- - 2.1.8
6
- - 2.2.4
7
- - 2.3.4
8
- - 2.4.1
9
- - ruby-head
10
-
11
- matrix:
12
- allow_failures:
13
- - rvm: rbx-2