resque-unique_in_queue 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []