sidekiq-unique-jobs 3.0.11 → 8.0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (158) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +2163 -25
  3. data/LICENSE.txt +21 -0
  4. data/README.md +984 -47
  5. data/bin/uniquejobs +7 -0
  6. data/lib/sidekiq-unique-jobs.rb +2 -36
  7. data/lib/sidekiq_unique_jobs/batch_delete.rb +120 -0
  8. data/lib/sidekiq_unique_jobs/changelog.rb +68 -0
  9. data/lib/sidekiq_unique_jobs/cli.rb +95 -0
  10. data/lib/sidekiq_unique_jobs/config.rb +306 -33
  11. data/lib/sidekiq_unique_jobs/connection.rb +20 -0
  12. data/lib/sidekiq_unique_jobs/constants.rb +55 -0
  13. data/lib/sidekiq_unique_jobs/core_ext.rb +132 -0
  14. data/lib/sidekiq_unique_jobs/deprecation.rb +65 -0
  15. data/lib/sidekiq_unique_jobs/digests.rb +134 -0
  16. data/lib/sidekiq_unique_jobs/exceptions.rb +105 -0
  17. data/lib/sidekiq_unique_jobs/expiring_digests.rb +14 -0
  18. data/lib/sidekiq_unique_jobs/job.rb +63 -0
  19. data/lib/sidekiq_unique_jobs/json.rb +47 -0
  20. data/lib/sidekiq_unique_jobs/key.rb +98 -0
  21. data/lib/sidekiq_unique_jobs/lock/base_lock.rb +165 -0
  22. data/lib/sidekiq_unique_jobs/lock/client_validator.rb +28 -0
  23. data/lib/sidekiq_unique_jobs/lock/server_validator.rb +27 -0
  24. data/lib/sidekiq_unique_jobs/lock/until_and_while_executing.rb +71 -0
  25. data/lib/sidekiq_unique_jobs/lock/until_executed.rb +48 -0
  26. data/lib/sidekiq_unique_jobs/lock/until_executing.rb +43 -0
  27. data/lib/sidekiq_unique_jobs/lock/until_expired.rb +42 -0
  28. data/lib/sidekiq_unique_jobs/lock/validator.rb +96 -0
  29. data/lib/sidekiq_unique_jobs/lock/while_executing.rb +70 -0
  30. data/lib/sidekiq_unique_jobs/lock/while_executing_reject.rb +21 -0
  31. data/lib/sidekiq_unique_jobs/lock.rb +348 -0
  32. data/lib/sidekiq_unique_jobs/lock_args.rb +127 -0
  33. data/lib/sidekiq_unique_jobs/lock_config.rb +132 -0
  34. data/lib/sidekiq_unique_jobs/lock_digest.rb +79 -0
  35. data/lib/sidekiq_unique_jobs/lock_info.rb +68 -0
  36. data/lib/sidekiq_unique_jobs/lock_timeout.rb +62 -0
  37. data/lib/sidekiq_unique_jobs/lock_ttl.rb +77 -0
  38. data/lib/sidekiq_unique_jobs/lock_type.rb +37 -0
  39. data/lib/sidekiq_unique_jobs/locksmith.rb +390 -0
  40. data/lib/sidekiq_unique_jobs/logging/middleware_context.rb +44 -0
  41. data/lib/sidekiq_unique_jobs/logging.rb +236 -0
  42. data/lib/sidekiq_unique_jobs/lua/delete.lua +49 -0
  43. data/lib/sidekiq_unique_jobs/lua/delete_by_digest.lua +39 -0
  44. data/lib/sidekiq_unique_jobs/lua/delete_job_by_digest.lua +38 -0
  45. data/lib/sidekiq_unique_jobs/lua/find_digest_in_queues.lua +26 -0
  46. data/lib/sidekiq_unique_jobs/lua/lock.lua +108 -0
  47. data/lib/sidekiq_unique_jobs/lua/lock_until_expired.lua +92 -0
  48. data/lib/sidekiq_unique_jobs/lua/locked.lua +35 -0
  49. data/lib/sidekiq_unique_jobs/lua/queue.lua +88 -0
  50. data/lib/sidekiq_unique_jobs/lua/reap_orphans.lua +119 -0
  51. data/lib/sidekiq_unique_jobs/lua/shared/_common.lua +35 -0
  52. data/lib/sidekiq_unique_jobs/lua/shared/_current_time.lua +8 -0
  53. data/lib/sidekiq_unique_jobs/lua/shared/_delete_from_queue.lua +22 -0
  54. data/lib/sidekiq_unique_jobs/lua/shared/_delete_from_sorted_set.lua +29 -0
  55. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_process_set.lua +53 -0
  56. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_queues.lua +43 -0
  57. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_sorted_set.lua +24 -0
  58. data/lib/sidekiq_unique_jobs/lua/shared/_hgetall.lua +13 -0
  59. data/lib/sidekiq_unique_jobs/lua/shared/_upgrades.lua +3 -0
  60. data/lib/sidekiq_unique_jobs/lua/unlock.lua +112 -0
  61. data/lib/sidekiq_unique_jobs/lua/update_version.lua +40 -0
  62. data/lib/sidekiq_unique_jobs/lua/upgrade.lua +66 -0
  63. data/lib/sidekiq_unique_jobs/middleware/client.rb +42 -0
  64. data/lib/sidekiq_unique_jobs/middleware/server.rb +31 -0
  65. data/lib/sidekiq_unique_jobs/middleware.rb +41 -15
  66. data/lib/sidekiq_unique_jobs/normalizer.rb +17 -0
  67. data/lib/sidekiq_unique_jobs/on_conflict/log.rb +24 -0
  68. data/lib/sidekiq_unique_jobs/on_conflict/null_strategy.rb +16 -0
  69. data/lib/sidekiq_unique_jobs/on_conflict/raise.rb +17 -0
  70. data/lib/sidekiq_unique_jobs/on_conflict/reject.rb +75 -0
  71. data/lib/sidekiq_unique_jobs/on_conflict/replace.rb +82 -0
  72. data/lib/sidekiq_unique_jobs/on_conflict/reschedule.rb +39 -0
  73. data/lib/sidekiq_unique_jobs/on_conflict/strategy.rb +51 -0
  74. data/lib/sidekiq_unique_jobs/on_conflict.rb +44 -0
  75. data/lib/sidekiq_unique_jobs/options_with_fallback.rb +78 -0
  76. data/lib/sidekiq_unique_jobs/orphans/lua_reaper.rb +29 -0
  77. data/lib/sidekiq_unique_jobs/orphans/manager.rb +242 -0
  78. data/lib/sidekiq_unique_jobs/orphans/null_reaper.rb +24 -0
  79. data/lib/sidekiq_unique_jobs/orphans/observer.rb +42 -0
  80. data/lib/sidekiq_unique_jobs/orphans/reaper.rb +115 -0
  81. data/lib/sidekiq_unique_jobs/orphans/reaper_resurrector.rb +170 -0
  82. data/lib/sidekiq_unique_jobs/orphans/ruby_reaper.rb +313 -0
  83. data/lib/sidekiq_unique_jobs/redis/entity.rb +112 -0
  84. data/lib/sidekiq_unique_jobs/redis/hash.rb +56 -0
  85. data/lib/sidekiq_unique_jobs/redis/list.rb +32 -0
  86. data/lib/sidekiq_unique_jobs/redis/set.rb +32 -0
  87. data/lib/sidekiq_unique_jobs/redis/sorted_set.rb +102 -0
  88. data/lib/sidekiq_unique_jobs/redis/string.rb +51 -0
  89. data/lib/sidekiq_unique_jobs/redis.rb +11 -0
  90. data/lib/sidekiq_unique_jobs/reflectable.rb +26 -0
  91. data/lib/sidekiq_unique_jobs/reflections.rb +79 -0
  92. data/lib/sidekiq_unique_jobs/rspec/matchers/have_valid_sidekiq_options.rb +51 -0
  93. data/lib/sidekiq_unique_jobs/rspec/matchers.rb +26 -0
  94. data/lib/sidekiq_unique_jobs/script/caller.rb +133 -0
  95. data/lib/sidekiq_unique_jobs/script/client.rb +94 -0
  96. data/lib/sidekiq_unique_jobs/script/config.rb +68 -0
  97. data/lib/sidekiq_unique_jobs/script/dsl.rb +60 -0
  98. data/lib/sidekiq_unique_jobs/script/logging.rb +95 -0
  99. data/lib/sidekiq_unique_jobs/script/lua_error.rb +96 -0
  100. data/lib/sidekiq_unique_jobs/script/script.rb +75 -0
  101. data/lib/sidekiq_unique_jobs/script/scripts.rb +123 -0
  102. data/lib/sidekiq_unique_jobs/script/template.rb +41 -0
  103. data/lib/sidekiq_unique_jobs/script/timing.rb +35 -0
  104. data/lib/sidekiq_unique_jobs/script.rb +46 -0
  105. data/lib/sidekiq_unique_jobs/server.rb +62 -0
  106. data/lib/sidekiq_unique_jobs/sidekiq_unique_ext.rb +110 -37
  107. data/lib/sidekiq_unique_jobs/sidekiq_unique_jobs.rb +304 -0
  108. data/lib/sidekiq_unique_jobs/sidekiq_worker_methods.rb +84 -0
  109. data/lib/sidekiq_unique_jobs/testing.rb +132 -9
  110. data/lib/sidekiq_unique_jobs/timer_task.rb +299 -0
  111. data/lib/sidekiq_unique_jobs/timing.rb +58 -0
  112. data/lib/sidekiq_unique_jobs/unlockable.rb +43 -0
  113. data/lib/sidekiq_unique_jobs/update_version.rb +25 -0
  114. data/lib/sidekiq_unique_jobs/upgrade_locks.rb +152 -0
  115. data/lib/sidekiq_unique_jobs/version.rb +5 -1
  116. data/lib/sidekiq_unique_jobs/version_check.rb +114 -0
  117. data/lib/sidekiq_unique_jobs/web/helpers.rb +175 -0
  118. data/lib/sidekiq_unique_jobs/web/views/_paging.erb +10 -0
  119. data/lib/sidekiq_unique_jobs/web/views/changelogs.erb +60 -0
  120. data/lib/sidekiq_unique_jobs/web/views/lock.erb +110 -0
  121. data/lib/sidekiq_unique_jobs/web/views/locks.erb +59 -0
  122. data/lib/sidekiq_unique_jobs/web.rb +109 -0
  123. data/lib/sidekiq_unique_jobs.rb +83 -0
  124. data/lib/tasks/changelog.rake +23 -0
  125. metadata +157 -126
  126. data/.gitignore +0 -10
  127. data/.rspec +0 -3
  128. data/.rubocop.yml +0 -36
  129. data/.travis.yml +0 -25
  130. data/Appraisals +0 -20
  131. data/Gemfile +0 -5
  132. data/LICENSE +0 -22
  133. data/Rakefile +0 -11
  134. data/gemfiles/sidekiq_2.15.gemfile +0 -9
  135. data/gemfiles/sidekiq_2.16.gemfile +0 -9
  136. data/gemfiles/sidekiq_2.17.gemfile +0 -9
  137. data/gemfiles/sidekiq_3.0.gemfile +0 -9
  138. data/gemfiles/sidekiq_develop.gemfile +0 -9
  139. data/lib/sidekiq_unique_jobs/connectors/redis_pool.rb +0 -11
  140. data/lib/sidekiq_unique_jobs/connectors/sidekiq_redis.rb +0 -9
  141. data/lib/sidekiq_unique_jobs/connectors/testing.rb +0 -11
  142. data/lib/sidekiq_unique_jobs/connectors.rb +0 -16
  143. data/lib/sidekiq_unique_jobs/middleware/client/strategies/testing_inline.rb +0 -25
  144. data/lib/sidekiq_unique_jobs/middleware/client/strategies/unique.rb +0 -76
  145. data/lib/sidekiq_unique_jobs/middleware/client/unique_jobs.rb +0 -39
  146. data/lib/sidekiq_unique_jobs/middleware/server/unique_jobs.rb +0 -69
  147. data/lib/sidekiq_unique_jobs/payload_helper.rb +0 -42
  148. data/sidekiq-unique-jobs.gemspec +0 -27
  149. data/spec/lib/.sidekiq_testing_enabled_spec.rb.swp +0 -0
  150. data/spec/lib/client_spec.rb +0 -173
  151. data/spec/lib/middleware/server/unique_jobs_spec.rb +0 -81
  152. data/spec/lib/sidekiq_testing_enabled_spec.rb +0 -123
  153. data/spec/lib/sidekiq_unique_ext_spec.rb +0 -70
  154. data/spec/lib/unlock_order_spec.rb +0 -64
  155. data/spec/spec_helper.rb +0 -37
  156. data/spec/support/my_worker.rb +0 -13
  157. data/spec/support/sidekiq_meta.rb +0 -17
  158. data/spec/support/unique_worker.rb +0 -13
data/bin/uniquejobs ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "sidekiq_unique_jobs"
6
+
7
+ SidekiqUniqueJobs::Cli.start(ARGV)
@@ -1,37 +1,3 @@
1
- # rubocop:disable FileName
2
- require 'yaml' if RUBY_VERSION.include?('2.0.0')
3
- require 'sidekiq_unique_jobs/middleware'
4
- require 'sidekiq_unique_jobs/version'
5
- require 'sidekiq_unique_jobs/config'
6
- require 'sidekiq_unique_jobs/payload_helper'
7
- require 'ostruct'
1
+ # frozen_string_literal: true
8
2
 
9
- module SidekiqUniqueJobs
10
- module_function
11
-
12
- def config
13
- @config ||= Config.new(
14
- unique_prefix: 'sidekiq_unique',
15
- unique_args_enabled: false,
16
- default_expiration: 30 * 60,
17
- default_unlock_order: :after_yield
18
- )
19
- end
20
-
21
- def unique_args_enabled?
22
- config.unique_args_enabled
23
- end
24
-
25
- def configure
26
- yield config
27
- end
28
-
29
- # Attempt to constantize a string worker_class argument, always
30
- # failing back to the original argument.
31
- def worker_class_constantize(worker_class)
32
- return worker_class unless worker_class.is_a?(String)
33
- worker_class.constantize
34
- rescue NameError
35
- worker_class
36
- end
37
- end
3
+ require "sidekiq_unique_jobs"
@@ -0,0 +1,120 @@
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 = 500
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
+ pipeline.unlink(*removable_keys)
107
+ end
108
+
109
+ def keys_for_digest(digest)
110
+ [digest, "#{digest}:RUN"].each_with_object([]) do |key, digest_keys|
111
+ digest_keys.push(key)
112
+ digest_keys.concat(SUFFIXES.map { |suffix| "#{key}:#{suffix}" })
113
+ end
114
+ end
115
+
116
+ def redis_version
117
+ @redis_version ||= SidekiqUniqueJobs.config.redis_version
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,68 @@
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
+ def initialize
11
+ super(CHANGELOGS)
12
+ end
13
+
14
+ #
15
+ # Adds a new changelog entry
16
+ #
17
+ # @param [String] message a descriptive message about the entry
18
+ # @param [String] digest a unique digest
19
+ # @param [String] job_id a Sidekiq JID
20
+ # @param [String] script the name of the script adding the entry
21
+ #
22
+ # @return [void]
23
+ #
24
+ def add(message:, digest:, job_id:, script:)
25
+ message = dump_json(message: message, digest: digest, job_id: job_id, script: script)
26
+ redis { |conn| conn.zadd(key, now_f, message) }
27
+ end
28
+
29
+ #
30
+ # The change log entries
31
+ #
32
+ # @param [String] pattern the pattern to match
33
+ # @param [Integer] count the number of matches to return
34
+ #
35
+ # @return [Array<Hash>] an array of entries
36
+ #
37
+ def entries(pattern: SCAN_PATTERN, count: DEFAULT_COUNT)
38
+ redis do |conn|
39
+ conn.zscan(key, match: pattern, count: count).to_a.map { |entry| load_json(entry[0]) }
40
+ end
41
+ end
42
+
43
+ #
44
+ # Paginate the changelog entries
45
+ #
46
+ # @param [Integer] cursor the cursor for this iteration
47
+ # @param [String] pattern "*" the pattern to match
48
+ # @param [Integer] page_size 100 the number of matches to return
49
+ #
50
+ # @return [Array<Integer, Integer, Array<Hash>] the total size, next cursor and changelog entries
51
+ #
52
+ def page(cursor: 0, pattern: "*", page_size: 100)
53
+ redis do |conn|
54
+ total_size, result = conn.multi do |pipeline|
55
+ pipeline.zcard(key)
56
+ pipeline.zscan(key, cursor, match: pattern, count: page_size)
57
+ end
58
+
59
+ # NOTE: When debugging, check the last item in the returned array.
60
+ [
61
+ total_size.to_i,
62
+ result[0].to_i, # next_cursor
63
+ result[1].map { |entry| load_json(entry) }.select { |entry| entry.is_a?(Hash) },
64
+ ]
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ module SidekiqUniqueJobs
6
+ #
7
+ # Command line interface for unique jobs
8
+ #
9
+ # @author Mikael Henriksson <mikael@mhenrixon.com>
10
+ #
11
+ class Cli < Thor
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
16
+ end
17
+ # rubocop:enable Style/OptionalBooleanParameter
18
+
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)
28
+ end
29
+
30
+ desc "del PATTERN", "deletes unique digests from redis by pattern"
31
+ option :dry_run, aliases: :d, type: :boolean, desc: "set to false to perform deletion"
32
+ option :count, aliases: :c, type: :numeric, default: 1000, desc: "The max number of digests to return"
33
+ # :nodoc:
34
+ def del(pattern)
35
+ max_count = options[:count]
36
+ if options[:dry_run]
37
+ count_entries_for_del(max_count, pattern)
38
+ else
39
+ del_entries(max_count, pattern)
40
+ end
41
+ end
42
+
43
+ desc "console", "drop into a console with easy access to helper methods"
44
+ # :nodoc:
45
+ def console
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
51
+ console_class.start
52
+ end
53
+
54
+ no_commands do # rubocop:disable Metrics/BlockLength
55
+ # :nodoc:
56
+ def digests
57
+ @digests ||= SidekiqUniqueJobs::Digests.new
58
+ end
59
+
60
+ # :nodoc:
61
+ def expiring_digests
62
+ @expiring_digests ||= SidekiqUniqueJobs::ExpiringDigests.new
63
+ end
64
+
65
+ # :nodoc:
66
+ def console_class
67
+ require "pry"
68
+ Pry
69
+ rescue NameError, LoadError
70
+ require "irb"
71
+ IRB
72
+ end
73
+
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}'"
92
+ end
93
+ end
94
+ end
95
+ end
@@ -1,46 +1,319 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SidekiqUniqueJobs
2
- class Config < OpenStruct
3
- CONFIG_ACCESSORS = [
4
- :unique_prefix,
5
- :unique_args_enabled,
6
- :default_expiration,
7
- :default_unlock_order
8
- ]
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"
9
135
 
10
- class << self
11
- CONFIG_ACCESSORS.each do |method|
12
- define_method(method) do
13
- warn("#{method} has been deprecated. See readme for information")
14
- config.send(method)
15
- end
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
16
231
 
17
- define_method("#{method}=") do |obj|
18
- warn("#{method} has been deprecated. See readme for information")
19
- config.send("#{method}=", obj)
20
- end
21
- end
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
22
266
 
23
- def unique_args_enabled?
24
- warn('unique_args_enabled has been deprecated. See readme for information')
25
- config.unique_args_enabled
26
- end
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)
27
283
 
28
- def config
29
- SidekiqUniqueJobs.config
30
- end
284
+ new_locks = locks.dup.merge(lock_sym => klass).freeze
285
+ self.locks = new_locks
31
286
  end
32
287
 
33
- def testing_enabled?
34
- if Sidekiq.const_defined?('Testing') && Sidekiq::Testing.enabled?
35
- require 'sidekiq_unique_jobs/testing'
36
- return true
37
- end
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)
38
302
 
39
- false
303
+ new_strategies = strategies.dup.merge(strategy_sym => klass).freeze
304
+ self.strategies = new_strategies
40
305
  end
41
306
 
42
- def unique_args_enabled?
43
- config.unique_args_enabled
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
44
316
  end
45
317
  end
318
+ # rubocop:enable Metrics/ClassLength
46
319
  end