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

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.

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