sidekiq-unique-jobs 6.0.23 → 7.1.12

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 (127) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +922 -41
  3. data/README.md +821 -284
  4. data/lib/sidekiq_unique_jobs/batch_delete.rb +123 -0
  5. data/lib/sidekiq_unique_jobs/changelog.rb +78 -0
  6. data/lib/sidekiq_unique_jobs/cli.rb +34 -31
  7. data/lib/sidekiq_unique_jobs/config.rb +314 -0
  8. data/lib/sidekiq_unique_jobs/connection.rb +6 -5
  9. data/lib/sidekiq_unique_jobs/constants.rb +45 -24
  10. data/lib/sidekiq_unique_jobs/core_ext.rb +80 -0
  11. data/lib/sidekiq_unique_jobs/deprecation.rb +65 -0
  12. data/lib/sidekiq_unique_jobs/digests.rb +70 -102
  13. data/lib/sidekiq_unique_jobs/exceptions.rb +88 -12
  14. data/lib/sidekiq_unique_jobs/job.rb +41 -12
  15. data/lib/sidekiq_unique_jobs/json.rb +47 -0
  16. data/lib/sidekiq_unique_jobs/key.rb +93 -0
  17. data/lib/sidekiq_unique_jobs/lock/base_lock.rb +111 -82
  18. data/lib/sidekiq_unique_jobs/lock/client_validator.rb +28 -0
  19. data/lib/sidekiq_unique_jobs/lock/server_validator.rb +27 -0
  20. data/lib/sidekiq_unique_jobs/lock/until_and_while_executing.rb +40 -15
  21. data/lib/sidekiq_unique_jobs/lock/until_executed.rb +25 -7
  22. data/lib/sidekiq_unique_jobs/lock/until_executing.rb +22 -2
  23. data/lib/sidekiq_unique_jobs/lock/until_expired.rb +26 -16
  24. data/lib/sidekiq_unique_jobs/lock/validator.rb +96 -0
  25. data/lib/sidekiq_unique_jobs/lock/while_executing.rb +23 -12
  26. data/lib/sidekiq_unique_jobs/lock/while_executing_reject.rb +3 -3
  27. data/lib/sidekiq_unique_jobs/lock.rb +325 -0
  28. data/lib/sidekiq_unique_jobs/lock_args.rb +123 -0
  29. data/lib/sidekiq_unique_jobs/lock_config.rb +126 -0
  30. data/lib/sidekiq_unique_jobs/lock_digest.rb +79 -0
  31. data/lib/sidekiq_unique_jobs/lock_info.rb +68 -0
  32. data/lib/sidekiq_unique_jobs/lock_timeout.rb +62 -0
  33. data/lib/sidekiq_unique_jobs/lock_ttl.rb +77 -0
  34. data/lib/sidekiq_unique_jobs/locksmith.rb +275 -102
  35. data/lib/sidekiq_unique_jobs/logging/middleware_context.rb +44 -0
  36. data/lib/sidekiq_unique_jobs/logging.rb +188 -33
  37. data/lib/sidekiq_unique_jobs/lua/delete.lua +51 -0
  38. data/lib/sidekiq_unique_jobs/lua/delete_by_digest.lua +42 -0
  39. data/lib/sidekiq_unique_jobs/lua/delete_job_by_digest.lua +38 -0
  40. data/lib/sidekiq_unique_jobs/lua/find_digest_in_queues.lua +26 -0
  41. data/lib/sidekiq_unique_jobs/lua/lock.lua +93 -0
  42. data/lib/sidekiq_unique_jobs/lua/locked.lua +35 -0
  43. data/lib/sidekiq_unique_jobs/lua/queue.lua +87 -0
  44. data/lib/sidekiq_unique_jobs/lua/reap_orphans.lua +94 -0
  45. data/lib/sidekiq_unique_jobs/lua/shared/_common.lua +40 -0
  46. data/lib/sidekiq_unique_jobs/lua/shared/_current_time.lua +8 -0
  47. data/lib/sidekiq_unique_jobs/lua/shared/_delete_from_queue.lua +22 -0
  48. data/lib/sidekiq_unique_jobs/lua/shared/_delete_from_sorted_set.lua +18 -0
  49. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_process_set.lua +53 -0
  50. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_queues.lua +43 -0
  51. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_sorted_set.lua +24 -0
  52. data/lib/sidekiq_unique_jobs/lua/shared/_hgetall.lua +13 -0
  53. data/lib/sidekiq_unique_jobs/lua/shared/_upgrades.lua +3 -0
  54. data/lib/sidekiq_unique_jobs/lua/unlock.lua +102 -0
  55. data/lib/sidekiq_unique_jobs/lua/update_version.lua +40 -0
  56. data/lib/sidekiq_unique_jobs/lua/upgrade.lua +68 -0
  57. data/lib/sidekiq_unique_jobs/middleware/client.rb +40 -0
  58. data/lib/sidekiq_unique_jobs/middleware/server.rb +29 -0
  59. data/lib/sidekiq_unique_jobs/middleware.rb +29 -31
  60. data/lib/sidekiq_unique_jobs/normalizer.rb +4 -4
  61. data/lib/sidekiq_unique_jobs/on_conflict/log.rb +9 -5
  62. data/lib/sidekiq_unique_jobs/on_conflict/null_strategy.rb +1 -1
  63. data/lib/sidekiq_unique_jobs/on_conflict/raise.rb +1 -1
  64. data/lib/sidekiq_unique_jobs/on_conflict/reject.rb +61 -15
  65. data/lib/sidekiq_unique_jobs/on_conflict/replace.rb +54 -14
  66. data/lib/sidekiq_unique_jobs/on_conflict/reschedule.rb +16 -5
  67. data/lib/sidekiq_unique_jobs/on_conflict/strategy.rb +25 -6
  68. data/lib/sidekiq_unique_jobs/on_conflict.rb +23 -10
  69. data/lib/sidekiq_unique_jobs/options_with_fallback.rb +35 -32
  70. data/lib/sidekiq_unique_jobs/orphans/lua_reaper.rb +29 -0
  71. data/lib/sidekiq_unique_jobs/orphans/manager.rb +248 -0
  72. data/lib/sidekiq_unique_jobs/orphans/null_reaper.rb +24 -0
  73. data/lib/sidekiq_unique_jobs/orphans/observer.rb +42 -0
  74. data/lib/sidekiq_unique_jobs/orphans/reaper.rb +114 -0
  75. data/lib/sidekiq_unique_jobs/orphans/reaper_resurrector.rb +170 -0
  76. data/lib/sidekiq_unique_jobs/orphans/ruby_reaper.rb +231 -0
  77. data/lib/sidekiq_unique_jobs/redis/entity.rb +112 -0
  78. data/lib/sidekiq_unique_jobs/redis/hash.rb +56 -0
  79. data/lib/sidekiq_unique_jobs/redis/list.rb +32 -0
  80. data/lib/sidekiq_unique_jobs/redis/set.rb +32 -0
  81. data/lib/sidekiq_unique_jobs/redis/sorted_set.rb +86 -0
  82. data/lib/sidekiq_unique_jobs/redis/string.rb +49 -0
  83. data/lib/sidekiq_unique_jobs/redis.rb +11 -0
  84. data/lib/sidekiq_unique_jobs/reflectable.rb +26 -0
  85. data/lib/sidekiq_unique_jobs/reflections.rb +79 -0
  86. data/lib/sidekiq_unique_jobs/rspec/matchers/have_valid_sidekiq_options.rb +51 -0
  87. data/lib/sidekiq_unique_jobs/rspec/matchers.rb +26 -0
  88. data/lib/sidekiq_unique_jobs/script/caller.rb +127 -0
  89. data/lib/sidekiq_unique_jobs/script.rb +15 -0
  90. data/lib/sidekiq_unique_jobs/server.rb +61 -0
  91. data/lib/sidekiq_unique_jobs/sidekiq_unique_ext.rb +114 -65
  92. data/lib/sidekiq_unique_jobs/sidekiq_unique_jobs.rb +241 -35
  93. data/lib/sidekiq_unique_jobs/sidekiq_worker_methods.rb +18 -16
  94. data/lib/sidekiq_unique_jobs/testing.rb +62 -21
  95. data/lib/sidekiq_unique_jobs/timer_task.rb +78 -0
  96. data/lib/sidekiq_unique_jobs/timing.rb +58 -0
  97. data/lib/sidekiq_unique_jobs/unlockable.rb +20 -4
  98. data/lib/sidekiq_unique_jobs/update_version.rb +25 -0
  99. data/lib/sidekiq_unique_jobs/upgrade_locks.rb +155 -0
  100. data/lib/sidekiq_unique_jobs/version.rb +3 -1
  101. data/lib/sidekiq_unique_jobs/version_check.rb +23 -4
  102. data/lib/sidekiq_unique_jobs/web/helpers.rb +128 -13
  103. data/lib/sidekiq_unique_jobs/web/views/_paging.erb +4 -4
  104. data/lib/sidekiq_unique_jobs/web/views/changelogs.erb +54 -0
  105. data/lib/sidekiq_unique_jobs/web/views/lock.erb +108 -0
  106. data/lib/sidekiq_unique_jobs/web/views/locks.erb +54 -0
  107. data/lib/sidekiq_unique_jobs/web.rb +57 -27
  108. data/lib/sidekiq_unique_jobs.rb +52 -7
  109. data/lib/tasks/changelog.rake +15 -15
  110. metadata +124 -184
  111. data/lib/sidekiq_unique_jobs/client/middleware.rb +0 -56
  112. data/lib/sidekiq_unique_jobs/scripts.rb +0 -118
  113. data/lib/sidekiq_unique_jobs/server/middleware.rb +0 -46
  114. data/lib/sidekiq_unique_jobs/timeout/calculator.rb +0 -63
  115. data/lib/sidekiq_unique_jobs/timeout.rb +0 -8
  116. data/lib/sidekiq_unique_jobs/unique_args.rb +0 -150
  117. data/lib/sidekiq_unique_jobs/util.rb +0 -103
  118. data/lib/sidekiq_unique_jobs/web/views/unique_digest.erb +0 -28
  119. data/lib/sidekiq_unique_jobs/web/views/unique_digests.erb +0 -46
  120. data/redis/acquire_lock.lua +0 -21
  121. data/redis/convert_legacy_lock.lua +0 -13
  122. data/redis/delete.lua +0 -14
  123. data/redis/delete_by_digest.lua +0 -23
  124. data/redis/delete_job_by_digest.lua +0 -60
  125. data/redis/lock.lua +0 -62
  126. data/redis/release_stale_locks.lua +0 -90
  127. data/redis/unlock.lua +0 -35
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqUniqueJobs
4
+ #
5
+ # Class BatchDelete provides batch deletion of digests
6
+ #
7
+ # @author Mikael Henriksson <mikael@mhenrixon.com>
8
+ #
9
+ class BatchDelete
10
+ #
11
+ # @return [Integer] the default batch size
12
+ BATCH_SIZE = 100
13
+
14
+ #
15
+ # @return [Array<String>] Supported key suffixes
16
+ SUFFIXES = %w[
17
+ QUEUED
18
+ PRIMED
19
+ LOCKED
20
+ INFO
21
+ ].freeze
22
+
23
+ # includes "SidekiqUniqueJobs::Connection"
24
+ # @!parse include SidekiqUniqueJobs::Connection
25
+ include SidekiqUniqueJobs::Connection
26
+ # includes "SidekiqUniqueJobs::Logging"
27
+ # @!parse include SidekiqUniqueJobs::Logging
28
+ include SidekiqUniqueJobs::Logging
29
+
30
+ #
31
+ # @!attribute [r] digests
32
+ # @return [Array<String>] a collection of digests to be deleted
33
+ attr_reader :digests
34
+ #
35
+ # @!attribute [r] conn
36
+ # @return [Redis, RedisConnection, ConnectionPool] a redis connection
37
+ attr_reader :conn
38
+
39
+ #
40
+ # Executes a batch deletion of the provided digests
41
+ #
42
+ # @param [Array<String>] digests the digests to delete
43
+ # @param [Redis] conn the connection to use for deletion
44
+ #
45
+ # @return [void]
46
+ #
47
+ def self.call(digests, conn = nil)
48
+ new(digests, conn).call
49
+ end
50
+
51
+ #
52
+ # Initialize a new batch delete instance
53
+ #
54
+ # @param [Array<String>] digests the digests to delete
55
+ # @param [Redis] conn the connection to use for deletion
56
+ #
57
+ def initialize(digests, conn)
58
+ @count = 0
59
+ @digests = digests
60
+ @conn = conn
61
+ @digests ||= []
62
+ @digests.compact!
63
+ redis_version # Avoid pipelined calling redis_version and getting a future.
64
+ end
65
+
66
+ #
67
+ # Executes a batch deletion of the provided digests
68
+ # @note Just wraps batch_delete to be able to provide no connection
69
+ #
70
+ #
71
+ def call
72
+ return log_info("Nothing to delete; exiting.") if digests.none?
73
+
74
+ log_info("Deleting batch with #{digests.size} digests")
75
+ return batch_delete(conn) if conn
76
+
77
+ redis { |rcon| batch_delete(rcon) }
78
+ end
79
+
80
+ private
81
+
82
+ #
83
+ # Does the actual batch deletion
84
+ #
85
+ #
86
+ # @return [Integer] the number of deleted digests
87
+ #
88
+ def batch_delete(conn)
89
+ digests.each_slice(BATCH_SIZE) do |chunk|
90
+ conn.pipelined do
91
+ chunk.each do |digest|
92
+ del_digest(conn, digest)
93
+ conn.zrem(SidekiqUniqueJobs::DIGESTS, digest)
94
+ @count += 1
95
+ end
96
+ end
97
+ end
98
+
99
+ @count
100
+ end
101
+
102
+ def del_digest(conn, digest)
103
+ removable_keys = keys_for_digest(digest)
104
+
105
+ if VersionCheck.satisfied?(redis_version, ">= 4.0.0")
106
+ conn.unlink(*removable_keys)
107
+ else
108
+ conn.del(*removable_keys)
109
+ end
110
+ end
111
+
112
+ def keys_for_digest(digest)
113
+ [digest, "#{digest}:RUN"].each_with_object([]) do |key, digest_keys|
114
+ digest_keys.concat([key])
115
+ digest_keys.concat(SUFFIXES.map { |suffix| "#{key}:#{suffix}" })
116
+ end
117
+ end
118
+
119
+ def redis_version
120
+ @redis_version ||= SidekiqUniqueJobs.config.redis_version
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqUniqueJobs
4
+ #
5
+ # Class Changelogs provides access to the changelog entries
6
+ #
7
+ # @author Mikael Henriksson <mikael@mhenrixon.com>
8
+ #
9
+ class Changelog < Redis::SortedSet
10
+ #
11
+ # @return [Integer] the number of matches to return by default
12
+ DEFAULT_COUNT = 1_000
13
+ #
14
+ # @return [String] the default pattern to use for matching
15
+ SCAN_PATTERN = "*"
16
+
17
+ def initialize
18
+ super(CHANGELOGS)
19
+ end
20
+
21
+ #
22
+ # Adds a new changelog entry
23
+ #
24
+ # @param [String] message a descriptive message about the entry
25
+ # @param [String] digest a unique digest
26
+ # @param [String] job_id a Sidekiq JID
27
+ # @param [String] script the name of the script adding the entry
28
+ #
29
+ # @return [void]
30
+ #
31
+ def add(message:, digest:, job_id:, script:)
32
+ message = dump_json(message: message, digest: digest, job_id: job_id, script: script)
33
+ redis { |conn| conn.zadd(key, now_f, message) }
34
+ end
35
+
36
+ #
37
+ # The change log entries
38
+ #
39
+ # @param [String] pattern the pattern to match
40
+ # @param [Integer] count the number of matches to return
41
+ #
42
+ # @return [Array<Hash>] an array of entries
43
+ #
44
+ def entries(pattern: SCAN_PATTERN, count: DEFAULT_COUNT)
45
+ options = {}
46
+ options[:match] = pattern
47
+ options[:count] = count
48
+
49
+ redis do |conn|
50
+ conn.zscan_each(key, **options).to_a.map { |entry| load_json(entry[0]) }
51
+ end
52
+ end
53
+
54
+ #
55
+ # Paginate the changelog entries
56
+ #
57
+ # @param [Integer] cursor the cursor for this iteration
58
+ # @param [String] pattern "*" the pattern to match
59
+ # @param [Integer] page_size 100 the number of matches to return
60
+ #
61
+ # @return [Array<Integer, Integer, Array<Hash>] the total size, next cursor and changelog entries
62
+ #
63
+ def page(cursor: 0, pattern: "*", page_size: 100)
64
+ redis do |conn|
65
+ total_size, result = conn.multi do
66
+ conn.zcard(key)
67
+ conn.zscan(key, cursor, match: pattern, count: page_size)
68
+ end
69
+
70
+ [
71
+ total_size.to_i,
72
+ result[0].to_i, # next_cursor
73
+ result[1].map { |entry| load_json(entry[0]) }, # entries
74
+ ]
75
+ end
76
+ end
77
+ end
78
+ end
@@ -6,61 +6,64 @@ module SidekiqUniqueJobs
6
6
  #
7
7
  # Command line interface for unique jobs
8
8
  #
9
- # @author Mikael Henriksson <mikael@zoolutions.se>
9
+ # @author Mikael Henriksson <mikael@mhenrixon.com>
10
10
  #
11
11
  class Cli < Thor
12
- def self.banner(command, _namespace = nil, _subcommand = false)
13
- "jobs #{@package_name} #{command.usage}"
12
+ # :nodoc:
13
+ # rubocop:disable Style/OptionalBooleanParameter
14
+ def self.banner(command, _namespace = nil, _subcommand = false) # rubocop:disable Style/OptionalBooleanParameter
15
+ "jobs #{@package_name} #{command.usage}" # rubocop:disable ThreadSafety/InstanceVariableInClassMethod
14
16
  end
17
+ # rubocop:enable Style/OptionalBooleanParameter
15
18
 
16
- desc "keys PATTERN", "list all unique keys and their expiry time"
17
- option :count, aliases: :c, type: :numeric, default: 1000, desc: "The max number of keys to return"
18
- def keys(pattern = "*")
19
- keys = Util.keys(pattern, options[:count])
20
- say "Found #{keys.size} keys matching '#{pattern}':"
21
- print_in_columns(keys.sort) if keys.any?
19
+ desc "list PATTERN", "list all unique digests and their expiry time"
20
+ option :count, aliases: :c, type: :numeric, default: 1000, desc: "The max number of digests to return"
21
+ # :nodoc:
22
+ def list(pattern = "*")
23
+ entries = digests.entries(pattern: pattern, count: options[:count])
24
+ say "Found #{entries.size} digests matching '#{pattern}':"
25
+ print_in_columns(entries.sort) if entries.any?
22
26
  end
23
27
 
24
- desc "del PATTERN", "deletes unique keys from redis by pattern"
28
+ desc "del PATTERN", "deletes unique digests from redis by pattern"
25
29
  option :dry_run, aliases: :d, type: :boolean, desc: "set to false to perform deletion"
26
- option :count, aliases: :c, type: :numeric, default: 1000, desc: "The max number of keys to return"
30
+ option :count, aliases: :c, type: :numeric, default: 1000, desc: "The max number of digests to return"
31
+ # :nodoc:
27
32
  def del(pattern)
28
33
  max_count = options[:count]
29
34
  if options[:dry_run]
30
- keys = Util.keys(pattern, max_count)
31
- say "Would delete #{keys.size} keys matching '#{pattern}'"
35
+ result = digests.entries(pattern: pattern, count: max_count)
36
+ say "Would delete #{result.size} digests matching '#{pattern}'"
32
37
  else
33
- deleted_count = Util.del(pattern, max_count)
34
- say "Deleted #{deleted_count} keys matching '#{pattern}'"
38
+ deleted_count = digests.delete_by_pattern(pattern, count: max_count)
39
+ say "Deleted #{deleted_count} digests matching '#{pattern}'"
35
40
  end
36
41
  end
37
42
 
38
43
  desc "console", "drop into a console with easy access to helper methods"
44
+ # :nodoc:
39
45
  def console
40
- say "Use `keys '*', 1000 to display the first 1000 unique keys matching '*'"
41
- say "Use `del '*', 1000, true (default) to see how many keys would be deleted for the pattern '*'"
42
- say "Use `del '*', 1000, false to delete the first 1000 keys matching '*'"
43
- Object.include SidekiqUniqueJobs::Util
46
+ say "Use `list '*', 1000 to display the first 1000 unique digests matching '*'"
47
+ say "Use `del '*', 1000, true (default) to see how many digests would be deleted for the pattern '*'"
48
+ say "Use `del '*', 1000, false to delete the first 1000 digests matching '*'"
49
+
50
+ # Object.include SidekiqUniqueJobs::Api
44
51
  console_class.start
45
52
  end
46
53
 
47
54
  no_commands do
48
- def console_class
49
- return irb if RUBY_PLATFORM == "JAVA"
50
-
51
- pry
52
- end
53
-
54
- def irb
55
- require "irb"
56
- IRB
55
+ # :nodoc:
56
+ def digests
57
+ @digests ||= SidekiqUniqueJobs::Digests.new
57
58
  end
58
59
 
59
- def pry
60
+ # :nodoc:
61
+ def console_class
60
62
  require "pry"
61
63
  Pry
62
- rescue LoadError, NameError
63
- irb
64
+ rescue NameError, LoadError
65
+ require "irb"
66
+ IRB
64
67
  end
65
68
  end
66
69
  end
@@ -0,0 +1,314 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqUniqueJobs
4
+ # ThreadSafe config exists to be able to document the config class without errors
5
+ ThreadSafeConfig = Concurrent::MutableStruct.new("ThreadSafeConfig",
6
+ :lock_timeout,
7
+ :lock_ttl,
8
+ :enabled,
9
+ :lock_prefix,
10
+ :logger,
11
+ :locks,
12
+ :strategies,
13
+ :debug_lua,
14
+ :max_history,
15
+ :reaper,
16
+ :reaper_count,
17
+ :reaper_interval,
18
+ :reaper_timeout,
19
+ :reaper_resurrector_interval,
20
+ :reaper_resurrector_enabled,
21
+ :lock_info,
22
+ :raise_on_config_error,
23
+ :current_redis_version)
24
+
25
+ #
26
+ # Shared class for dealing with gem configuration
27
+ #
28
+ # @author Mauro Berlanda <mauro.berlanda@gmail.com>
29
+ # rubocop:disable Metrics/ClassLength
30
+ class Config < ThreadSafeConfig
31
+ #
32
+ # @return [Hash<Symbol, SidekiqUniqueJobs::Lock::BaseLock] all available queued locks
33
+ LOCKS_WHILE_ENQUEUED = {
34
+ until_executing: SidekiqUniqueJobs::Lock::UntilExecuting,
35
+ while_enqueued: SidekiqUniqueJobs::Lock::UntilExecuting,
36
+ }.freeze
37
+
38
+ #
39
+ # @return [Hash<Symbol, SidekiqUniqueJobs::Lock::BaseLock] all available fulltime locks
40
+ LOCKS_FROM_PUSH_TO_PROCESSED = {
41
+ until_completed: SidekiqUniqueJobs::Lock::UntilExecuted,
42
+ until_executed: SidekiqUniqueJobs::Lock::UntilExecuted,
43
+ until_performed: SidekiqUniqueJobs::Lock::UntilExecuted,
44
+ until_processed: SidekiqUniqueJobs::Lock::UntilExecuted,
45
+ until_and_while_executing: SidekiqUniqueJobs::Lock::UntilAndWhileExecuting,
46
+ until_successfully_completed: SidekiqUniqueJobs::Lock::UntilExecuted,
47
+ }.freeze
48
+
49
+ #
50
+ # @return [Hash<Symbol, SidekiqUniqueJobs::Lock::BaseLock] all available locks without unlock
51
+ LOCKS_WITHOUT_UNLOCK = {
52
+ until_expired: SidekiqUniqueJobs::Lock::UntilExpired,
53
+ }.freeze
54
+
55
+ #
56
+ # @return [Hash<Symbol, SidekiqUniqueJobs::Lock::BaseLock] all available runtime/client locks
57
+ LOCKS_WHEN_BUSY = {
58
+ around_perform: SidekiqUniqueJobs::Lock::WhileExecuting,
59
+ while_busy: SidekiqUniqueJobs::Lock::WhileExecuting,
60
+ while_executing: SidekiqUniqueJobs::Lock::WhileExecuting,
61
+ while_working: SidekiqUniqueJobs::Lock::WhileExecuting,
62
+ while_executing_reject: SidekiqUniqueJobs::Lock::WhileExecutingReject,
63
+ }.freeze
64
+
65
+ #
66
+ # @return [Hash<Symbol, SidekiqUniqueJobs::Lock::BaseLock] all available default locks
67
+ LOCKS =
68
+ LOCKS_WHEN_BUSY.dup
69
+ .merge(LOCKS_WHILE_ENQUEUED.dup)
70
+ .merge(LOCKS_WITHOUT_UNLOCK.dup)
71
+ .merge(LOCKS_FROM_PUSH_TO_PROCESSED.dup)
72
+ .freeze
73
+
74
+ #
75
+ # @return [Hash<Symbol, SidekiqUniqueJobs::OnConflict::Strategy] all available default strategies
76
+ STRATEGIES = {
77
+ log: SidekiqUniqueJobs::OnConflict::Log,
78
+ raise: SidekiqUniqueJobs::OnConflict::Raise,
79
+ reject: SidekiqUniqueJobs::OnConflict::Reject,
80
+ replace: SidekiqUniqueJobs::OnConflict::Replace,
81
+ reschedule: SidekiqUniqueJobs::OnConflict::Reschedule,
82
+ }.freeze
83
+
84
+ #
85
+ # @return ['uniquejobs'] by default we use this prefix
86
+ PREFIX = "uniquejobs"
87
+ #
88
+ # @return [0] by default don't wait for locks
89
+ LOCK_TIMEOUT = 0
90
+ #
91
+ # @return [nil]
92
+ LOCK_TTL = nil
93
+ #
94
+ # @return [true] by default the gem is enabled
95
+ ENABLED = true
96
+ #
97
+ # @return [false] by default we don't debug the lua scripts because it is slow
98
+ DEBUG_LUA = false
99
+ #
100
+ # @return [1_000] use a changelog history of 1_000 entries by default
101
+ MAX_HISTORY = 1_000
102
+ #
103
+ # @return [:ruby] prefer the ruby reaper by default since the lua reaper still has problems
104
+ REAPER = :ruby
105
+ #
106
+ # @return [1_000] reap 1_000 orphaned locks at a time by default
107
+ REAPER_COUNT = 1_000
108
+ #
109
+ # @return [600] reap locks every 10 minutes
110
+ REAPER_INTERVAL = 600
111
+ #
112
+ # @return [10] stop reaper after 10 seconds
113
+ REAPER_TIMEOUT = 10
114
+ #
115
+ # @return [3600] check if reaper is dead each 3600 seconds
116
+ REAPER_RESURRECTOR_INTERVAL = 3600
117
+
118
+ #
119
+ # @return [false] enable reaper resurrector
120
+ REAPER_RESURRECTOR_ENABLED = false
121
+
122
+ #
123
+ # @return [false] while useful it also adds overhead so disable lock_info by default
124
+ USE_LOCK_INFO = false
125
+ #
126
+ # @return [false] by default we don't raise validation errors for workers
127
+ RAISE_ON_CONFIG_ERROR = false
128
+ #
129
+ # @return [0.0.0] default redis version is only to avoid NoMethodError on nil
130
+ REDIS_VERSION = "0.0.0"
131
+
132
+ #
133
+ # Returns a default configuration
134
+ #
135
+ # @example
136
+ # SidekiqUniqueJobs::Config.default => <concurrent/mutable_struct/thread_safe_config SidekiqUniqueJobs::Config {
137
+ # default_lock_timeout: 0,
138
+ # default_lock_ttl: nil,
139
+ # enabled: true,
140
+ # lock_prefix: "uniquejobs",
141
+ # logger: #<Sidekiq::Logger:0x00007f81e096b0e0 @level=1 ...>,
142
+ # locks: {
143
+ # around_perform: SidekiqUniqueJobs::Lock::WhileExecuting,
144
+ # while_busy: SidekiqUniqueJobs::Lock::WhileExecuting,
145
+ # while_executing: SidekiqUniqueJobs::Lock::WhileExecuting,
146
+ # while_working: SidekiqUniqueJobs::Lock::WhileExecuting,
147
+ # while_executing_reject: SidekiqUniqueJobs::Lock::WhileExecutingReject,
148
+ # until_executing: SidekiqUniqueJobs::Lock::UntilExecuting,
149
+ # while_enqueued: SidekiqUniqueJobs::Lock::UntilExecuting,
150
+ # until_expired: SidekiqUniqueJobs::Lock::UntilExpired,
151
+ # until_completed: SidekiqUniqueJobs::Lock::UntilExecuted,
152
+ # until_executed: SidekiqUniqueJobs::Lock::UntilExecuted,
153
+ # until_performed: SidekiqUniqueJobs::Lock::UntilExecuted,
154
+ # until_processed: SidekiqUniqueJobs::Lock::UntilExecuted,
155
+ # until_and_while_executing: SidekiqUniqueJobs::Lock::UntilAndWhileExecuting,
156
+ # until_successfully_completed: SidekiqUniqueJobs::Lock::UntilExecuted
157
+ # },
158
+ # strategies: {
159
+ # log: SidekiqUniqueJobs::OnConflict::Log,
160
+ # raise: SidekiqUniqueJobs::OnConflict::Raise,
161
+ # reject: SidekiqUniqueJobs::OnConflict::Reject,
162
+ # replace: SidekiqUniqueJobs::OnConflict::Replace,
163
+ # reschedule: SidekiqUniqueJobs::OnConflict::Reschedule
164
+ # },
165
+ # debug_lua: false,
166
+ # max_history: 1000,
167
+ # reaper:: ruby,
168
+ # reaper_count: 1000,
169
+ # lock_info: false,
170
+ # raise_on_config_error: false,
171
+ # }>
172
+ #
173
+ #
174
+ # @return [SidekiqUniqueJobs::Config] a default configuration
175
+ #
176
+ def self.default # rubocop:disable Metrics/MethodLength
177
+ new(
178
+ LOCK_TIMEOUT,
179
+ LOCK_TTL,
180
+ ENABLED,
181
+ PREFIX,
182
+ Sidekiq.logger,
183
+ LOCKS,
184
+ STRATEGIES,
185
+ DEBUG_LUA,
186
+ MAX_HISTORY,
187
+ REAPER,
188
+ REAPER_COUNT,
189
+ REAPER_INTERVAL,
190
+ REAPER_TIMEOUT,
191
+ REAPER_RESURRECTOR_INTERVAL,
192
+ REAPER_RESURRECTOR_ENABLED,
193
+ USE_LOCK_INFO,
194
+ RAISE_ON_CONFIG_ERROR,
195
+ REDIS_VERSION,
196
+ )
197
+ end
198
+
199
+ #
200
+ # Set the default_lock_ttl
201
+ # @deprecated
202
+ #
203
+ # @param [Integer] obj value to set (seconds)
204
+ #
205
+ # @return [<type>] <description>
206
+ #
207
+ def default_lock_ttl=(obj)
208
+ warn "[DEPRECATION] `#{class_name}##{__method__}` is deprecated." \
209
+ " Please use `#{class_name}#lock_ttl=` instead."
210
+ self.lock_ttl = obj
211
+ end
212
+
213
+ #
214
+ # Set new value for default_lock_timeout
215
+ # @deprecated
216
+ #
217
+ # @param [Integer] obj value to set (seconds)
218
+ #
219
+ # @return [Integer]
220
+ #
221
+ def default_lock_timeout=(obj)
222
+ warn "[DEPRECATION] `#{class_name}##{__method__}` is deprecated." \
223
+ " Please use `#{class_name}#lock_timeout=` instead."
224
+ self.lock_timeout = obj
225
+ end
226
+
227
+ #
228
+ # Default lock TTL (Time To Live)
229
+ # @deprecated
230
+ #
231
+ # @return [nil, Integer] configured value or nil
232
+ #
233
+ def default_lock_ttl
234
+ warn "[DEPRECATION] `#{class_name}##{__method__}` is deprecated." \
235
+ " Please use `#{class_name}#lock_ttl` instead."
236
+ lock_ttl
237
+ end
238
+
239
+ #
240
+ # Default Lock Timeout
241
+ # @deprecated
242
+ #
243
+ #
244
+ # @return [nil, Integer] configured value or nil
245
+ #
246
+ def default_lock_timeout
247
+ warn "[DEPRECATION] `#{class_name}##{__method__}` is deprecated." \
248
+ " Please use `#{class_name}#lock_timeout` instead."
249
+ lock_timeout
250
+ end
251
+
252
+ #
253
+ # Memoized variable to get the class name
254
+ #
255
+ #
256
+ # @return [String] name of the class
257
+ #
258
+ def class_name
259
+ @class_name ||= self.class.name
260
+ end
261
+
262
+ #
263
+ # Adds a lock type to the configuration. It will raise if the lock exists already
264
+ #
265
+ # @example Add a custom lock
266
+ # add_lock(:my_lock, CustomLocks::MyLock)
267
+ #
268
+ # @raise DuplicateLock when the name already exists
269
+ #
270
+ # @param [String, Symbol] name the name of the lock
271
+ # @param [Class] klass the class describing the lock
272
+ #
273
+ # @return [void]
274
+ #
275
+ def add_lock(name, klass)
276
+ lock_sym = name.to_sym
277
+ raise DuplicateLock, ":#{name} already defined, please use another name" if locks.key?(lock_sym)
278
+
279
+ new_locks = locks.dup.merge(lock_sym => klass).freeze
280
+ self.locks = new_locks
281
+ end
282
+
283
+ #
284
+ # Adds an on_conflict strategy to the configuration.
285
+ #
286
+ # @example Add a custom strategy
287
+ # add_lock(:my_strategy, CustomStrategies::MyStrategy)
288
+ #
289
+ # @raise [DuplicateStrategy] when the name already exists
290
+ #
291
+ # @param [String] name the name of the custom strategy
292
+ # @param [Class] klass the class describing the strategy
293
+ #
294
+ def add_strategy(name, klass)
295
+ strategy_sym = name.to_sym
296
+ raise DuplicateStrategy, ":#{name} already defined, please use another name" if strategies.key?(strategy_sym)
297
+
298
+ new_strategies = strategies.dup.merge(strategy_sym => klass).freeze
299
+ self.strategies = new_strategies
300
+ end
301
+
302
+ #
303
+ # The current version of redis
304
+ #
305
+ #
306
+ # @return [String] a version string eg. `5.0.1`
307
+ #
308
+ def redis_version
309
+ self.current_redis_version = SidekiqUniqueJobs.fetch_redis_version if current_redis_version == REDIS_VERSION
310
+ current_redis_version
311
+ end
312
+ end
313
+ # rubocop:enable Metrics/ClassLength
314
+ end
@@ -3,7 +3,7 @@
3
3
  module SidekiqUniqueJobs
4
4
  # Shared module for dealing with redis connections
5
5
  #
6
- # @author Mikael Henriksson <mikael@zoolutions.se>
6
+ # @author Mikael Henriksson <mikael@mhenrixon.com>
7
7
  module Connection
8
8
  def self.included(base)
9
9
  base.send(:extend, self)
@@ -11,11 +11,12 @@ module SidekiqUniqueJobs
11
11
 
12
12
  # Creates a connection to redis
13
13
  # @return [Sidekiq::RedisConnection, ConnectionPool] a connection to redis
14
- def redis(redis_pool = nil)
15
- if redis_pool
16
- redis_pool.with { |conn| yield conn }
14
+ def redis(r_pool = nil, &block)
15
+ r_pool ||= defined?(redis_pool) ? redis_pool : r_pool
16
+ if r_pool
17
+ r_pool.with(&block)
17
18
  else
18
- Sidekiq.redis { |conn| yield conn }
19
+ Sidekiq.redis(&block)
19
20
  end
20
21
  end
21
22
  end