sidekiq-unique-jobs 6.0.25 → 7.1.33

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1157 -41
  3. data/README.md +825 -291
  4. data/lib/sidekiq_unique_jobs/batch_delete.rb +124 -0
  5. data/lib/sidekiq_unique_jobs/changelog.rb +78 -0
  6. data/lib/sidekiq_unique_jobs/cli.rb +57 -29
  7. data/lib/sidekiq_unique_jobs/config.rb +319 -0
  8. data/lib/sidekiq_unique_jobs/connection.rb +6 -5
  9. data/lib/sidekiq_unique_jobs/constants.rb +46 -25
  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/expiring_digests.rb +14 -0
  15. data/lib/sidekiq_unique_jobs/job.rb +46 -12
  16. data/lib/sidekiq_unique_jobs/json.rb +47 -0
  17. data/lib/sidekiq_unique_jobs/key.rb +98 -0
  18. data/lib/sidekiq_unique_jobs/lock/base_lock.rb +111 -82
  19. data/lib/sidekiq_unique_jobs/lock/client_validator.rb +28 -0
  20. data/lib/sidekiq_unique_jobs/lock/server_validator.rb +27 -0
  21. data/lib/sidekiq_unique_jobs/lock/until_and_while_executing.rb +40 -15
  22. data/lib/sidekiq_unique_jobs/lock/until_executed.rb +30 -7
  23. data/lib/sidekiq_unique_jobs/lock/until_executing.rb +26 -2
  24. data/lib/sidekiq_unique_jobs/lock/until_expired.rb +27 -15
  25. data/lib/sidekiq_unique_jobs/lock/validator.rb +96 -0
  26. data/lib/sidekiq_unique_jobs/lock/while_executing.rb +26 -12
  27. data/lib/sidekiq_unique_jobs/lock/while_executing_reject.rb +3 -3
  28. data/lib/sidekiq_unique_jobs/lock.rb +342 -0
  29. data/lib/sidekiq_unique_jobs/lock_args.rb +127 -0
  30. data/lib/sidekiq_unique_jobs/lock_config.rb +126 -0
  31. data/lib/sidekiq_unique_jobs/lock_digest.rb +79 -0
  32. data/lib/sidekiq_unique_jobs/lock_info.rb +68 -0
  33. data/lib/sidekiq_unique_jobs/lock_timeout.rb +62 -0
  34. data/lib/sidekiq_unique_jobs/lock_ttl.rb +77 -0
  35. data/lib/sidekiq_unique_jobs/lock_type.rb +37 -0
  36. data/lib/sidekiq_unique_jobs/locksmith.rb +305 -101
  37. data/lib/sidekiq_unique_jobs/logging/middleware_context.rb +44 -0
  38. data/lib/sidekiq_unique_jobs/logging.rb +202 -33
  39. data/lib/sidekiq_unique_jobs/lua/delete.lua +51 -0
  40. data/lib/sidekiq_unique_jobs/lua/delete_by_digest.lua +42 -0
  41. data/lib/sidekiq_unique_jobs/lua/delete_job_by_digest.lua +38 -0
  42. data/lib/sidekiq_unique_jobs/lua/find_digest_in_queues.lua +26 -0
  43. data/lib/sidekiq_unique_jobs/lua/lock.lua +99 -0
  44. data/lib/sidekiq_unique_jobs/lua/lock_until_expired.lua +92 -0
  45. data/lib/sidekiq_unique_jobs/lua/locked.lua +35 -0
  46. data/lib/sidekiq_unique_jobs/lua/queue.lua +87 -0
  47. data/lib/sidekiq_unique_jobs/lua/reap_orphans.lua +122 -0
  48. data/lib/sidekiq_unique_jobs/lua/shared/_common.lua +40 -0
  49. data/lib/sidekiq_unique_jobs/lua/shared/_current_time.lua +8 -0
  50. data/lib/sidekiq_unique_jobs/lua/shared/_delete_from_queue.lua +22 -0
  51. data/lib/sidekiq_unique_jobs/lua/shared/_delete_from_sorted_set.lua +18 -0
  52. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_process_set.lua +53 -0
  53. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_queues.lua +43 -0
  54. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_sorted_set.lua +24 -0
  55. data/lib/sidekiq_unique_jobs/lua/shared/_hgetall.lua +13 -0
  56. data/lib/sidekiq_unique_jobs/lua/shared/_upgrades.lua +3 -0
  57. data/lib/sidekiq_unique_jobs/lua/unlock.lua +107 -0
  58. data/lib/sidekiq_unique_jobs/lua/update_version.lua +40 -0
  59. data/lib/sidekiq_unique_jobs/lua/upgrade.lua +68 -0
  60. data/lib/sidekiq_unique_jobs/middleware/client.rb +42 -0
  61. data/lib/sidekiq_unique_jobs/middleware/server.rb +31 -0
  62. data/lib/sidekiq_unique_jobs/middleware.rb +29 -43
  63. data/lib/sidekiq_unique_jobs/normalizer.rb +4 -4
  64. data/lib/sidekiq_unique_jobs/on_conflict/log.rb +9 -5
  65. data/lib/sidekiq_unique_jobs/on_conflict/null_strategy.rb +1 -1
  66. data/lib/sidekiq_unique_jobs/on_conflict/raise.rb +1 -1
  67. data/lib/sidekiq_unique_jobs/on_conflict/reject.rb +63 -17
  68. data/lib/sidekiq_unique_jobs/on_conflict/replace.rb +54 -14
  69. data/lib/sidekiq_unique_jobs/on_conflict/reschedule.rb +16 -5
  70. data/lib/sidekiq_unique_jobs/on_conflict/strategy.rb +25 -6
  71. data/lib/sidekiq_unique_jobs/on_conflict.rb +23 -10
  72. data/lib/sidekiq_unique_jobs/options_with_fallback.rb +39 -36
  73. data/lib/sidekiq_unique_jobs/orphans/lua_reaper.rb +29 -0
  74. data/lib/sidekiq_unique_jobs/orphans/manager.rb +241 -0
  75. data/lib/sidekiq_unique_jobs/orphans/null_reaper.rb +24 -0
  76. data/lib/sidekiq_unique_jobs/orphans/observer.rb +42 -0
  77. data/lib/sidekiq_unique_jobs/orphans/reaper.rb +114 -0
  78. data/lib/sidekiq_unique_jobs/orphans/reaper_resurrector.rb +170 -0
  79. data/lib/sidekiq_unique_jobs/orphans/ruby_reaper.rb +298 -0
  80. data/lib/sidekiq_unique_jobs/redis/entity.rb +112 -0
  81. data/lib/sidekiq_unique_jobs/redis/hash.rb +56 -0
  82. data/lib/sidekiq_unique_jobs/redis/list.rb +32 -0
  83. data/lib/sidekiq_unique_jobs/redis/set.rb +32 -0
  84. data/lib/sidekiq_unique_jobs/redis/sorted_set.rb +86 -0
  85. data/lib/sidekiq_unique_jobs/redis/string.rb +51 -0
  86. data/lib/sidekiq_unique_jobs/redis.rb +11 -0
  87. data/lib/sidekiq_unique_jobs/reflectable.rb +26 -0
  88. data/lib/sidekiq_unique_jobs/reflections.rb +79 -0
  89. data/lib/sidekiq_unique_jobs/rspec/matchers/have_valid_sidekiq_options.rb +51 -0
  90. data/lib/sidekiq_unique_jobs/rspec/matchers.rb +26 -0
  91. data/lib/sidekiq_unique_jobs/script/caller.rb +127 -0
  92. data/lib/sidekiq_unique_jobs/script.rb +15 -0
  93. data/lib/sidekiq_unique_jobs/server.rb +61 -0
  94. data/lib/sidekiq_unique_jobs/sidekiq_unique_ext.rb +114 -65
  95. data/lib/sidekiq_unique_jobs/sidekiq_unique_jobs.rb +252 -36
  96. data/lib/sidekiq_unique_jobs/sidekiq_worker_methods.rb +47 -32
  97. data/lib/sidekiq_unique_jobs/testing.rb +102 -29
  98. data/lib/sidekiq_unique_jobs/timer_task.rb +299 -0
  99. data/lib/sidekiq_unique_jobs/timing.rb +58 -0
  100. data/lib/sidekiq_unique_jobs/unlockable.rb +20 -4
  101. data/lib/sidekiq_unique_jobs/update_version.rb +25 -0
  102. data/lib/sidekiq_unique_jobs/upgrade_locks.rb +155 -0
  103. data/lib/sidekiq_unique_jobs/version.rb +3 -1
  104. data/lib/sidekiq_unique_jobs/version_check.rb +23 -4
  105. data/lib/sidekiq_unique_jobs/web/helpers.rb +138 -13
  106. data/lib/sidekiq_unique_jobs/web/views/_paging.erb +4 -4
  107. data/lib/sidekiq_unique_jobs/web/views/changelogs.erb +54 -0
  108. data/lib/sidekiq_unique_jobs/web/views/lock.erb +110 -0
  109. data/lib/sidekiq_unique_jobs/web/views/locks.erb +54 -0
  110. data/lib/sidekiq_unique_jobs/web.rb +82 -32
  111. data/lib/sidekiq_unique_jobs.rb +54 -7
  112. data/lib/tasks/changelog.rake +16 -16
  113. metadata +134 -177
  114. data/lib/sidekiq_unique_jobs/client/middleware.rb +0 -56
  115. data/lib/sidekiq_unique_jobs/scripts.rb +0 -118
  116. data/lib/sidekiq_unique_jobs/server/middleware.rb +0 -46
  117. data/lib/sidekiq_unique_jobs/timeout/calculator.rb +0 -63
  118. data/lib/sidekiq_unique_jobs/timeout.rb +0 -8
  119. data/lib/sidekiq_unique_jobs/unique_args.rb +0 -150
  120. data/lib/sidekiq_unique_jobs/util.rb +0 -103
  121. data/lib/sidekiq_unique_jobs/web/views/unique_digest.erb +0 -28
  122. data/lib/sidekiq_unique_jobs/web/views/unique_digests.erb +0 -46
  123. data/redis/acquire_lock.lua +0 -21
  124. data/redis/convert_legacy_lock.lua +0 -13
  125. data/redis/delete.lua +0 -14
  126. data/redis/delete_by_digest.lua +0 -23
  127. data/redis/delete_job_by_digest.lua +0 -60
  128. data/redis/lock.lua +0 -62
  129. data/redis/release_stale_locks.lua +0 -90
  130. data/redis/unlock.lua +0 -35
@@ -0,0 +1,124 @@
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 |pipeline|
91
+ chunk.each do |digest|
92
+ del_digest(pipeline, digest)
93
+ pipeline.zrem(SidekiqUniqueJobs::DIGESTS, digest)
94
+ pipeline.zrem(SidekiqUniqueJobs::EXPIRING_DIGESTS, digest)
95
+ @count += 1
96
+ end
97
+ end
98
+ end
99
+
100
+ @count
101
+ end
102
+
103
+ def del_digest(pipeline, digest)
104
+ removable_keys = keys_for_digest(digest)
105
+
106
+ if VersionCheck.satisfied?(redis_version, ">= 4.0.0")
107
+ pipeline.unlink(*removable_keys)
108
+ else
109
+ pipeline.del(*removable_keys)
110
+ end
111
+ end
112
+
113
+ def keys_for_digest(digest)
114
+ [digest, "#{digest}:RUN"].each_with_object([]) do |key, digest_keys|
115
+ digest_keys.push(key)
116
+ digest_keys.concat(SUFFIXES.map { |suffix| "#{key}:#{suffix}" })
117
+ end
118
+ end
119
+
120
+ def redis_version
121
+ @redis_version ||= SidekiqUniqueJobs.config.redis_version
122
+ end
123
+ end
124
+ 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 |pipeline|
66
+ pipeline.zcard(key)
67
+ pipeline.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,89 @@ 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
+ max_count = options[:count]
24
+ say "Searching for regular digests"
25
+ list_entries(digests.entries(pattern: pattern, count: max_count), pattern)
26
+ say "Searching for expiring digests"
27
+ list_entries(expiring_digests.entries(pattern: pattern, count: max_count), pattern)
22
28
  end
23
29
 
24
- desc "del PATTERN", "deletes unique keys from redis by pattern"
30
+ desc "del PATTERN", "deletes unique digests from redis by pattern"
25
31
  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"
32
+ option :count, aliases: :c, type: :numeric, default: 1000, desc: "The max number of digests to return"
33
+ # :nodoc:
27
34
  def del(pattern)
28
35
  max_count = options[:count]
29
36
  if options[:dry_run]
30
- keys = Util.keys(pattern, max_count)
31
- say "Would delete #{keys.size} keys matching '#{pattern}'"
37
+ count_entries_for_del(max_count, pattern)
32
38
  else
33
- deleted_count = Util.del(pattern, max_count)
34
- say "Deleted #{deleted_count} keys matching '#{pattern}'"
39
+ del_entries(max_count, 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
- no_commands do
48
- def console_class
49
- return irb if RUBY_PLATFORM == "JAVA"
54
+ no_commands do # rubocop:disable Metrics/BlockLength
55
+ # :nodoc:
56
+ def digests
57
+ @digests ||= SidekiqUniqueJobs::Digests.new
58
+ end
50
59
 
51
- pry
60
+ # :nodoc:
61
+ def expiring_digests
62
+ @expiring_digests ||= SidekiqUniqueJobs::ExpiringDigests.new
52
63
  end
53
64
 
54
- def irb
65
+ # :nodoc:
66
+ def console_class
67
+ require "pry"
68
+ Pry
69
+ rescue NameError, LoadError
55
70
  require "irb"
56
71
  IRB
57
72
  end
58
73
 
59
- def pry
60
- require "pry"
61
- Pry
62
- rescue LoadError, NameError
63
- irb
74
+ # :nodoc:
75
+ def list_entries(entries, pattern)
76
+ say "Found #{entries.size} digests matching '#{pattern}':"
77
+ print_in_columns(entries.sort) if entries.any?
78
+ end
79
+
80
+ # :nodoc:
81
+ def count_entries_for_del(max_count, pattern)
82
+ count = digests.entries(pattern: pattern, count: max_count).size +
83
+ expiring_digests.entries(pattern: pattern, count: max_count).size
84
+ say "Would delete #{count} digests matching '#{pattern}'"
85
+ end
86
+
87
+ # :nodoc:
88
+ def del_entries(max_count, pattern)
89
+ deleted_count = digests.delete_by_pattern(pattern, count: max_count).to_i +
90
+ expiring_digests.delete_by_pattern(pattern, count: max_count).to_i
91
+ say "Deleted #{deleted_count} digests matching '#{pattern}'"
64
92
  end
65
93
  end
66
94
  end
@@ -0,0 +1,319 @@
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
+ :logger_enabled,
12
+ :locks,
13
+ :strategies,
14
+ :debug_lua,
15
+ :max_history,
16
+ :reaper,
17
+ :reaper_count,
18
+ :reaper_interval,
19
+ :reaper_timeout,
20
+ :reaper_resurrector_interval,
21
+ :reaper_resurrector_enabled,
22
+ :lock_info,
23
+ :raise_on_config_error,
24
+ :current_redis_version)
25
+
26
+ #
27
+ # Shared class for dealing with gem configuration
28
+ #
29
+ # @author Mauro Berlanda <mauro.berlanda@gmail.com>
30
+ # rubocop:disable Metrics/ClassLength
31
+ class Config < ThreadSafeConfig
32
+ #
33
+ # @return [Hash<Symbol, SidekiqUniqueJobs::Lock::BaseLock] all available queued locks
34
+ LOCKS_WHILE_ENQUEUED = {
35
+ until_executing: SidekiqUniqueJobs::Lock::UntilExecuting,
36
+ while_enqueued: SidekiqUniqueJobs::Lock::UntilExecuting,
37
+ }.freeze
38
+
39
+ #
40
+ # @return [Hash<Symbol, SidekiqUniqueJobs::Lock::BaseLock] all available fulltime locks
41
+ LOCKS_FROM_PUSH_TO_PROCESSED = {
42
+ until_completed: SidekiqUniqueJobs::Lock::UntilExecuted,
43
+ until_executed: SidekiqUniqueJobs::Lock::UntilExecuted,
44
+ until_performed: SidekiqUniqueJobs::Lock::UntilExecuted,
45
+ until_processed: SidekiqUniqueJobs::Lock::UntilExecuted,
46
+ until_and_while_executing: SidekiqUniqueJobs::Lock::UntilAndWhileExecuting,
47
+ until_successfully_completed: SidekiqUniqueJobs::Lock::UntilExecuted,
48
+ }.freeze
49
+
50
+ #
51
+ # @return [Hash<Symbol, SidekiqUniqueJobs::Lock::BaseLock] all available locks without unlock
52
+ LOCKS_WITHOUT_UNLOCK = {
53
+ until_expired: SidekiqUniqueJobs::Lock::UntilExpired,
54
+ }.freeze
55
+
56
+ #
57
+ # @return [Hash<Symbol, SidekiqUniqueJobs::Lock::BaseLock] all available runtime/client locks
58
+ LOCKS_WHEN_BUSY = {
59
+ around_perform: SidekiqUniqueJobs::Lock::WhileExecuting,
60
+ while_busy: SidekiqUniqueJobs::Lock::WhileExecuting,
61
+ while_executing: SidekiqUniqueJobs::Lock::WhileExecuting,
62
+ while_working: SidekiqUniqueJobs::Lock::WhileExecuting,
63
+ while_executing_reject: SidekiqUniqueJobs::Lock::WhileExecutingReject,
64
+ }.freeze
65
+
66
+ #
67
+ # @return [Hash<Symbol, SidekiqUniqueJobs::Lock::BaseLock] all available default locks
68
+ LOCKS =
69
+ LOCKS_WHEN_BUSY.dup
70
+ .merge(LOCKS_WHILE_ENQUEUED.dup)
71
+ .merge(LOCKS_WITHOUT_UNLOCK.dup)
72
+ .merge(LOCKS_FROM_PUSH_TO_PROCESSED.dup)
73
+ .freeze
74
+
75
+ #
76
+ # @return [Hash<Symbol, SidekiqUniqueJobs::OnConflict::Strategy] all available default strategies
77
+ STRATEGIES = {
78
+ log: SidekiqUniqueJobs::OnConflict::Log,
79
+ raise: SidekiqUniqueJobs::OnConflict::Raise,
80
+ reject: SidekiqUniqueJobs::OnConflict::Reject,
81
+ replace: SidekiqUniqueJobs::OnConflict::Replace,
82
+ reschedule: SidekiqUniqueJobs::OnConflict::Reschedule,
83
+ }.freeze
84
+
85
+ #
86
+ # @return ['uniquejobs'] by default we use this prefix
87
+ PREFIX = "uniquejobs"
88
+ #
89
+ # @return [0] by default don't wait for locks
90
+ LOCK_TIMEOUT = 0
91
+ #
92
+ # @return [nil]
93
+ LOCK_TTL = nil
94
+ #
95
+ # @return [true,false] by default false (don't disable logger)
96
+ LOGGER_ENABLED = true
97
+ #
98
+ # @return [true] by default the gem is enabled
99
+ ENABLED = true
100
+ #
101
+ # @return [false] by default we don't debug the lua scripts because it is slow
102
+ DEBUG_LUA = false
103
+ #
104
+ # @return [1_000] use a changelog history of 1_000 entries by default
105
+ MAX_HISTORY = 1_000
106
+ #
107
+ # @return [:ruby] prefer the ruby reaper by default since the lua reaper still has problems
108
+ REAPER = :ruby
109
+ #
110
+ # @return [1_000] reap 1_000 orphaned locks at a time by default
111
+ REAPER_COUNT = 1_000
112
+ #
113
+ # @return [600] reap locks every 10 minutes
114
+ REAPER_INTERVAL = 600
115
+ #
116
+ # @return [10] stop reaper after 10 seconds
117
+ REAPER_TIMEOUT = 10
118
+ #
119
+ # @return [3600] check if reaper is dead each 3600 seconds
120
+ REAPER_RESURRECTOR_INTERVAL = 3600
121
+
122
+ #
123
+ # @return [false] enable reaper resurrector
124
+ REAPER_RESURRECTOR_ENABLED = false
125
+
126
+ #
127
+ # @return [false] while useful it also adds overhead so disable lock_info by default
128
+ USE_LOCK_INFO = false
129
+ #
130
+ # @return [false] by default we don't raise validation errors for workers
131
+ RAISE_ON_CONFIG_ERROR = false
132
+ #
133
+ # @return [0.0.0] default redis version is only to avoid NoMethodError on nil
134
+ REDIS_VERSION = "0.0.0"
135
+
136
+ #
137
+ # Returns a default configuration
138
+ #
139
+ # @example
140
+ # SidekiqUniqueJobs::Config.default => <concurrent/mutable_struct/thread_safe_config SidekiqUniqueJobs::Config {
141
+ # default_lock_timeout: 0,
142
+ # default_lock_ttl: nil,
143
+ # enabled: true,
144
+ # lock_prefix: "uniquejobs",
145
+ # logger: #<Sidekiq::Logger:0x00007f81e096b0e0 @level=1 ...>,
146
+ # locks: {
147
+ # around_perform: SidekiqUniqueJobs::Lock::WhileExecuting,
148
+ # while_busy: SidekiqUniqueJobs::Lock::WhileExecuting,
149
+ # while_executing: SidekiqUniqueJobs::Lock::WhileExecuting,
150
+ # while_working: SidekiqUniqueJobs::Lock::WhileExecuting,
151
+ # while_executing_reject: SidekiqUniqueJobs::Lock::WhileExecutingReject,
152
+ # until_executing: SidekiqUniqueJobs::Lock::UntilExecuting,
153
+ # while_enqueued: SidekiqUniqueJobs::Lock::UntilExecuting,
154
+ # until_expired: SidekiqUniqueJobs::Lock::UntilExpired,
155
+ # until_completed: SidekiqUniqueJobs::Lock::UntilExecuted,
156
+ # until_executed: SidekiqUniqueJobs::Lock::UntilExecuted,
157
+ # until_performed: SidekiqUniqueJobs::Lock::UntilExecuted,
158
+ # until_processed: SidekiqUniqueJobs::Lock::UntilExecuted,
159
+ # until_and_while_executing: SidekiqUniqueJobs::Lock::UntilAndWhileExecuting,
160
+ # until_successfully_completed: SidekiqUniqueJobs::Lock::UntilExecuted
161
+ # },
162
+ # strategies: {
163
+ # log: SidekiqUniqueJobs::OnConflict::Log,
164
+ # raise: SidekiqUniqueJobs::OnConflict::Raise,
165
+ # reject: SidekiqUniqueJobs::OnConflict::Reject,
166
+ # replace: SidekiqUniqueJobs::OnConflict::Replace,
167
+ # reschedule: SidekiqUniqueJobs::OnConflict::Reschedule
168
+ # },
169
+ # debug_lua: false,
170
+ # max_history: 1000,
171
+ # reaper:: ruby,
172
+ # reaper_count: 1000,
173
+ # lock_info: false,
174
+ # raise_on_config_error: false,
175
+ # }>
176
+ #
177
+ #
178
+ # @return [SidekiqUniqueJobs::Config] a default configuration
179
+ #
180
+ def self.default # rubocop:disable Metrics/MethodLength
181
+ new(
182
+ LOCK_TIMEOUT,
183
+ LOCK_TTL,
184
+ ENABLED,
185
+ PREFIX,
186
+ Sidekiq.logger,
187
+ LOGGER_ENABLED,
188
+ LOCKS,
189
+ STRATEGIES,
190
+ DEBUG_LUA,
191
+ MAX_HISTORY,
192
+ REAPER,
193
+ REAPER_COUNT,
194
+ REAPER_INTERVAL,
195
+ REAPER_TIMEOUT,
196
+ REAPER_RESURRECTOR_INTERVAL,
197
+ REAPER_RESURRECTOR_ENABLED,
198
+ USE_LOCK_INFO,
199
+ RAISE_ON_CONFIG_ERROR,
200
+ REDIS_VERSION,
201
+ )
202
+ end
203
+
204
+ #
205
+ # Set the default_lock_ttl
206
+ # @deprecated
207
+ #
208
+ # @param [Integer] obj value to set (seconds)
209
+ #
210
+ # @return [<type>] <description>
211
+ #
212
+ def default_lock_ttl=(obj)
213
+ warn "[DEPRECATION] `#{class_name}##{__method__}` is deprecated." \
214
+ " Please use `#{class_name}#lock_ttl=` instead."
215
+ self.lock_ttl = obj
216
+ end
217
+
218
+ #
219
+ # Set new value for default_lock_timeout
220
+ # @deprecated
221
+ #
222
+ # @param [Integer] obj value to set (seconds)
223
+ #
224
+ # @return [Integer]
225
+ #
226
+ def default_lock_timeout=(obj)
227
+ warn "[DEPRECATION] `#{class_name}##{__method__}` is deprecated." \
228
+ " Please use `#{class_name}#lock_timeout=` instead."
229
+ self.lock_timeout = obj
230
+ end
231
+
232
+ #
233
+ # Default lock TTL (Time To Live)
234
+ # @deprecated
235
+ #
236
+ # @return [nil, Integer] configured value or nil
237
+ #
238
+ def default_lock_ttl
239
+ warn "[DEPRECATION] `#{class_name}##{__method__}` is deprecated." \
240
+ " Please use `#{class_name}#lock_ttl` instead."
241
+ lock_ttl
242
+ end
243
+
244
+ #
245
+ # Default Lock Timeout
246
+ # @deprecated
247
+ #
248
+ #
249
+ # @return [nil, Integer] configured value or nil
250
+ #
251
+ def default_lock_timeout
252
+ warn "[DEPRECATION] `#{class_name}##{__method__}` is deprecated." \
253
+ " Please use `#{class_name}#lock_timeout` instead."
254
+ lock_timeout
255
+ end
256
+
257
+ #
258
+ # Memoized variable to get the class name
259
+ #
260
+ #
261
+ # @return [String] name of the class
262
+ #
263
+ def class_name
264
+ @class_name ||= self.class.name
265
+ end
266
+
267
+ #
268
+ # Adds a lock type to the configuration. It will raise if the lock exists already
269
+ #
270
+ # @example Add a custom lock
271
+ # add_lock(:my_lock, CustomLocks::MyLock)
272
+ #
273
+ # @raise DuplicateLock when the name already exists
274
+ #
275
+ # @param [String, Symbol] name the name of the lock
276
+ # @param [Class] klass the class describing the lock
277
+ #
278
+ # @return [void]
279
+ #
280
+ def add_lock(name, klass)
281
+ lock_sym = name.to_sym
282
+ raise DuplicateLock, ":#{name} already defined, please use another name" if locks.key?(lock_sym)
283
+
284
+ new_locks = locks.dup.merge(lock_sym => klass).freeze
285
+ self.locks = new_locks
286
+ end
287
+
288
+ #
289
+ # Adds an on_conflict strategy to the configuration.
290
+ #
291
+ # @example Add a custom strategy
292
+ # add_lock(:my_strategy, CustomStrategies::MyStrategy)
293
+ #
294
+ # @raise [DuplicateStrategy] when the name already exists
295
+ #
296
+ # @param [String] name the name of the custom strategy
297
+ # @param [Class] klass the class describing the strategy
298
+ #
299
+ def add_strategy(name, klass)
300
+ strategy_sym = name.to_sym
301
+ raise DuplicateStrategy, ":#{name} already defined, please use another name" if strategies.key?(strategy_sym)
302
+
303
+ new_strategies = strategies.dup.merge(strategy_sym => klass).freeze
304
+ self.strategies = new_strategies
305
+ end
306
+
307
+ #
308
+ # The current version of redis
309
+ #
310
+ #
311
+ # @return [String] a version string eg. `5.0.1`
312
+ #
313
+ def redis_version
314
+ self.current_redis_version = SidekiqUniqueJobs.fetch_redis_version if current_redis_version == REDIS_VERSION
315
+ current_redis_version
316
+ end
317
+ end
318
+ # rubocop:enable Metrics/ClassLength
319
+ 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