sidekiq-unique-jobs 7.0.0.beta2 → 7.0.0.beta3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f3b55e52f9a512b127403e97a0bfe243f24174db6d31d03da0876c2cb285ed57
4
- data.tar.gz: 60968aa5ffa0125fad0a35f2dadf2372b1f3b2cb0b529a26c036f08617526250
3
+ metadata.gz: f5f537750acf3b6a7b8f25ede43519210cc05dc77069417539a54c0c2d0c114c
4
+ data.tar.gz: 367d2a7f27d10200d4eb2b148b2ea34fbdffe16eb52ae71cfe73b5b20d2822d8
5
5
  SHA512:
6
- metadata.gz: 41441c9e7280b8f3bda892879e29077778c0f99e0573dd478ee1f65c462c1b1a13b1573b7e3d74cdcf60c67cfa6e86f39ee366693b9c9396cfc9d8d54e2cbffe
7
- data.tar.gz: 8e9b5d486a9f2714fdeff5968dfd9964c68bcf6e9d07af40488f800f76f40e56dc6040b48e0aa0369e178e3678f2d4692ab4221615b6a41cefff830cbc8f0e10
6
+ metadata.gz: 8fe2296811e1d1d8350c51d232261e61879317a6a4c46a86a302dfac9a0ad341191086ac96db74430d51ead315e370f1073f6015098fff09d5995abcc40d5146
7
+ data.tar.gz: 6fd909cfb0c8960cc1cb14bbd336493897121d24801ad1c2b678733666fba76a3ea0a5d6868e814c9f426080ff027fdf6102ffee08a2108e2ee840496a493717
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [v7.0.0.beta2](https://github.com/mhenrixon/sidekiq-unique-jobs/tree/v7.0.0.beta2) (2019-10-08)
4
+
5
+ [Full Changelog](https://github.com/mhenrixon/sidekiq-unique-jobs/compare/v7.0.0.beta1...v7.0.0.beta2)
6
+
7
+ **Fixed bugs:**
8
+
9
+ - Pass redis\_version into scripts [\#431](https://github.com/mhenrixon/sidekiq-unique-jobs/pull/431) ([mhenrixon](https://github.com/mhenrixon))
10
+
11
+ **Closed issues:**
12
+
13
+ - incorrect `:until\_and\_while\_executing` behavior [\#424](https://github.com/mhenrixon/sidekiq-unique-jobs/issues/424)
14
+
3
15
  ## [v7.0.0.beta1](https://github.com/mhenrixon/sidekiq-unique-jobs/tree/v7.0.0.beta1) (2019-10-07)
4
16
 
5
17
  [Full Changelog](https://github.com/mhenrixon/sidekiq-unique-jobs/compare/v6.0.15...v7.0.0.beta1)
data/README.md CHANGED
@@ -418,7 +418,7 @@ The last one is log which can be be used with the lock `UntilExecuted` and `Unti
418
418
  ### log
419
419
 
420
420
  ```ruby
421
- sidekiq_options on_conflict: :log`
421
+ sidekiq_options on_conflict: :log
422
422
  ```
423
423
 
424
424
  This strategy is intended to be used with `UntilExecuted` and `UntilExpired`. It will log a line about that this is job is a duplicate of another.
@@ -426,7 +426,7 @@ This strategy is intended to be used with `UntilExecuted` and `UntilExpired`. It
426
426
  ### raise
427
427
 
428
428
  ```ruby
429
- sidekiq_options on_conflict: :raise`
429
+ sidekiq_options on_conflict: :raise
430
430
  ```
431
431
 
432
432
  This strategy is intended to be used with `WhileExecuting`. Basically it will allow us to let the server process crash with a specific error message and be retried without messing up the Sidekiq stats.
@@ -434,7 +434,7 @@ This strategy is intended to be used with `WhileExecuting`. Basically it will al
434
434
  ### reject
435
435
 
436
436
  ```ruby
437
- sidekiq_options on_conflict: :reject`
437
+ sidekiq_options on_conflict: :reject
438
438
  ```
439
439
 
440
440
  This strategy is intended to be used with `WhileExecuting` and will push the job to the dead queue on conflict.
@@ -442,7 +442,7 @@ This strategy is intended to be used with `WhileExecuting` and will push the job
442
442
  ### replace
443
443
 
444
444
  ```ruby
445
- sidekiq_options on_conflict: :replace`
445
+ sidekiq_options on_conflict: :replace
446
446
  ```
447
447
 
448
448
  This strategy is intended to be used with client locks like `UntilExecuted`.
@@ -455,7 +455,7 @@ always scheduled in the future. Currently only attempting to retry one time.
455
455
  ### Reschedule
456
456
 
457
457
  ```ruby
458
- sidekiq_options on_conflict: :reschedule`
458
+ sidekiq_options on_conflict: :reschedule
459
459
  ```
460
460
 
461
461
  This strategy is intended to be used with `WhileExecuting` and will delay the job to be tried again in 5 seconds. This will mess up the sidekiq stats but will prevent exceptions from being logged and confuse your sysadmins.
@@ -475,7 +475,7 @@ module Strategies
475
475
  end
476
476
  ```
477
477
 
478
- You can refer on all the startegies defined in `lib/sidekiq_unique_jobs/on_conflict`.
478
+ You can refer to all the strategies defined in `lib/sidekiq_unique_jobs/on_conflict`.
479
479
 
480
480
  In order to make it available, you should call in your project startup:
481
481
 
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "brpoplpush/redis_script"
3
4
  require "concurrent/future"
4
5
  require "concurrent/promises"
5
6
  require "concurrent/timer_task"
@@ -25,7 +26,6 @@ require "sidekiq_unique_jobs/connection"
25
26
  require "sidekiq_unique_jobs/exceptions"
26
27
  require "sidekiq_unique_jobs/script"
27
28
  require "sidekiq_unique_jobs/script/caller"
28
- require "sidekiq_unique_jobs/script/template"
29
29
  require "sidekiq_unique_jobs/json"
30
30
  require "sidekiq_unique_jobs/normalizer"
31
31
  require "sidekiq_unique_jobs/job"
@@ -82,89 +82,6 @@ module SidekiqUniqueJobs
82
82
  end
83
83
  end
84
84
 
85
- # Error raised from {OnConflict::Raise}
86
- #
87
- # @author Mikael Henriksson <mikael@zoolutions.se>
88
- class ScriptError < UniqueJobsError
89
- # Reformats errors raised by redis representing failures while executing
90
- # a lua script. The default errors have confusing messages and backtraces,
91
- # and a type of +RuntimeError+. This class improves the message and
92
- # modifies the backtrace to include the lua script itself in a reasonable
93
- # way.
94
-
95
- PATTERN = /ERR Error (compiling|running) script \(.*?\): .*?:(\d+): (.*)/.freeze
96
- LIB_PATH = File.expand_path("..", __dir__)
97
- CONTEXT_LINE_NUMBER = 3
98
-
99
- attr_reader :error, :file, :content
100
-
101
- # Is this error one that should be reformatted?
102
- #
103
- # @param error [StandardError] the original error raised by redis
104
- # @return [Boolean] is this an error that should be reformatted?
105
- def self.intercepts?(error)
106
- error.message =~ PATTERN
107
- end
108
-
109
- # Initialize a new {ScriptError} from an existing redis error, adjusting
110
- # the message and backtrace in the process.
111
- #
112
- # @param error [StandardError] the original error raised by redis
113
- # @param file [Pathname] full path to the lua file the error ocurred in
114
- # @param content [String] lua file content the error ocurred in
115
- # :nocov:
116
- def initialize(error, file, content)
117
- @error = error
118
- @file = file
119
- @content = content
120
- @backtrace = @error.backtrace
121
-
122
- @error.message.match(PATTERN) do |regexp_match|
123
- line_number = regexp_match[2].to_i
124
- message = regexp_match[3]
125
- error_context = generate_error_context(content, line_number)
126
-
127
- super("#{message}\n\n#{error_context}\n\n")
128
- set_backtrace(generate_backtrace(file, line_number))
129
- end
130
- end
131
-
132
- private
133
-
134
- # :nocov:
135
- def generate_error_context(content, line_number)
136
- lines = content.lines.to_a
137
- beginning_line_number = [1, line_number - CONTEXT_LINE_NUMBER].max
138
- ending_line_number = [lines.count, line_number + CONTEXT_LINE_NUMBER].min
139
- line_number_width = ending_line_number.to_s.length
140
-
141
- (beginning_line_number..ending_line_number).map do |number|
142
- indicator = (number == line_number) ? "=>" : " "
143
- formatted_number = format("%#{line_number_width}d", number)
144
- " #{indicator} #{formatted_number}: #{lines[number - 1]}"
145
- end.join.chomp
146
- end
147
-
148
- # :nocov:
149
- def generate_backtrace(file, line_number)
150
- pre_gem = backtrace_before_entering_gem(@backtrace)
151
- index_of_first_unique_jobs_line = (@backtrace.size - pre_gem.size - 1)
152
- pre_gem.unshift(@backtrace[index_of_first_unique_jobs_line])
153
- pre_gem.unshift("#{file}:#{line_number}")
154
- pre_gem
155
- end
156
-
157
- # :nocov:
158
- def backtrace_before_entering_gem(backtrace)
159
- backtrace.reverse.take_while { |line| !line_from_gem(line) }.reverse
160
- end
161
-
162
- # :nocov:
163
- def line_from_gem(line)
164
- line.split(":").first.include?(LIB_PATH)
165
- end
166
- end
167
-
168
85
  # Error raised from {OptionsWithFallback#lock_class}
169
86
  #
170
87
  # @author Mikael Henriksson <mikael@zoolutions.se>
@@ -2,6 +2,11 @@
2
2
 
3
3
  module SidekiqUniqueJobs
4
4
  class Lock
5
+ #
6
+ # UntilExpired locks until the job expires
7
+ #
8
+ # @author Mikael Henriksson <mikael@zoolutions.se>
9
+ #
5
10
  class UntilExpired < UntilExecuted
6
11
  end
7
12
  end
@@ -19,7 +19,7 @@ module SidekiqUniqueJobs
19
19
  #
20
20
  def self.start
21
21
  with_logging_context do
22
- logger.info("Starting Reaper")
22
+ log_info("Starting Reaper")
23
23
  task.add_observer(Observer.new)
24
24
  task.execute
25
25
  task
@@ -34,7 +34,7 @@ module SidekiqUniqueJobs
34
34
  #
35
35
  def self.stop
36
36
  with_logging_context do
37
- logger.info("Stopping Reaper")
37
+ log_info("Stopping Reaper")
38
38
  task.shutdown
39
39
  end
40
40
  end
@@ -175,11 +175,43 @@ module SidekiqUniqueJobs
175
175
  #
176
176
  #
177
177
  def enqueued?(digest)
178
- if (result = call_script(:find_digest_in_queues, conn, keys: [digest]))
179
- log_debug("#{digest} found in #{result}")
180
- true
181
- else
182
- log_debug("#{digest} NOT found in any queues")
178
+ Sidekiq.redis do |conn|
179
+ queues(conn) do |queue|
180
+ entries(conn, queue) do |entry|
181
+ if entry.include?(digest)
182
+ log_info("#{digest} found in #{queue}")
183
+ return true
184
+ end
185
+ end
186
+ end
187
+
188
+ log_info("#{digest} not enqueued")
189
+ false
190
+ end
191
+ end
192
+
193
+ def queues(conn, &block)
194
+ conn.sscan_each("queues", &block)
195
+ end
196
+
197
+ def entries(conn, queue) # rubocop:disable Metrics/MethodLength
198
+ queue_key = "queue:#{queue}"
199
+ initial_size = conn.llen(queue_key)
200
+ deleted_size = 0
201
+ page = 0
202
+ page_size = 50
203
+
204
+ loop do
205
+ range_start = page * page_size - deleted_size
206
+ range_end = range_start + page_size - 1
207
+ entries = conn.lrange(queue_key, range_start, range_end)
208
+ page += 1
209
+
210
+ entries.each do |entry|
211
+ yield entry
212
+ end
213
+
214
+ deleted_size = initial_size - size
183
215
  end
184
216
  end
185
217
 
@@ -5,128 +5,11 @@ module SidekiqUniqueJobs
5
5
  #
6
6
  # @author Mikael Henriksson <mikael@zoolutions.se>
7
7
  module Script
8
- LUA_PATHNAME ||= Pathname.new(__FILE__).dirname.join("lua").freeze
9
- SCRIPT_SHAS ||= Concurrent::Map.new
8
+ include Brpoplpush::RedisScript::DSL
10
9
 
11
- extend SidekiqUniqueJobs::Connection
12
- extend SidekiqUniqueJobs::Logging
13
- extend SidekiqUniqueJobs::Timing
14
-
15
- module_function
16
-
17
- #
18
- # Call a lua script with the provided file_name
19
- #
20
- # @note this method is recursive if we need to load a lua script
21
- # that wasn't previously loaded.
22
- #
23
- # @param [Symbol] file_name the name of the lua script
24
- # @param [Array<String>] keys script keys
25
- # @param [Array<Object>] argv script arguments
26
- # @param [Redis] conn the redis connection to use
27
- #
28
- # @return value from script
29
- #
30
- def call(file_name, conn, keys: [], argv: [])
31
- result, elapsed = timed do
32
- execute_script(file_name, conn, keys, argv)
33
- end
34
-
35
- log_debug("Executed #{file_name}.lua in #{elapsed}ms")
36
- result
37
- rescue ::Redis::CommandError => ex
38
- handle_error(ex, file_name, conn) do
39
- call(file_name, conn, keys: keys, argv: argv)
40
- end
41
- end
42
-
43
- #
44
- # Execute the script file
45
- #
46
- # @param [Symbol] file_name the name of the lua script
47
- # @param [Redis] conn the redis connection to use
48
- # @param [Array] keys the array of keys to pass to the script
49
- # @param [Array] argv the array of arguments to pass to the script
50
- #
51
- # @return value from script (evalsha)
52
- #
53
- def execute_script(file_name, conn, keys, argv)
54
- conn.evalsha(
55
- script_sha(conn, file_name),
56
- keys,
57
- argv,
58
- )
59
- end
60
-
61
- #
62
- # Return sha of already loaded lua script or load it and return the sha
63
- #
64
- # @param [Sidekiq::RedisConnection] conn the redis connection
65
- # @param [Symbol] file_name the name of the lua script
66
- # @return [String] sha of the script file
67
- #
68
- # @return [String] the sha of the script
69
- #
70
- def script_sha(conn, file_name)
71
- if (sha = SCRIPT_SHAS.get(file_name))
72
- return sha
73
- end
74
-
75
- sha = conn.script(:load, script_source(file_name))
76
- SCRIPT_SHAS.put(file_name, sha)
77
- sha
78
- end
79
-
80
- #
81
- # Handle errors to allow retrying errors that need retrying
82
- #
83
- # @param [Redis::CommandError] ex exception to handle
84
- # @param [Symbol] file_name the name of the lua script
85
- # @param [Redis] conn the redis connection to use
86
- #
87
- # @return [void]
88
- #
89
- # @yieldreturn [void] yields back to the caller when NOSCRIPT is raised
90
- def handle_error(ex, file_name, conn) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
91
- case ex.message
92
- when "NOSCRIPT No matching script. Please use EVAL."
93
- SCRIPT_SHAS.delete(file_name)
94
- return yield if block_given?
95
- when "BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE."
96
- begin
97
- conn.script(:kill)
98
- return yield if block_given?
99
- rescue ::Redis::CommandError => ex
100
- log_warn(ex)
101
- return yield if block_given?
102
- end
103
- end
104
-
105
- raise unless ScriptError.intercepts?(ex)
106
-
107
- raise ScriptError.new(ex, script_path(file_name).to_s, script_source(file_name))
108
- end
109
-
110
- #
111
- # Reads the lua file from disk
112
- #
113
- # @param [Symbol] file_name the name of the lua script
114
- #
115
- # @return [String] the content of the lua file
116
- #
117
- def script_source(file_name)
118
- Template.new(LUA_PATHNAME).render(script_path(file_name))
119
- end
120
-
121
- #
122
- # Construct a Pathname to a lua script
123
- #
124
- # @param [Symbol] file_name the name of the lua script
125
- #
126
- # @return [Pathname] the full path to the gems lua script
127
- #
128
- def script_path(file_name)
129
- LUA_PATHNAME.join("#{file_name}.lua")
10
+ configure do |config|
11
+ config.scripts_path = Pathname.new(__FILE__).dirname.join("lua")
12
+ config.logger = Sidekiq.logger # TODO: This becomes a little weird
130
13
  end
131
14
  end
132
15
  end
@@ -59,7 +59,7 @@ module SidekiqUniqueJobs
59
59
  file_name,
60
60
  redis_version,
61
61
  ])
62
- Script.call(file_name, conn, keys: keys, argv: argv)
62
+ Script.execute(file_name, conn, keys: keys, argv: argv)
63
63
  end
64
64
 
65
65
  #
@@ -3,5 +3,5 @@
3
3
  module SidekiqUniqueJobs
4
4
  #
5
5
  # @return [String] the current SidekiqUniqueJobs version
6
- VERSION = "7.0.0.beta2"
6
+ VERSION = "7.0.0.beta3"
7
7
  end
metadata CHANGED
@@ -1,15 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-unique-jobs
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.0.0.beta2
4
+ version: 7.0.0.beta3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikael Henriksson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-08 00:00:00.000000000 Z
11
+ date: 2019-11-24 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: brpoplpush-redis_script
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.0.0
20
+ - - "<="
21
+ - !ruby/object:Gem::Version
22
+ version: 2.0.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">"
28
+ - !ruby/object:Gem::Version
29
+ version: 0.0.0
30
+ - - "<="
31
+ - !ruby/object:Gem::Version
32
+ version: 2.0.0
13
33
  - !ruby/object:Gem::Dependency
14
34
  name: concurrent-ruby
15
35
  requirement: !ruby/object:Gem::Requirement
@@ -309,7 +329,6 @@ files:
309
329
  - lib/sidekiq_unique_jobs/rspec/matchers/have_valid_sidekiq_options.rb
310
330
  - lib/sidekiq_unique_jobs/script.rb
311
331
  - lib/sidekiq_unique_jobs/script/caller.rb
312
- - lib/sidekiq_unique_jobs/script/template.rb
313
332
  - lib/sidekiq_unique_jobs/sidekiq_unique_ext.rb
314
333
  - lib/sidekiq_unique_jobs/sidekiq_unique_jobs.rb
315
334
  - lib/sidekiq_unique_jobs/sidekiq_worker_methods.rb
@@ -345,14 +364,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
345
364
  requirements:
346
365
  - - ">="
347
366
  - !ruby/object:Gem::Version
348
- version: '0'
367
+ version: 2.5.0
349
368
  required_rubygems_version: !ruby/object:Gem::Requirement
350
369
  requirements:
351
370
  - - ">"
352
371
  - !ruby/object:Gem::Version
353
372
  version: 1.3.1
354
373
  requirements: []
355
- rubygems_version: 3.0.6
374
+ rubygems_version: 3.0.3
356
375
  signing_key:
357
376
  specification_version: 4
358
377
  summary: Sidekiq middleware that prevents duplicates jobs
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SidekiqUniqueJobs
4
- # Interface to dealing with .lua files
5
- #
6
- # @author Mikael Henriksson <mikael@zoolutions.se>
7
- module Script
8
- #
9
- # Class Template provides LUA script partial template rendering
10
- #
11
- # @author Mikael Henriksson <mikael@zoolutions.se>
12
- #
13
- class Template
14
- def initialize(script_path)
15
- @script_path = script_path
16
- end
17
-
18
- #
19
- # Renders a Lua script and includes any partials in that file
20
- # all `<%= include_partial '' %>` replaced with the actual contents of the partial
21
- #
22
- # @param [Pathname] pathname the path to the
23
- #
24
- # @return [String] the rendered Luascript
25
- #
26
- def render(pathname)
27
- @partial_templates ||= {}
28
- ERB.new(File.read(pathname)).result(binding)
29
- end
30
-
31
- # helper method to include a lua partial within another lua script
32
- #
33
- def include_partial(relative_path)
34
- return if @partial_templates.key?(relative_path)
35
-
36
- @partial_templates[relative_path] = nil
37
- render(Pathname.new("#{@script_path}/#{relative_path}"))
38
- end
39
- end
40
- end
41
- end