sucker_punch 2.1.2 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
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