sidekiq-unique-jobs 4.0.17 → 4.0.18

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sidekiq-unique-jobs might be problematic. Click here for more details.

Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.dockerignore +4 -0
  3. data/.gitignore +1 -0
  4. data/.travis.yml +3 -3
  5. data/CHANGELOG.md +9 -0
  6. data/Gemfile +1 -0
  7. data/README.md +6 -3
  8. data/circle.yml +3 -6
  9. data/lib/sidekiq-unique-jobs.rb +4 -0
  10. data/lib/sidekiq_unique_jobs/cli.rb +2 -2
  11. data/lib/sidekiq_unique_jobs/lock/until_executed.rb +5 -1
  12. data/lib/sidekiq_unique_jobs/lock/while_executing.rb +3 -1
  13. data/lib/sidekiq_unique_jobs/script_mock.rb +68 -0
  14. data/lib/sidekiq_unique_jobs/scripts.rb +8 -5
  15. data/lib/sidekiq_unique_jobs/testing.rb +36 -0
  16. data/lib/sidekiq_unique_jobs/testing/sidekiq_overrides.rb +1 -1
  17. data/lib/sidekiq_unique_jobs/version.rb +1 -1
  18. data/rails_example/.env +12 -0
  19. data/rails_example/.rspec +2 -0
  20. data/rails_example/Gemfile +21 -7
  21. data/rails_example/app/channels/appearance_channel.rb +17 -0
  22. data/rails_example/app/channels/application_cable/channel.rb +4 -0
  23. data/rails_example/app/channels/application_cable/connection.rb +9 -0
  24. data/rails_example/app/channels/post_channel.rb +5 -0
  25. data/rails_example/app/controllers/work_controller.rb +8 -5
  26. data/rails_example/app/models/application_record.rb +3 -0
  27. data/rails_example/app/models/guest.rb +21 -0
  28. data/rails_example/app/models/post.rb +1 -1
  29. data/rails_example/app/workers/simple_worker.rb +5 -4
  30. data/rails_example/bin/check_or_setup_db +57 -0
  31. data/rails_example/bin/docker-setup +20 -0
  32. data/rails_example/bin/rails +1 -1
  33. data/rails_example/bin/setup +15 -10
  34. data/rails_example/bin/update +29 -0
  35. data/rails_example/cable.ru +6 -0
  36. data/rails_example/common-services.yml +50 -0
  37. data/rails_example/config/application.rb +5 -4
  38. data/rails_example/config/boot.rb +1 -1
  39. data/rails_example/config/cable.yml +9 -0
  40. data/rails_example/config/database.docker.yml +12 -0
  41. data/rails_example/config/database.yml +15 -22
  42. data/rails_example/config/environment.rb +1 -1
  43. data/rails_example/config/environments/development.rb +24 -11
  44. data/rails_example/config/environments/production.rb +24 -17
  45. data/rails_example/config/environments/test.rb +6 -6
  46. data/rails_example/config/initializers/application_controller_renderer.rb +6 -0
  47. data/rails_example/config/initializers/backtrace_silencers.rb +3 -2
  48. data/rails_example/config/initializers/cookies_serializer.rb +2 -0
  49. data/rails_example/config/initializers/new_framework_defaults.rb +23 -0
  50. data/rails_example/config/initializers/session_store.rb +1 -1
  51. data/rails_example/config/initializers/sidekiq.rb +2 -2
  52. data/rails_example/config/initializers/wrap_parameters.rb +2 -2
  53. data/rails_example/config/puma.rb +38 -0
  54. data/rails_example/config/routes.rb +2 -1
  55. data/rails_example/config/secrets.yml +3 -3
  56. data/rails_example/config/spring.rb +6 -0
  57. data/rails_example/db/migrate/20160724111322_create_posts.rb +12 -0
  58. data/rails_example/db/schema.rb +8 -4
  59. data/rails_example/dev-entrypoint.sh +55 -0
  60. data/rails_example/dev.env +12 -0
  61. data/rails_example/docker-compose.yml +90 -0
  62. data/rails_example/docker/rails.Dockerfile +27 -0
  63. data/rails_example/spec/controllers/work_controller_spec.rb +172 -0
  64. data/rails_example/spec/factories/posts.rb +8 -0
  65. data/rails_example/spec/models/post_spec.rb +4 -0
  66. data/rails_example/spec/rails_helper.rb +21 -0
  67. data/rails_example/spec/spec_helper.rb +20 -0
  68. data/rails_example/spec/support/sidekiq_meta.rb +12 -0
  69. data/rails_example/spec/workers/simple_worker_spec.rb +4 -0
  70. data/spec/jobs/my_unique_job.rb +7 -2
  71. data/spec/jobs/my_unique_job_with_filter_method.rb +17 -0
  72. data/spec/jobs/my_unique_job_with_filter_proc.rb +15 -0
  73. data/spec/jobs/notify_worker.rb +1 -1
  74. data/spec/lib/sidekiq_unique_jobs/client/middleware_spec.rb +34 -0
  75. data/spec/lib/sidekiq_unique_jobs/lock/until_and_while_executing_spec.rb +5 -1
  76. data/spec/lib/sidekiq_unique_jobs/lock/until_executed_spec.rb +5 -8
  77. data/spec/lib/sidekiq_unique_jobs/options_with_fallback_spec.rb +2 -1
  78. data/spec/lib/sidekiq_unique_jobs/script_mock_spec.rb +84 -0
  79. data/spec/lib/sidekiq_unique_jobs/scripts_spec.rb +4 -0
  80. data/spec/lib/sidekiq_unique_jobs/sidekiq_testing_enabled_spec.rb +7 -11
  81. data/spec/lib/sidekiq_unique_jobs/sidekiq_unique_ext_spec.rb +2 -1
  82. data/spec/spec_helper.rb +1 -2
  83. data/spec/support/sidekiq_meta.rb +16 -11
  84. metadata +39 -7
  85. data/rails_example/db/development.sqlite3 +0 -0
  86. data/rails_example/db/migrate/20151107231835_create_posts.rb +0 -10
  87. data/rails_example/db/test.sqlite3 +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ed1a8ffb2ff6f320e5304d4d205b8e1b0d45e98b
4
- data.tar.gz: a65deff79a35909d35259361b430211bb30fb65a
3
+ metadata.gz: 5e48826d7669ceaa951fae20cf68de7a3e9676b9
4
+ data.tar.gz: 42a874852687d4bba62d79fee6adc3119aec6ebe
5
5
  SHA512:
6
- metadata.gz: 5e88464e143a66dd207f509b1739bb7d838f08131694ce3c7dca2d0eae16d10e751c24731a7e41f1a1ea68085c9850326a16685e6c254726d778cbf89bdbf13f
7
- data.tar.gz: 90416313c946c6bbf96cfe2c6399714d2720254755db8d4505c24bfd6e4f7e9aac364487c6dc5ffba9cd64947bfd9cbee9472ff9d197606395b05d9871200165
6
+ metadata.gz: a40449737349637916e6498dd0cb2408ec9f9300d3cf0f900effdfc290f4148a1acc672fcd0e85932d9d9198d5102807b103ec768346beeebe7cfdce06fabad7
7
+ data.tar.gz: 2ac854b6034970cb0609a9f855f576939d31e3dacfa8627bc4bbbccbed8d08e98e41ca478a29103e0991da599f7fc0378a6dd34f6ba7fcaca15a43751e580b14
@@ -0,0 +1,4 @@
1
+ tmp/restart.txt
2
+ tmp/cache
3
+ tmp/pids
4
+ tmp/sockets
data/.gitignore CHANGED
@@ -11,3 +11,4 @@ gemfiles/*.lock
11
11
  *.sw?
12
12
  coverage/
13
13
  tmp/
14
+ rails_example/spec/examples.txt
@@ -13,10 +13,10 @@ script:
13
13
  rvm:
14
14
  - jruby-19mode
15
15
  - jruby-9.0.4.0
16
- - rbx-2
16
+ - jruby-9.1.2.0
17
+ - rbx-3.9
17
18
  - 2.1.8
18
19
  - 2.2.4
19
- - 2.3.0
20
20
  env: STYLE=false
21
21
  gemfile:
22
22
  - gemfiles/sidekiq_develop.gemfile
@@ -35,7 +35,7 @@ notifications:
35
35
  matrix:
36
36
  fast_finish: true
37
37
  include:
38
- - rvm: 2.3.0
38
+ - rvm: 2.3.1
39
39
  gemfile: gemfiles/sidekiq_develop.gemfile
40
40
  env: STYLE=true
41
41
  addons:
@@ -1,3 +1,12 @@
1
+ ## v4.0.18
2
+
3
+ - Allow mock_redis to be used over redis
4
+ - Fixes some locking inconsistencies
5
+
6
+ ## v4.0.17
7
+
8
+ - Always release lock in the lua script `release_lock.lua` and return success [#169](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/169)
9
+
1
10
  ## v4.0.16
2
11
 
3
12
  - Allow run & queue lock timeout (expiration) to be different [#164](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/164)
data/Gemfile CHANGED
@@ -4,6 +4,7 @@ gemspec
4
4
  gem 'appraisal', '~> 2.0.0'
5
5
  gem 'rspec-its', require: false
6
6
  gem 'rspec-wait', require: false
7
+ gem 'mock_redis', require: false
7
8
 
8
9
  platforms :mri_23 do
9
10
  gem 'fasterer', require: false
data/README.md CHANGED
@@ -43,7 +43,7 @@ Due to discoverability of the different lock types `unique` sidekiq option it wa
43
43
  sidekiq_options unique: :while_executing
44
44
  ```
45
45
 
46
- Is to make sure that a job can be scheduled any number of times but only executed a single time per argument provided to the job we call this runtime uniqueness. This is probably most useful forbackground jobs that are fast to execute. (See mhenrixon/sidekiq-unique-jobs#111 for a great example of when this would be right.) While the job is executing/performing no other jobs can be executed at the same time.
46
+ Is to make sure that a job can be scheduled any number of times but only executed a single time per argument provided to the job we call this runtime uniqueness. This is probably most useful for background jobs that are fast to execute. (See mhenrixon/sidekiq-unique-jobs#111 for a great example of when this would be right.) While the job is executing/performing no other jobs can be executed at the same time.
47
47
 
48
48
  ### Until Executing
49
49
 
@@ -212,13 +212,16 @@ Start the console with the following command `bundle exec jobs console`.
212
212
 
213
213
  ## Communication
214
214
 
215
- There is a [![Join the chat at https://gitter.im/mhenrixon/sidekiq-unique-jobs](https://badges.gitter.im/mhenrixon/sidekiq-unique-jobs.svg)](https://gitter.im/mhenri
216
- xon/sidekiq-unique-jobs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) for praise or scorn. This would be a good place to have lengthy discuss or brilliant suggestions or simply just nudge me if I forget about anything.
215
+ There is a [![Join the chat at https://gitter.im/mhenrixon/sidekiq-unique-jobs](https://badges.gitter.im/mhenrixon/sidekiq-unique-jobs.svg)](https://gitter.im/mhenrixon/sidekiq-unique-jobs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) for praise or scorn. This would be a good place to have lengthy discuss or brilliant suggestions or simply just nudge me if I forget about anything.
217
216
 
218
217
  ## Testing
219
218
 
220
219
  To enable the testing for `sidekiq-unique-jobs`, add `require 'sidekiq_unique_jobs/testing'` to your testing helper.
221
220
 
221
+ You can if you want use `gem 'mock_redis'` to prevent sidekiq unique jobs using redis.
222
+
223
+ See https://github.com/mhenrixon/sidekiq-unique-jobs/tree/master/rails_example/spec/controllers/work_controller_spec.rb for an example of how to configure sidekiq and unique jobs without redis.
224
+
222
225
  ## Contributing
223
226
 
224
227
  1. Fork it
data/circle.yml CHANGED
@@ -1,7 +1,7 @@
1
1
  machine:
2
2
  ruby:
3
3
  version:
4
- 2.3.0
4
+ 2.2.3
5
5
  java:
6
6
  version: oraclejdk8
7
7
  environment:
@@ -14,21 +14,19 @@ dependencies:
14
14
  - rvm install rbx-2
15
15
  - rvm install 2.0.0
16
16
  - rvm install 2.1.7
17
- - rvm install 2.2.3
18
17
  override:
19
18
  - rvm-exec jruby-9.0.0.0 bundle install
20
19
  - rvm-exec jruby-1.7 bundle install
21
20
  - rvm-exec rbx-2 bundle install
22
21
  - rvm-exec 2.0.0 bundle install
23
- - rvm-exec 2.1.8 bundle install
24
- - rvm-exec 2.2.4 bundle install
22
+ - rvm-exec 2.1.7 bundle install
23
+ - rvm-exec 2.2.3 bundle install
25
24
  - rvm-exec jruby-9.0.0.0 bundle exec appraisal install
26
25
  - rvm-exec jruby-1.7 bundle exec appraisal install
27
26
  - rvm-exec rbx-2 bundle exec appraisal install
28
27
  - rvm-exec 2.0.0 bundle exec appraisal install
29
28
  - rvm-exec 2.1.7 bundle exec appraisal install
30
29
  - rvm-exec 2.2.3 bundle exec appraisal install
31
- - rvm-exec 2.3.0 bundle exec appraisal install
32
30
 
33
31
  test:
34
32
  override:
@@ -38,4 +36,3 @@ test:
38
36
  - rvm-exec 2.0.0 bundle exec appraisal rspec
39
37
  - rvm-exec 2.1.7 bundle exec appraisal rspec
40
38
  - rvm-exec 2.2.3 bundle exec appraisal rspec
41
- - rvm-exec 2.3.0 bundle exec appraisal rspec
@@ -57,6 +57,10 @@ module SidekiqUniqueJobs
57
57
  worker_class
58
58
  end
59
59
 
60
+ def mocked?
61
+ config.redis_test_mode == :mock
62
+ end
63
+
60
64
  def redis_version
61
65
  @redis_version ||= connection { |c| c.info('server'.freeze)['redis_version'.freeze] }
62
66
  end
@@ -12,10 +12,10 @@ module SidekiqUniqueJobs
12
12
  option :dry_run, aliases: :d, type: :boolean, desc: 'set to false to perform deletion'
13
13
  option :count, aliases: :c, type: :numeric, default: 1000, desc: 'The max number of keys to return'
14
14
  def del(pattern)
15
- Util.del(pattern, count, dry_run)
15
+ Util.del(pattern, options[:count], options[:dry_run])
16
16
  end
17
17
 
18
- desc 'drop into a console', 'easy access to helper methods'
18
+ desc 'console', 'drop into a console with easy access to helper methods'
19
19
  def console
20
20
  puts "Use `keys '*', 1000 to display the first 1000 unique keys matching '*'"
21
21
  puts "Use `del '*', 1000, true (default) to see how many keys would be deleted for the pattern '*'"
@@ -20,7 +20,11 @@ module SidekiqUniqueJobs
20
20
  operative = false
21
21
  raise
22
22
  ensure
23
- callback.call if operative && unlock(:server)
23
+ if operative && unlock(:server)
24
+ callback.call
25
+ else
26
+ logger.fatal { "the unique_key: #{unique_key} needs to be unlocked manually" }
27
+ end
24
28
  end
25
29
 
26
30
  def unlock(scope)
@@ -17,7 +17,9 @@ module SidekiqUniqueJobs
17
17
  sleep 0.001 until locked?
18
18
 
19
19
  yield
20
-
20
+ rescue Sidekiq::Shutdown
21
+ logger.fatal { "the unique_key: #{@unique_digest} needs to be unlocked manually" }
22
+ raise
21
23
  ensure
22
24
  SidekiqUniqueJobs.connection(@redis_pool) { |c| c.del @unique_digest }
23
25
  @mutex.unlock
@@ -0,0 +1,68 @@
1
+ require 'pathname'
2
+ require 'digest/sha1'
3
+
4
+ module SidekiqUniqueJobs
5
+ module ScriptMock
6
+ module_function
7
+
8
+ extend SingleForwardable
9
+ def_delegator :SidekiqUniqueJobs, :connection
10
+
11
+ def call(file_name, redis_pool, options = {})
12
+ send(file_name, redis_pool, options)
13
+ end
14
+
15
+ def acquire_lock(redis_pool, options = {})
16
+ connection(redis_pool) do |redis|
17
+ unique_key = options[:keys][0]
18
+ job_id = options[:argv][0]
19
+ expires = options[:argv][1].to_i
20
+ stored_jid = redis.get(unique_key)
21
+
22
+ return stored_jid == job_id ? 1 : 0 if stored_jid
23
+
24
+ if redis.set(unique_key, job_id, nx: true, ex: expires)
25
+ redis.hsetnx('uniquejobs', job_id, unique_key)
26
+ return 1
27
+ else
28
+ return 0
29
+ end
30
+ end
31
+ end
32
+
33
+ def release_lock(redis_pool, options = {})
34
+ connection(redis_pool) do |redis|
35
+ unique_key = options[:keys][0]
36
+ job_id = options[:argv][0]
37
+ stored_jid = redis.get(unique_key)
38
+
39
+ return -1 unless stored_jid
40
+ return 0 unless stored_jid == job_id || stored_jid == '2'
41
+
42
+ redis.del(unique_key)
43
+ return 1
44
+ end
45
+ end
46
+
47
+ def synchronize(redis_pool, options = {})
48
+ connection(redis_pool) do |redis|
49
+ unique_key = options[:keys][0]
50
+ time = options[:argv][0].to_i
51
+ expires = options[:argv][1].to_f
52
+
53
+ stored_jid = redis.get(unique_key).to_i
54
+ return 1 if redis.set(unique_key, time + expires, nx: true, ex: expires)
55
+
56
+ stored_time = redis.get(unique_key)
57
+ if stored_time && stored_time < time
58
+ if redis.set(unique_key, time + expires, xx: true, ex: expires)
59
+ return 1
60
+ end
61
+ end
62
+
63
+ return 0
64
+ end
65
+ end
66
+ end
67
+ # rubocop:enable MethodLength
68
+ end
@@ -28,11 +28,14 @@ module SidekiqUniqueJobs
28
28
  redis.evalsha(script_shas[file_name], options)
29
29
  end
30
30
  rescue Redis::CommandError => ex
31
- raise ScriptError,
32
- "#{file_name}.lua\n\n" +
33
- ex.message + "\n\n" +
34
- script_source(file_name) +
35
- ex.backtrace.join("\n")
31
+ if ex.message == 'NOSCRIPT No matching script. Please use EVAL.'
32
+ script_shas[file_name] = nil
33
+ call(file_name, redis_pool, options)
34
+ else
35
+ raise ScriptError,
36
+ "#{file_name}.lua\n\n#{ex.message}\n\n#{script_source(file_name)}" \
37
+ "#{ex.backtrace.join("\n")}"
38
+ end
36
39
  end
37
40
 
38
41
  def script_source(file_name)
@@ -1,6 +1,42 @@
1
1
  require 'sidekiq_unique_jobs/testing/sidekiq_overrides'
2
+ require 'sidekiq_unique_jobs/script_mock'
2
3
 
3
4
  module SidekiqUniqueJobs
5
+ alias redis_version_real redis_version
6
+ def redis_version
7
+ if mocked?
8
+ '0.0'
9
+ else
10
+ redis_version_real
11
+ end
12
+ end
13
+
14
+ module Scripts
15
+ module Overrides
16
+ def self.included(base)
17
+ base.extend Testing
18
+ base.class_eval do
19
+ class << self
20
+ alias_method :call_orig, :call
21
+ alias_method :call, :call_ext
22
+ end
23
+ end
24
+ end
25
+
26
+ module Testing
27
+ def call_ext(file_name, redis_pool, options = {})
28
+ if SidekiqUniqueJobs.config.redis_test_mode == :mock
29
+ SidekiqUniqueJobs::ScriptMock.call(file_name, redis_pool, options)
30
+ else
31
+ call_orig(file_name, redis_pool, options)
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ include Overrides
38
+ end
39
+
4
40
  module Client
5
41
  class Middleware
6
42
  alias call_real call
@@ -59,7 +59,7 @@ module Sidekiq
59
59
 
60
60
  module Testing
61
61
  def clear_all_ext
62
- SidekiqUniqueJobs::Util.del('*', 1000, false)
62
+ SidekiqUniqueJobs::Util.del('*', 1000, false) unless SidekiqUniqueJobs.mocked?
63
63
  clear_all_orig
64
64
  end
65
65
  end
@@ -1,3 +1,3 @@
1
1
  module SidekiqUniqueJobs
2
- VERSION = '4.0.17'.freeze
2
+ VERSION = '4.0.18'.freeze
3
3
  end
@@ -0,0 +1,12 @@
1
+ SECRET_KEY_BASE=10167c7f7654ed02b3557b05b88ece1866a9748a2e8e064016f76cb74ddae38264f7403ba65f301d63c21d770e0ee2ac6906f245e904b3b140d12297f67e0db1
2
+ APP_DOMAIN=localhost
3
+ APP_WEB_URL=http://localhost:3000
4
+ APP_CABLE_URL=ws://localhost:28080
5
+ WEB_CONSOLE_WHITELISTED_IPS=127.0.0.1 ::1 127.0.0.0/8 ::1
6
+ DB_HOST=postgres
7
+ DB_PORT=5432
8
+ DB_USERNAME=mhenrixon
9
+ DB_PASSWORD=
10
+ DB_POOL=25
11
+ DB_TIMEOUT=5000
12
+ DB_REAPING_FREQUENCY=10
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -1,20 +1,34 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- ruby '2.2.3'
3
+ ruby '2.3.1'
4
+
5
+ gem 'rails', '~> 5.0'
6
+ gem 'puma'
7
+ gem 'json'
8
+ gem 'bigdecimal'
4
9
 
5
10
  platforms :ruby do
6
- gem 'sqlite3'
11
+ gem 'pg'
7
12
  gem 'pry-byebug'
8
13
  gem 'ruby-prof'
9
14
  end
10
15
 
11
16
  platforms :jruby do
12
17
  gem 'jruby-openssl'
13
- gem 'activerecord-jdbcsqlite3-adapter'
18
+ gem 'jdbc-pg'
19
+ gem 'activerecord-jdbc-adapter'
14
20
  end
15
21
 
16
- gem 'rails'
17
- gem 'sidekiq-unique-jobs', path: '..'
18
- gem 'sinatra'
19
- gem 'web-console', '~> 2.0'
22
+ gem 'sidekiq-unique-jobs', path: '../'
23
+ gem 'sinatra', github: 'sinatra/sinatra'
24
+
25
+ group :development do
26
+ gem 'web-console'
27
+ end
28
+
29
+ group :development, :test do
30
+ gem 'rspec-rails'
31
+ gem 'factory_girl_rails'
32
+ gem 'mock_redis'
33
+ end
20
34
 
@@ -0,0 +1,17 @@
1
+ class AppearanceChannel < ApplicationCable::Channel
2
+ def subscribed
3
+ current_user.appear
4
+ end
5
+
6
+ def unsubscribed
7
+ current_user.disappear
8
+ end
9
+
10
+ def appear(data)
11
+ current_user.appear on: data['appearing_on']
12
+ end
13
+
14
+ def away
15
+ current_user.away
16
+ end
17
+ end
@@ -0,0 +1,4 @@
1
+ module ApplicationCable
2
+ class Channel < ActionCable::Channel::Base
3
+ end
4
+ end
@@ -0,0 +1,9 @@
1
+ module ApplicationCable
2
+ class Connection < ActionCable::Connection::Base
3
+ identified_by :current_user
4
+
5
+ def connect
6
+ self.current_user = Guest.new
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ class PostChannel < ApplicationCable::Channel
2
+ def subscribed
3
+ stream_from "post_#{params[:post_id]}"
4
+ end
5
+ end
@@ -1,9 +1,12 @@
1
1
  class WorkController < ApplicationController
2
- def duplicate
3
- SimpleWorker.perform_async(1)
4
- 4.times do |_x|
5
- SpawnSimpleWorker.perform_async(1)
6
- end
2
+ def duplicate_simple
3
+ 4.times { SimpleWorker.perform_async(1) }
4
+
5
+ redirect_to '/sidekiq'
6
+ end
7
+
8
+ def duplicate_nested
9
+ 4.times { SpawnSimpleWorker.perform_async(1) }
7
10
 
8
11
  redirect_to '/sidekiq'
9
12
  end