resque-unique_in_queue 1.0.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 32f50a9af11ff498a110d4a93549fbb653c2d471252bde302e50c8b7d30eb5f2
4
+ data.tar.gz: 5913f22ba7c2c23666833c86200d181431b2e4f2c9827e363bee62d8dbca18a2
5
+ SHA512:
6
+ metadata.gz: f2905f2489519bff3cd7424827fb67d6ef24bfaba5e164d0c539ebb986c2174d4d8fd38a4e5433447bc171a1313de44a82c9129662e0bfcb0c64ada21fb5868d
7
+ data.tar.gz: 8bda5be739bb307679330cf658d5e8865dedc6295af8d7ec657ac6982d5d4ac1ff394cd943070240d8d8c79c5504d3d7b1431645820c338e8fb67c8666c50633
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1,2 @@
1
+ inherit_from: .rubocop_todo.yml
2
+ TargetRubyVersion: 2.5
@@ -0,0 +1,91 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2018-11-07 04:05:15 -0800 using RuboCop version 0.60.0.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 1
10
+ # Configuration parameters: Include.
11
+ # Include: **/*.gemspec
12
+ Gemspec/DuplicatedAssignment:
13
+ Exclude:
14
+ - 'resque-unique_in_queue.gemspec'
15
+
16
+ # Offense count: 1
17
+ # Configuration parameters: Include.
18
+ # Include: **/*.gemspec
19
+ Gemspec/RequiredRubyVersion:
20
+ Exclude:
21
+ - 'resque-unique_in_queue.gemspec'
22
+
23
+ # Offense count: 1
24
+ # Cop supports --auto-correct.
25
+ # Configuration parameters: EnforcedStyleAlignWith, AutoCorrect, Severity.
26
+ # SupportedStylesAlignWith: start_of_line, def
27
+ Layout/DefEndAlignment:
28
+ Exclude:
29
+ - 'lib/resque/unique_in_queue/queue.rb'
30
+
31
+ # Offense count: 1
32
+ Lint/HandleExceptions:
33
+ Exclude:
34
+ - 'test/test_helper.rb'
35
+
36
+ # Offense count: 2
37
+ # Configuration parameters: CountComments, ExcludedMethods.
38
+ Metrics/MethodLength:
39
+ Max: 11
40
+
41
+ # Offense count: 1
42
+ # Configuration parameters: ExpectMatchingDefinition, Regex, IgnoreExecutableScripts, AllowedAcronyms.
43
+ # AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS
44
+ Naming/FileName:
45
+ Exclude:
46
+ - 'lib/resque-unique_in_queue.rb'
47
+
48
+ # Offense count: 1
49
+ # Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist, MethodDefinitionMacros.
50
+ # NamePrefix: is_, has_, have_
51
+ # NamePrefixBlacklist: is_, has_, have_
52
+ # NameWhitelist: is_a?
53
+ # MethodDefinitionMacros: define_method, define_singleton_method
54
+ Naming/PredicateName:
55
+ Exclude:
56
+ - 'spec/**/*'
57
+ - 'lib/resque/unique_in_queue/queue.rb'
58
+
59
+ # Offense count: 4
60
+ # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
61
+ # AllowedNames: io, id, to, by, on, in, at, ip, db
62
+ Naming/UncommunicativeMethodParamName:
63
+ Exclude:
64
+ - 'test/fake_jobs.rb'
65
+
66
+ # Offense count: 6
67
+ Style/Documentation:
68
+ Exclude:
69
+ - 'spec/**/*'
70
+ - 'test/**/*'
71
+ - 'lib/resque-unique_in_queue.rb'
72
+ - 'lib/resque/plugins/unique_in_queue.rb'
73
+ - 'lib/resque/unique_in_queue/configuration.rb'
74
+ - 'lib/resque/unique_in_queue/queue.rb'
75
+ - 'lib/resque/unique_in_queue/resque_ext/job.rb'
76
+ - 'lib/resque/unique_in_queue/resque_ext/resque.rb'
77
+
78
+ # Offense count: 1
79
+ # Cop supports --auto-correct.
80
+ # Configuration parameters: AutoCorrect, EnforcedStyle, IgnoredMethods.
81
+ # SupportedStyles: predicate, comparison
82
+ Style/NumericPredicate:
83
+ Exclude:
84
+ - 'spec/**/*'
85
+ - 'lib/resque/unique_in_queue/queue.rb'
86
+
87
+ # Offense count: 26
88
+ # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
89
+ # URISchemes: http, https
90
+ Metrics/LineLength:
91
+ Max: 116
@@ -0,0 +1 @@
1
+ ruby-2.5.1
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ sudo: false
3
+ cache: bundler
4
+ rvm:
5
+ - 2.4.0
6
+ - 2.3.3
7
+ - 2.2.6
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ group :test do
8
+ unless ENV['TRAVIS']
9
+ gem 'byebug', '~> 10', platform: :mri, require: false
10
+ gem 'pry', '~> 0', platform: :mri, require: false
11
+ gem 'pry-byebug', '~> 3', platform: :mri, require: false
12
+ end
13
+ gem 'rubocop', '~> 0.60.0'
14
+ gem 'simplecov', '~> 0', require: false
15
+ end
16
+
17
+ # Specify your gem's dependencies in resque-unique_in_queue.gemspec
18
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ Copyright (c) 2010 Moviepilot GmbH http://moviepilot.com
2
+ Copyright (c) 2013 Neighborland, Inc.
3
+ Copyright (c) 2017 - 2018 Peter H. Boling
4
+
5
+ MIT License
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining
8
+ a copy of this software and associated documentation files (the
9
+ "Software"), to deal in the Software without restriction, including
10
+ without limitation the rights to use, copy, modify, merge, publish,
11
+ distribute, sublicense, and/or sell copies of the Software, and to
12
+ permit persons to whom the Software is furnished to do so, subject to
13
+ the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be
16
+ included in all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,158 @@
1
+ # Resque::UniqueInQueue
2
+
3
+ | Project | Resque::UniqueInQueue |
4
+ |------------------------ | ----------------------- |
5
+ | gem name | [resque-unique_in_queue](https://rubygems.org/gems/resque-unique_in_queue) |
6
+ | license | [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) |
7
+ | download rank | [![Downloads Today](https://img.shields.io/gem/rd/resque-unique_in_queue.svg)](https://github.com/pboling/resque-unique_in_queue) |
8
+ | version | [![Version](https://img.shields.io/gem/v/resque-unique_in_queue.svg)](https://rubygems.org/gems/resque-unique_in_queue) |
9
+ | dependencies | [![Depfu](https://badges.depfu.com/badges/25c6e1e4c671926e9adea898f2df9a47/count.svg)](https://depfu.com/github/pboling/resque-unique_in_queue?project_id=2729) |
10
+ | continuous integration | [![Build Status](https://travis-ci.org/pboling/resque-unique_in_queue.svg?branch=master)](https://travis-ci.org/pboling/resque-unique_in_queue) |
11
+ | test coverage | [![Test Coverage](https://api.codeclimate.com/v1/badges/7520df3968eb146c8894/test_coverage)](https://codeclimate.com/github/pboling/resque-unique_in_queue/test_coverage) |
12
+ | maintainability | [![Maintainability](https://api.codeclimate.com/v1/badges/7520df3968eb146c8894/maintainability)](https://codeclimate.com/github/pboling/resque-unique_in_queue/maintainability) |
13
+ | code triage | [![Open Source Helpers](https://www.codetriage.com/pboling/resque-unique_in_queue/badges/users.svg)](https://www.codetriage.com/pboling/resque-unique_in_queue) |
14
+ | homepage | [on Github.com][homepage], [on Railsbling.com][blogpage] |
15
+ | documentation | [on RDoc.info][documentation] |
16
+ | Spread ~♡ⓛⓞⓥⓔ♡~ | [🌍 🌎 🌏](https://about.me/peter.boling), [🍚](https://www.crowdrise.com/helprefugeeswithhopefortomorrowliberia/fundraiser/peterboling), [➕](https://plus.google.com/+PeterBoling/posts), [👼](https://angel.co/peter-boling), [🐛](https://www.topcoder.com/members/pboling/), [:shipit:](http://coderwall.com/pboling), [![Tweet Peter](https://img.shields.io/twitter/follow/galtzo.svg?style=social&label=Follow)](http://twitter.com/galtzo) |
17
+
18
+ Resque::UniqueInQueue is a resque plugin to add unique jobs to resque.
19
+
20
+ It is a re-write of [resque-loner](https://github.com/jayniz/resque-loner).
21
+
22
+ It requires resque 1.25 and works with ruby 2.0 and later.
23
+
24
+ It removes the dependency on `Resque::Helpers`, which is deprecated for resque 2.0.
25
+
26
+ ## Install
27
+
28
+ Add the gem to your Gemfile:
29
+
30
+ ```ruby
31
+ gem 'resque-unique_in_queue'
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ ```ruby
37
+ class UpdateCat
38
+ include Resque::Plugins::UniqueJob
39
+ @queue = :cats
40
+
41
+ def self.perform(cat_id)
42
+ # do something
43
+ end
44
+ end
45
+ ```
46
+
47
+ If you attempt to queue a unique job multiple times, it is ignored:
48
+
49
+ ```
50
+ Resque.enqueue UpdateCat, 1
51
+ => true
52
+ Resque.enqueue UpdateCat, 1
53
+ => nil
54
+ Resque.enqueue UpdateCat, 1
55
+ => nil
56
+ Resque.size :cats
57
+ => 1
58
+ Resque.enqueued? UpdateCat, 1
59
+ => true
60
+ Resque.enqueued_in? :dogs, UpdateCat, 1
61
+ => false
62
+ ```
63
+
64
+ ### Options
65
+
66
+ #### `lock_after_execution_period`
67
+
68
+ By default, lock_after_execution_period is 0 and `enqueued?` becomes false as soon as the job
69
+ is being worked on.
70
+
71
+ The `lock_after_execution_period` setting can be used to delay when the unique job key is deleted
72
+ (i.e. when `enqueued?` becomes `false`). For example, if you have a long-running unique job that
73
+ takes around 10 seconds, and you don't want to requeue another job until you are sure it is done,
74
+ you could set `lock_after_execution_period = 20`. Or if you never want to run a long running
75
+ job more than once per minute, set `lock_after_execution_period = 60`.
76
+
77
+ ```ruby
78
+ class UpdateCat
79
+ include Resque::Plugins::UniqueJob
80
+ @queue = :cats
81
+ @lock_after_execution_period = 20
82
+
83
+ def self.perform(cat_id)
84
+ # do something
85
+ end
86
+ end
87
+ ```
88
+
89
+ #### Oops, I have stale Queue Time uniqueness keys...
90
+
91
+ Preventing jobs with matching signatures from being queued, and they never get
92
+ dequeued because there is no actual corresponding job to dequeue.
93
+
94
+ *How to deal?*
95
+
96
+ Option: Rampage
97
+
98
+ ```ruby
99
+ # Delete *all* queued jobs in the queue, and
100
+ # delete *all* unqueness keys for the queue.
101
+ Redis.remove_queue('queue_name')
102
+ ```
103
+
104
+ Option: Butterfly
105
+
106
+ ```ruby
107
+ # Delete *no* queued jobs at all, and
108
+ # delete *all* unqueness keys for the queue (might then allow duplicates).
109
+ Resque::UniqueInQueue::Queue.cleanup('queue_name')
110
+ ```
111
+
112
+ ## Contributing
113
+
114
+ Bug reports and pull requests are welcome on GitHub at https://github.com/pboling/resque-unique_in_queue. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
115
+
116
+ 1. Fork it
117
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
118
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
119
+ 4. Push to the branch (`git push origin my-new-feature`)
120
+ 5. Create new Pull Request
121
+
122
+ ## Code of Conduct
123
+
124
+ Everyone interacting in the Resque::Plugins::UniqueInQueue project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/pboling/resque-unique_in_queue/blob/master/CODE_OF_CONDUCT.md).
125
+
126
+ ## Versioning
127
+
128
+ This library aims to adhere to [Semantic Versioning 2.0.0][semver].
129
+ Violations of this scheme should be reported as bugs. Specifically,
130
+ if a minor or patch version is released that breaks backward
131
+ compatibility, a new version should be immediately released that
132
+ restores compatibility. Breaking changes to the public API will
133
+ only be introduced with new major versions.
134
+
135
+ As a result of this policy, you can (and should) specify a
136
+ dependency on this gem using the [Pessimistic Version Constraint][pvc] with two digits of precision.
137
+
138
+ For example:
139
+
140
+ ```ruby
141
+ spec.add_dependency 'resque-unique_in_queue', '~> 1.0'
142
+ ```
143
+
144
+ ## License
145
+
146
+ * Copyright (c) 2012 Jonathan R. Wallace
147
+ * Copyright (c) 2017 - 2018 [Peter H. Boling][peterboling] of [Rails Bling][railsbling]
148
+
149
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
150
+
151
+ [license]: LICENSE
152
+ [semver]: http://semver.org/
153
+ [pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint
154
+ [railsbling]: http://www.railsbling.com
155
+ [peterboling]: http://www.peterboling.com
156
+ [documentation]: http://rdoc.info/github/pboling/resque-unique_in_queue/frames
157
+ [homepage]: https://github.com/pboling/resque-unique_in_queue/
158
+ [blogpage]: http://www.railsbling.com/tags/resque-unique_in_queue/
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env rake
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/gem_tasks'
5
+ require 'rake/testtask'
6
+
7
+ desc 'Run tests'
8
+ Rake::TestTask.new(:test) do |t|
9
+ t.libs << 'test'
10
+ t.pattern = 'test/**/*_test.rb'
11
+ t.verbose = false
12
+ end
13
+
14
+ task default: :test
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'resque/unique_in_queue/version'
4
+
5
+ # Ruby Std Lib
6
+ require 'digest/md5'
7
+
8
+ # External Gems
9
+ require 'colorized_string'
10
+ require 'resque'
11
+
12
+ # This Gem
13
+ require 'resque/plugins/unique_in_queue'
14
+ require 'resque/unique_in_queue/resque_ext/job'
15
+ require 'resque/unique_in_queue/resque_ext/resque'
16
+ require 'resque/unique_in_queue/queue'
17
+ require 'resque/unique_in_queue/configuration'
18
+
19
+ # See lib/resque/plugins/unique_in_queue.rb for the actual plugin
20
+ #
21
+ # This is not that ^. Rather, it is an API used by the plugin or as tools by a
22
+ # developer. These methods are not intended to be included/extended into
23
+ # Resque, Resque::Job, or Resque::Queue.
24
+ module Resque
25
+ module UniqueInQueue
26
+ PLUGIN_TAG = (ColorizedString['[R-UIQ] '].blue).freeze
27
+
28
+ def in_queue_unique_log(message, config_proxy = nil)
29
+ config_proxy ||= uniqueness_configuration
30
+ config_proxy.unique_logger&.send(config_proxy.unique_log_level, message) if config_proxy.unique_logger
31
+ end
32
+
33
+ def in_queue_unique_debug(message, config_proxy = nil)
34
+ config_proxy ||= uniqueness_configuration
35
+ config_proxy.unique_logger&.debug("#{PLUGIN_TAG}#{message}") if config_proxy.debug_mode
36
+ end
37
+
38
+ # There are times when the class will need access to the configuration object,
39
+ # such as to override it per instance method
40
+ def uniq_config
41
+ @uniqueness_configuration
42
+ end
43
+
44
+ # For per-class config with a block
45
+ def uniqueness_configure
46
+ @uniqueness_configuration ||= Configuration.new
47
+ yield(@uniqueness_configuration)
48
+ end
49
+
50
+ #### CONFIG ####
51
+ class << self
52
+ attr_accessor :uniqueness_configuration
53
+ end
54
+ def uniqueness_config_reset(config = Configuration.new)
55
+ @uniqueness_configuration = config
56
+ end
57
+
58
+ def uniqueness_log_level
59
+ @uniqueness_configuration.log_level
60
+ end
61
+
62
+ def uniqueness_log_level=(log_level)
63
+ @uniqueness_configuration.log_level = log_level
64
+ end
65
+
66
+ self.uniqueness_configuration = Configuration.new # setup defaults
67
+
68
+ module_function(:in_queue_unique_log,
69
+ :in_queue_unique_debug,
70
+ :uniq_config,
71
+ :uniqueness_configure,
72
+ :uniqueness_config_reset,
73
+ :uniqueness_log_level,
74
+ :uniqueness_log_level=)
75
+ end
76
+ end
@@ -0,0 +1,72 @@
1
+ module Resque
2
+ module Plugins
3
+ # If you want your job to support uniqueness at enqueue-time, simply include
4
+ # this module into your job class.
5
+ #
6
+ # class EnqueueAlone
7
+ # @queue = :enqueue_alone
8
+ # include Resque::Plugins::UniqueInQueue
9
+ #
10
+ # def self.perform(arg1, arg2)
11
+ # alone_stuff
12
+ # end
13
+ # end
14
+ #
15
+ module UniqueInQueue
16
+ def self.included(base)
17
+ base.extend ClassMethods
18
+ end
19
+
20
+ module ClassMethods
21
+ def unique_in_queue_redis_key(queue, item)
22
+ "#{unique_in_queue_key_base}:queue:#{queue}:job:#{Resque::UniqueInQueue::Queue.const_for(item).redis_key(item)}"
23
+ end
24
+
25
+ # Payload is what Resque stored for this job along with the job's class name:
26
+ # a hash containing string keys 'class' and 'args'
27
+ def redis_key(payload)
28
+ payload = Resque.decode(Resque.encode(payload))
29
+ job = payload['class']
30
+ args = payload['args']
31
+ args.map! do |arg|
32
+ arg.is_a?(Hash) ? arg.sort : arg
33
+ end
34
+
35
+ Digest::MD5.hexdigest Resque.encode(class: job, args: args)
36
+ end
37
+
38
+ # The default ttl of a locking key is -1 (forever).
39
+ # To expire the lock after a certain amount of time, set a ttl (in seconds).
40
+ # For example:
41
+ #
42
+ # class FooJob
43
+ # include Resque::Plugins::UniqueJob
44
+ # @ttl = 40
45
+ # end
46
+ def ttl
47
+ @ttl ||= Resque::UniqueInQueue.uniq_config&.ttl
48
+ end
49
+
50
+ # The default ttl of a persisting key is 0, i.e. immediately deleted.
51
+ # Set lock_after_execution_period to block the execution
52
+ # of the job for a certain amount of time (in seconds).
53
+ # For example:
54
+ #
55
+ # class FooJob
56
+ # include Resque::Plugins::UniqueJob
57
+ # @lock_after_execution_period = 40
58
+ # end
59
+ def lock_after_execution_period
60
+ @lock_after_execution_period ||= Resque::UniqueInQueue.uniq_config&.lock_after_execution_period
61
+ end
62
+
63
+ # Can't be overridden per each class because it wouldn't make sense.
64
+ # It wouldn't be able to determine or enforce uniqueness across queues,
65
+ # and general cleanup of stray keys would be nearly impossible.
66
+ def unique_in_queue_key_base
67
+ Resque::UniqueInQueue.uniq_config&.unique_in_queue_key_base
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,49 @@
1
+ require 'logger'
2
+ module Resque
3
+ module UniqueInQueue
4
+ class Configuration
5
+ DEFAULT_IN_QUEUE_KEY_BASE = 'r-uiq'.freeze
6
+ DEFAULT_LOCK_AFTER_EXECUTION_PERIOD = 0
7
+ DEFAULT_TTL = -1
8
+
9
+ attr_accessor :logger,
10
+ :log_level,
11
+ :unique_in_queue_key_base,
12
+ :lock_after_execution_period,
13
+ :ttl,
14
+ :debug_mode
15
+ def initialize(**options)
16
+ @logger = options.key?(:logger) ? options[:logger] : Logger.new(STDOUT)
17
+ @log_level = options.key?(:log_level) ? options[:log_level] : :debug
18
+
19
+ # Can't be set per job:
20
+ @unique_in_queue_key_base = options.key?(:unique_in_queue_key_base) ? options[:unique_in_queue_key_base] : DEFAULT_IN_QUEUE_KEY_BASE
21
+
22
+ # Can be set per each job:
23
+ @lock_after_execution_period = options.key?(:lock_after_execution_period) ? options[:lock_after_execution_period] : DEFAULT_LOCK_AFTER_EXECUTION_PERIOD
24
+ @ttl = options.key?(:ttl) ? options[:ttl] : DEFAULT_TTL
25
+ env_debug = ENV['RESQUE_DEBUG']
26
+ @debug_mode = options.key?(:debug_mode) ? options[:debug_mode] : env_debug == 'true' || (env_debug.is_a?(String) && env_debug.match?(/in_queue/))
27
+ end
28
+
29
+ def unique_logger
30
+ logger
31
+ end
32
+
33
+ def unique_log_level
34
+ log_level
35
+ end
36
+
37
+ def log(msg)
38
+ Resque::UniqueInQueue.in_queue_unique_log(msg, self)
39
+ end
40
+
41
+ def to_hash
42
+ {
43
+ logger: logger,
44
+ log_level: log_level
45
+ }
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,90 @@
1
+ module Resque
2
+ module UniqueInQueue
3
+ module Queue
4
+ def queued?(queue, item)
5
+ return false unless is_unique?(item)
6
+
7
+ redis.get(unique_key(queue, item)) == '1'
8
+ end
9
+
10
+ def mark_queued(queue, item)
11
+ return unless is_unique?(item)
12
+
13
+ key = unique_key(queue, item)
14
+ redis.set(key, 1)
15
+ ttl = item_ttl(item)
16
+ redis.expire(key, ttl) if ttl >= 0
17
+ end
18
+
19
+ def mark_unqueued(queue, job)
20
+ item = job.is_a?(Resque::Job) ? job.payload : job
21
+ return unless is_unique?(item)
22
+
23
+ ttl = lock_after_execution_period(item)
24
+ if ttl == 0
25
+ redis.del(unique_key(queue, item))
26
+ else
27
+ redis.expire(unique_key(queue, item), ttl)
28
+ end
29
+ end
30
+
31
+ def unique_key(queue, item)
32
+ const_for(item).unique_in_queue_redis_key(queue, item)
33
+ end
34
+
35
+ def is_unique?(item)
36
+ const_for(item).included_modules.include?(::Resque::Plugins::UniqueInQueue)
37
+ rescue NameError
38
+ false
39
+ end
40
+
41
+ def item_ttl(item)
42
+ const_for(item).ttl
43
+ rescue NameError
44
+ -1
45
+ end
46
+
47
+ def lock_after_execution_period(item)
48
+ const_for(item).lock_after_execution_period
49
+ rescue NameError
50
+ 0
51
+ end
52
+
53
+ def destroy(queue, klass, *args)
54
+ klass = klass.to_s
55
+ redis_queue = "queue:#{queue}"
56
+
57
+ redis.lrange(redis_queue, 0, -1).each do |string|
58
+ json = Resque.decode(string)
59
+ next unless json['class'] == klass
60
+ next if args.any? && json['args'] != args
61
+
62
+ Resque::UniqueInQueue::Queue.mark_unqueued(queue, json)
63
+ end
64
+ end
65
+
66
+ def cleanup(queue)
67
+ keys = redis.keys("#{Resque::UniqueInQueue.uniq_config&.unique_in_queue_key_base}:queue:#{queue}:job:*")
68
+ redis.del(*keys) if keys.any?
69
+ end
70
+
71
+ private
72
+
73
+ def redis
74
+ Resque.redis
75
+ end
76
+
77
+ def item_class(item)
78
+ item[:class] || item['class']
79
+ end
80
+
81
+ def const_for(item)
82
+ Resque.constantize(item_class(item))
83
+ end
84
+
85
+ module_function :queued?, :mark_queued, :mark_unqueued, :unique_key
86
+ module_function :is_unique?, :item_ttl, :lock_after_execution_period
87
+ module_function :destroy, :cleanup, :redis, :item_class, :const_for
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,64 @@
1
+ module Resque
2
+ class Job
3
+ class << self
4
+ # Mark an item as queued
5
+ def create_unique_in_queue(queue, klass, *args)
6
+ item = { class: klass.to_s, args: args }
7
+ if Resque.inline? || !Resque::UniqueInQueue::Queue.is_unique?(item)
8
+ return create_without_unique_in_queue(queue, klass, *args)
9
+ end
10
+ return 'EXISTED' if Resque::UniqueInQueue::Queue.queued?(queue, item)
11
+
12
+ create_return_value = false
13
+ # redis transaction block
14
+ Resque.redis.multi do
15
+ create_return_value = create_without_unique_in_queue(queue, klass, *args)
16
+ Resque::UniqueInQueue::Queue.mark_queued(queue, item)
17
+ end
18
+ create_return_value
19
+ end
20
+
21
+ # Mark an item as unqueued
22
+ def reserve_unique_in_queue(queue)
23
+ item = reserve_without_unique_in_queue(queue)
24
+ Resque::UniqueInQueue::Queue.mark_unqueued(queue, item) if item && !Resque.inline?
25
+ item
26
+ end
27
+
28
+ # Mark destroyed jobs as unqueued
29
+ def destroy_unique_in_queue(queue, klass, *args)
30
+ Resque::UniqueInQueue::Queue.destroy(queue, klass, *args) unless Resque.inline?
31
+ destroy_without_unique_in_queue(queue, klass, *args)
32
+ end
33
+
34
+ alias create_without_unique_in_queue create
35
+ alias create create_unique_in_queue
36
+ alias reserve_without_unique_in_queue reserve
37
+ alias reserve reserve_unique_in_queue
38
+ alias destroy_without_unique_in_queue destroy
39
+ alias destroy destroy_unique_in_queue
40
+
41
+ if defined?(Resque::Plugins::PriorityEnqueue::Resque)
42
+ # Hack to support resque-priority_enqueue: https://github.com/coupa/resque-priority_enqueue
43
+ def priority_create_unique_in_queue(queue, klass, *args)
44
+ item = { class: klass.to_s, args: args }
45
+ if Resque.inline? || !Resque::UniqueInQueue::Queue.is_unique?(item)
46
+ return priority_create_without_unique_in_queue(queue, klass, *args)
47
+ end
48
+ return 'EXISTED' if Resque::UniqueInQueue::Queue.queued?(queue, item)
49
+
50
+ priority_create_return_value = false
51
+ # redis transaction block
52
+ Resque.redis.multi do
53
+ priority_create_return_value = priority_create_without_unique_in_queue(queue, klass, *args)
54
+ Resque::UniqueInQueue::Queue.mark_queued(queue, item)
55
+ end
56
+ priority_create_return_value
57
+ end
58
+
59
+ alias priority_create_without_unique_in_queue priority_create
60
+ alias priority_create priority_create_unique_in_queue
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,41 @@
1
+ module Resque
2
+ class << self
3
+ # Override
4
+ # https://github.com/resque/resque/blob/master/lib/resque.rb
5
+ def enqueue_to(queue, klass, *args)
6
+ # Perform before_enqueue hooks. Don't perform enqueue if any hook returns false
7
+ before_hooks = Plugin.before_enqueue_hooks(klass).collect do |hook|
8
+ klass.send(hook, *args)
9
+ end
10
+ return nil if before_hooks.any? { |result| result == false }
11
+
12
+ result = Job.create(queue, klass, *args)
13
+ return nil if result == 'EXISTED'
14
+
15
+ Plugin.after_enqueue_hooks(klass).each do |hook|
16
+ klass.send(hook, *args)
17
+ end
18
+
19
+ true
20
+ end
21
+
22
+ def enqueued?(klass, *args)
23
+ enqueued_in?(queue_from_class(klass), klass, *args)
24
+ end
25
+
26
+ def enqueued_in?(queue, klass, *args)
27
+ item = { class: klass.to_s, args: args }
28
+ return nil unless Resque::UniqueInQueue::Queue.is_unique?(item)
29
+
30
+ Resque::UniqueInQueue::Queue.queued?(queue, item)
31
+ end
32
+
33
+ def remove_queue_with_unique_in_queue_cleanup(queue)
34
+ remove_queue_without_unique_in_queue_cleanup(queue)
35
+ Resque::UniqueInQueue::Queue.cleanup(queue)
36
+ end
37
+
38
+ alias remove_queue_without_unique_in_queue_cleanup remove_queue
39
+ alias remove_queue remove_queue_with_unique_in_queue_cleanup
40
+ end
41
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Resque
4
+ module UniqueInQueue
5
+ VERSION = '1.0.0'.freeze
6
+ end
7
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path('lib/resque/unique_in_queue/version', __dir__)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'resque-unique_in_queue'
7
+ spec.version = Resque::UniqueInQueue::VERSION
8
+ spec.authors = ['Peter H. Boling', 'Tee Parham']
9
+ spec.email = %w[peter.boling@gmail.com]
10
+ spec.license = 'MIT'
11
+
12
+ spec.summary = 'A resque plugin that ensures job uniqueness at enqueue time.'
13
+ spec.summary = 'A resque plugin that ensures job uniqueness at enqueue time.'
14
+ spec.homepage = 'https://github.com/pboling/resque-unique_in_queue'
15
+ spec.required_ruby_version = '>= 2.3.0'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_runtime_dependency 'colorize', '~> 0.8'
24
+ spec.add_runtime_dependency 'resque', '>= 1.2'
25
+
26
+ spec.add_development_dependency 'bundler', '~> 1.16'
27
+ spec.add_development_dependency 'byebug', '~> 10.0'
28
+ spec.add_development_dependency 'fakeredis', '~> 0.7'
29
+ spec.add_development_dependency 'minitest', '~> 5.11'
30
+ spec.add_development_dependency 'pry', '~> 0.11'
31
+ spec.add_development_dependency 'pry-byebug', '~> 3.6'
32
+ spec.add_development_dependency 'rake', '~> 12.3'
33
+ spec.add_development_dependency 'rubocop', '~> 0.60'
34
+ end
metadata ADDED
@@ -0,0 +1,202 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: resque-unique_in_queue
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Peter H. Boling
8
+ - Tee Parham
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2018-11-08 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: colorize
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '0.8'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '0.8'
28
+ - !ruby/object:Gem::Dependency
29
+ name: resque
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '1.2'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '1.2'
42
+ - !ruby/object:Gem::Dependency
43
+ name: bundler
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '1.16'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '1.16'
56
+ - !ruby/object:Gem::Dependency
57
+ name: byebug
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '10.0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '10.0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: fakeredis
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '0.7'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '0.7'
84
+ - !ruby/object:Gem::Dependency
85
+ name: minitest
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '5.11'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '5.11'
98
+ - !ruby/object:Gem::Dependency
99
+ name: pry
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: '0.11'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: '0.11'
112
+ - !ruby/object:Gem::Dependency
113
+ name: pry-byebug
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - "~>"
117
+ - !ruby/object:Gem::Version
118
+ version: '3.6'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - "~>"
124
+ - !ruby/object:Gem::Version
125
+ version: '3.6'
126
+ - !ruby/object:Gem::Dependency
127
+ name: rake
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - "~>"
131
+ - !ruby/object:Gem::Version
132
+ version: '12.3'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - "~>"
138
+ - !ruby/object:Gem::Version
139
+ version: '12.3'
140
+ - !ruby/object:Gem::Dependency
141
+ name: rubocop
142
+ requirement: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - "~>"
145
+ - !ruby/object:Gem::Version
146
+ version: '0.60'
147
+ type: :development
148
+ prerelease: false
149
+ version_requirements: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - "~>"
152
+ - !ruby/object:Gem::Version
153
+ version: '0.60'
154
+ description:
155
+ email:
156
+ - peter.boling@gmail.com
157
+ executables: []
158
+ extensions: []
159
+ extra_rdoc_files: []
160
+ files:
161
+ - ".gitignore"
162
+ - ".rubocop.yml"
163
+ - ".rubocop_todo.yml"
164
+ - ".ruby-version"
165
+ - ".travis.yml"
166
+ - Gemfile
167
+ - LICENSE
168
+ - README.md
169
+ - Rakefile
170
+ - lib/resque-unique_in_queue.rb
171
+ - lib/resque/plugins/unique_in_queue.rb
172
+ - lib/resque/unique_in_queue/configuration.rb
173
+ - lib/resque/unique_in_queue/queue.rb
174
+ - lib/resque/unique_in_queue/resque_ext/job.rb
175
+ - lib/resque/unique_in_queue/resque_ext/resque.rb
176
+ - lib/resque/unique_in_queue/version.rb
177
+ - resque-unique_in_queue.gemspec
178
+ homepage: https://github.com/pboling/resque-unique_in_queue
179
+ licenses:
180
+ - MIT
181
+ metadata: {}
182
+ post_install_message:
183
+ rdoc_options: []
184
+ require_paths:
185
+ - lib
186
+ required_ruby_version: !ruby/object:Gem::Requirement
187
+ requirements:
188
+ - - ">="
189
+ - !ruby/object:Gem::Version
190
+ version: 2.3.0
191
+ required_rubygems_version: !ruby/object:Gem::Requirement
192
+ requirements:
193
+ - - ">="
194
+ - !ruby/object:Gem::Version
195
+ version: '0'
196
+ requirements: []
197
+ rubyforge_project:
198
+ rubygems_version: 2.7.7
199
+ signing_key:
200
+ specification_version: 4
201
+ summary: A resque plugin that ensures job uniqueness at enqueue time.
202
+ test_files: []