distributed_job 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e8aa1822c85ccb44521e470a312b430b9409dd537add85122045f7cd71d075ed
4
+ data.tar.gz: a781158c7699303544cb78185a200059da25013830f677a44a704b6a0c115af8
5
+ SHA512:
6
+ metadata.gz: 6885d739ae86c615588dfd85ca1b4d0b0b3e4bcdf8cca1ddf1aebcb806a703c1980ca2d1e6f758d22b38a24a3859d5673fa47e7b2438db4635a4e24351bff4eb
7
+ data.tar.gz: ffbea863763a62d8f22a2e8c2f2b8555a910fc908801e9a93157703a3a2e7e6fbd9bee3b90e6c35971d430b8b720781b02c1d116159e1ea2c3cb00b9fa57b7d4
@@ -0,0 +1,30 @@
1
+ on: push
2
+ name: test
3
+ jobs:
4
+ test:
5
+ runs-on: ubuntu-latest
6
+ strategy:
7
+ fail-fast: false
8
+ matrix:
9
+ redis:
10
+ - redis:5.0
11
+ - redis:6.0
12
+ ruby:
13
+ - 2.6
14
+ - 2.7
15
+ - 3.0
16
+ services:
17
+ elasticsearch:
18
+ image: ${{ matrix.redis }}
19
+ ports:
20
+ - 6379:6379
21
+ steps:
22
+ - uses: actions/checkout@v1
23
+ - uses: actions/setup-ruby@v1
24
+ with:
25
+ ruby-version: ${{ matrix.ruby }}
26
+ - run: gem install bundler
27
+ - run: bundle
28
+ - run: sleep 3
29
+ - run: bundle exec rspec
30
+ - run: bundle exec rubocop
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,19 @@
1
+ AllCops:
2
+ NewCops: enable
3
+ TargetRubyVersion: 2.5
4
+ SuggestExtensions: false
5
+
6
+ Metrics/MethodLength:
7
+ Enabled: false
8
+
9
+ Metrics/BlockLength:
10
+ Enabled: false
11
+
12
+ Layout/LineLength:
13
+ Enabled: false
14
+
15
+ Style/Documentation:
16
+ Enabled: false
17
+
18
+ Style/NumericPredicate:
19
+ Enabled: false
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.6.6
6
+ before_install: gem install bundler -v 2.1.4
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # CHANGELOG
2
+
3
+ ## v1.0.0
4
+
5
+ * Initial release
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in distributed_job.gemspec
6
+ gemspec
7
+
8
+ gem 'rake', '~> 12.0'
9
+ gem 'rspec', '~> 3.0'
data/Gemfile.lock ADDED
@@ -0,0 +1,57 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ distributed_job (1.0.0)
5
+ redis
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ ast (2.4.2)
11
+ diff-lcs (1.4.4)
12
+ parallel (1.21.0)
13
+ parser (3.0.2.0)
14
+ ast (~> 2.4.1)
15
+ rainbow (3.0.0)
16
+ rake (12.3.3)
17
+ redis (4.5.1)
18
+ regexp_parser (2.1.1)
19
+ rexml (3.2.5)
20
+ rspec (3.10.0)
21
+ rspec-core (~> 3.10.0)
22
+ rspec-expectations (~> 3.10.0)
23
+ rspec-mocks (~> 3.10.0)
24
+ rspec-core (3.10.1)
25
+ rspec-support (~> 3.10.0)
26
+ rspec-expectations (3.10.1)
27
+ diff-lcs (>= 1.2.0, < 2.0)
28
+ rspec-support (~> 3.10.0)
29
+ rspec-mocks (3.10.2)
30
+ diff-lcs (>= 1.2.0, < 2.0)
31
+ rspec-support (~> 3.10.0)
32
+ rspec-support (3.10.2)
33
+ rubocop (1.22.1)
34
+ parallel (~> 1.10)
35
+ parser (>= 3.0.0.0)
36
+ rainbow (>= 2.2.2, < 4.0)
37
+ regexp_parser (>= 1.8, < 3.0)
38
+ rexml
39
+ rubocop-ast (>= 1.12.0, < 2.0)
40
+ ruby-progressbar (~> 1.7)
41
+ unicode-display_width (>= 1.4.0, < 3.0)
42
+ rubocop-ast (1.12.0)
43
+ parser (>= 3.0.1.1)
44
+ ruby-progressbar (1.11.0)
45
+ unicode-display_width (2.1.0)
46
+
47
+ PLATFORMS
48
+ ruby
49
+
50
+ DEPENDENCIES
51
+ distributed_job!
52
+ rake (~> 12.0)
53
+ rspec (~> 3.0)
54
+ rubocop
55
+
56
+ BUNDLED WITH
57
+ 2.1.4
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Benjamin Vetter
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # DistributedJob
2
+
3
+ Easily keep track of distributed jobs consisting of an arbitrary number of
4
+ parts spanning multiple workers using redis. Can be used with any kind of
5
+ backround job processing queue.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'distributed_job'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install distributed_job
22
+
23
+ ## Usage
24
+
25
+ Getting started is very easy. A `DistributedJob` allows to keep track of a
26
+ distributed job, i.e. a job which is split into multiple units running in
27
+ parallel and in multiple workers.
28
+
29
+ To create a distributed job and add parts, i.e. units of work, to it, simply:
30
+
31
+ ```ruby
32
+ distributed_job = DistributedJob.new(redis: Redis.new, token: SecureRandom.hex)
33
+
34
+ distributed_job.push_each(Date.parse('2021-01-01')..Date.today) do |date, part|
35
+ SomeBackgroundJob.perform_async(date, distributed_job.token, part)
36
+ end
37
+
38
+ distributed_job.token # can be used to query the status of the distributed job
39
+ ```
40
+
41
+ The `token` can be used to keep query the status of the distributed job, e.g.
42
+ on a job summary page or similar. You can show some progress bar in the browser
43
+ or in the terminal, etc:
44
+
45
+ ```ruby
46
+ # token is given via URL or via some other means
47
+ distributed_job = Distributed.new(redis: Redis.new, token: params[:token])
48
+
49
+ distributed_job.total # total number of parts
50
+ distributed_job.count # number of unfinished parts
51
+ distributed_job.finished?
52
+ ```
53
+
54
+ Within the background job, you use the passed token and part to query and
55
+ update the status of the distributed job and part accordingly. Please note
56
+ that you can use whatever background job processing tool you like most.
57
+
58
+ ```ruby
59
+ class SomeBackgroundJob
60
+ def perform(whatever, token, part)
61
+ distributed_job = DistributedJob.new(redis: Redis.new, token: token)
62
+
63
+ return if distributed_job.stopped?
64
+
65
+ # ...
66
+
67
+ distributed_job.done(part)
68
+
69
+ if distributed_job.finished?
70
+ # perform e.g. cleanup or the some other job
71
+ end
72
+ rescue
73
+ distributed_job.stop
74
+
75
+ raise
76
+ end
77
+ end
78
+ ```
79
+
80
+ The `#stop` and `#stopped?` methods can be used to globally stop a distributed
81
+ job in case of errors. Contrary, the `#done` method tells the `DistributedJob` that the
82
+ specified part has successfully finished. Finally, the `#finished?` method
83
+ returns true when all parts of the distributed job are finished, which is useful
84
+ to start cleanup jobs or to even start another subsequent distributed job.
85
+
86
+ ## Development
87
+
88
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
89
+ `bundle exec rspec` to run the tests. You can also run `bin/console` for an
90
+ interactive prompt that will allow you to experiment.
91
+
92
+ ## Contributing
93
+
94
+ Bug reports and pull requests are welcome on GitHub at
95
+ https://github.com/mrkamel/distributed_job.
96
+
97
+ ## License
98
+
99
+ The gem is available as open source under the terms of the [MIT
100
+ License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'distributed_job'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/distributed_job/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'distributed_job'
7
+ spec.version = DistributedJob::VERSION
8
+ spec.authors = ['Benjamin Vetter']
9
+ spec.email = ['benjamin.vetter@wlw.de']
10
+
11
+ spec.summary = 'Keep track of distributed jobs using redis'
12
+ spec.description = 'Keep track of distributed jobs spanning multiple workers using redis'
13
+ spec.homepage = 'https://github.com/mrkamel/distributed_job'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['source_code_uri'] = 'https://github.com/mrkamel/distributed_job'
19
+ spec.metadata['changelog_uri'] = 'https://github.com/mrkamel/distributed_job/blob/master/CHANGELOG.md'
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
+ end
26
+ spec.bindir = 'exe'
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ['lib']
29
+
30
+ spec.add_development_dependency 'rspec'
31
+ spec.add_development_dependency 'rubocop'
32
+ spec.add_dependency 'redis'
33
+ end
@@ -0,0 +1,6 @@
1
+ version: '2'
2
+ services:
3
+ elasticsearch:
4
+ image: redis
5
+ ports:
6
+ - 6379:6379
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DistributedJob
4
+ VERSION = '1.0.0'
5
+ end
@@ -0,0 +1,275 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'distributed_job/version'
4
+ require 'redis'
5
+
6
+ # A distributed job instance allows to keep track of distributed jobs, i.e.
7
+ # jobs which are split into multiple units running in parallel and in multiple
8
+ # workers using redis.
9
+ #
10
+ # @example Creating a distributed job
11
+ # distributed_job = DistributedJob.new(redis: Redis.new, token: SecureRandom.hex)
12
+ #
13
+ # distributed_job.push_each(Date.parse('2021-01-01')..Date.today) do |date, part|
14
+ # SomeBackgroundJob.perform_async(date, distributed_job.token, part)
15
+ # end
16
+ #
17
+ # distributed_job.token # can be used to query the status of the distributed job
18
+ #
19
+ # @example Processing a distributed job part
20
+ # class SomeBackgroundJob
21
+ # def perform(whatever, token, part)
22
+ # distributed_job = DistributedJob.new(redis: Redis.new, token: token)
23
+ #
24
+ # return if distributed_job.stopped?
25
+ #
26
+ # # ...
27
+ #
28
+ # distributed_job.done(part)
29
+ #
30
+ # if distributed_job.finished?
31
+ # # perform e.g. cleanup or the some other job
32
+ # end
33
+ # rescue
34
+ # distributed_job.stop
35
+ #
36
+ # raise
37
+ # end
38
+ # end
39
+
40
+ class DistributedJob
41
+ attr_reader :redis, :token, :ttl
42
+
43
+ # Initializes a new distributed job.
44
+ #
45
+ # @param redis [Redis] The redis connection instance
46
+ # @param token [String] Some token to be used to identify the job. You can
47
+ # e.g. use SecureRandom.hex to generate one.
48
+ # @param ttl [Integer] The number of seconds this job will stay available
49
+ # in redis. This value is used to automatically expire and clean up the
50
+ # job in redis. Default is 86400, i.e. one day. The ttl is used everytime
51
+ # the job is modified in redis.
52
+ #
53
+ # @example
54
+ # distributed_job = DistributedJob.new(redis: Redis.new, token: SecureRandom.hex)
55
+
56
+ def initialize(redis:, token:, ttl: 86_400)
57
+ @redis = redis
58
+ @token = token
59
+ @ttl = ttl
60
+ end
61
+
62
+ # Pass an enum to be used to iterate all the units of work of the distributed
63
+ # job. The distributed job needs to know all of them to keep track of the
64
+ # overall number and status of the parts. Passing an enum is much better
65
+ # compared to pushing the parts manually, because the distributed job needs
66
+ # to be closed before the last part of the distributed job is enqueued into
67
+ # some job queue. Otherwise it could potentially happen that the last part is
68
+ # already processed in the job queue before it is pushed to redis, such that
69
+ # the last job doesn't know that the distributed job is finished.
70
+ #
71
+ # @param enum [#each_with_index] The enum which can be iterated to get all
72
+ # job parts
73
+ #
74
+ # @example
75
+ # distributed_job.push_each(Date.parse('2021-01-01')..Date.today) do |date, part|
76
+ # # e.g. SomeBackgroundJob.perform_async(date, distributed_job.token, part)
77
+ # end
78
+ #
79
+ # @example ActiveRecord
80
+ # distributed_job.push_each(User.select(:id).find_in_batches) do |batch, part|
81
+ # # e.g. SomeBackgroundJob.perform_async(batch.first.id, batch.last.id, distributed_job.token, part)
82
+ # end
83
+
84
+ def push_each(enum)
85
+ return enum_for(:push_each, enum) unless block_given?
86
+
87
+ previous_object = nil
88
+ previous_index = nil
89
+
90
+ enum.each_with_index do |current_object, current_index|
91
+ push(current_index)
92
+
93
+ yield(previous_object, previous_index.to_s) if previous_index
94
+
95
+ previous_object = current_object
96
+ previous_index = current_index
97
+ end
98
+
99
+ close
100
+
101
+ yield(previous_object, previous_index.to_s) if previous_index
102
+ end
103
+
104
+ # Returns all pushed parts of the distributed job
105
+ #
106
+ # @return [Enumerator] The enum which allows to iterate all parts
107
+
108
+ def parts
109
+ redis.sscan_each("#{redis_key}:parts")
110
+ end
111
+
112
+ # Removes the specified part from the distributed job, i.e. from the set of
113
+ # unfinished parts. Use this method when the respective job part has been
114
+ # successfully processed, i.e. finished.
115
+ #
116
+ # @param part [String] The job part
117
+ # @returns [Boolean] Returns true when there are no more unfinished parts
118
+ # left or false otherwise
119
+ #
120
+ # @example
121
+ # class SomeBackgroundJob
122
+ # def perform(whatever, token, part)
123
+ # distributed_job = DistributedJob.new(redis: Redis.new, token: token)
124
+ #
125
+ # # ...
126
+ #
127
+ # distributed_job.done(part)
128
+ # end
129
+ # end
130
+
131
+ def done(part)
132
+ @done_script ||= <<~SCRIPT
133
+ local key, part, ttl = ARGV[1], ARGV[2], tonumber(ARGV[3])
134
+
135
+ if redis.call('srem', key .. ':parts', part) == 0 then return end
136
+
137
+ redis.call('expire', key .. ':parts', ttl)
138
+ redis.call('expire', key .. ':state', ttl)
139
+
140
+ return redis.call('scard', key .. ':parts')
141
+ SCRIPT
142
+
143
+ redis.eval(@done_script, argv: [redis_script_key, part.to_s, ttl]) == 0 && closed?
144
+ end
145
+
146
+ # Returns the total number of pushed parts, no matter if finished or not.
147
+ #
148
+ # @example
149
+ # distributed_job.total # => e.g. 13
150
+
151
+ def total
152
+ redis.hget("#{redis_key}:state", 'total').to_i
153
+ end
154
+
155
+ # Returns the number of pushed parts which are not finished.
156
+ #
157
+ # @example
158
+ # distributed_job.count # => e.g. 8
159
+
160
+ def count
161
+ redis.scard("#{redis_key}:parts")
162
+ end
163
+
164
+ # Returns true if there are no more unfinished parts.
165
+ #
166
+ # @example
167
+ # distributed_job.finished? #=> true/false
168
+
169
+ def finished?
170
+ closed? && count.zero?
171
+ end
172
+
173
+ # Allows to stop a distributed job. This is useful if some error occurred in
174
+ # some part, i.e. background job, of the distributed job and you then want to
175
+ # stop all other not yet finished parts. Please note that only jobs can be
176
+ # stopped which ask the distributed job actively whether or not it was
177
+ # stopped.
178
+ #
179
+ # @returns [Boolean] Always returns true
180
+ #
181
+ # @example
182
+ # class SomeBackgroundJob
183
+ # def perform(whatever, token, part)
184
+ # distributed_job = DistributedJob.new(redis: Redis.new, token: token)
185
+ #
186
+ # return if distributed_job.stopped?
187
+ #
188
+ # # ...
189
+ #
190
+ # distributed_job.done(part)
191
+ # rescue
192
+ # distributed_job.stop
193
+ #
194
+ # raise
195
+ # end
196
+ # end
197
+
198
+ def stop
199
+ redis.multi do
200
+ redis.hset("#{redis_key}:state", 'stopped', 1)
201
+
202
+ redis.expire("#{redis_key}:state", ttl)
203
+ redis.expire("#{redis_key}:parts", ttl)
204
+ end
205
+
206
+ true
207
+ end
208
+
209
+ # Returns true when the distributed job was stopped or false otherwise.
210
+ #
211
+ # @returns [Boolean] Returns true or false
212
+ #
213
+ # @example
214
+ # class SomeBackgroundJob
215
+ # def perform(whatever, token, part)
216
+ # distributed_job = DistributedJob.new(redis: Redis.new, token: token)
217
+ #
218
+ # return if distributed_job.stopped?
219
+ #
220
+ # # ...
221
+ #
222
+ # distributed_job.done(part)
223
+ # rescue
224
+ # distributed_job.stop
225
+ #
226
+ # raise
227
+ # end
228
+ # end
229
+
230
+ def stopped?
231
+ redis.hget("#{redis_key}:state", 'stopped') == '1'
232
+ end
233
+
234
+ private
235
+
236
+ def close
237
+ redis.multi do
238
+ redis.hset("#{redis_key}:state", 'closed', 1)
239
+
240
+ redis.expire("#{redis_key}:state", ttl)
241
+ redis.expire("#{redis_key}:parts", ttl)
242
+ end
243
+
244
+ true
245
+ end
246
+
247
+ def closed?
248
+ redis.hget("#{redis_key}:state", 'closed') == '1'
249
+ end
250
+
251
+ def push(part)
252
+ @push_script ||= <<~SCRIPT
253
+ local key, part, ttl = ARGV[1], ARGV[2], tonumber(ARGV[3])
254
+
255
+ if redis.call('sadd', key .. ':parts', part) == 1 then
256
+ redis.call('hincrby', key .. ':state', 'total', 1)
257
+ end
258
+
259
+ redis.call('expire', key .. ':parts', ttl)
260
+ redis.call('expire', key .. ':state', ttl)
261
+ SCRIPT
262
+
263
+ redis.eval(@push_script, argv: [redis_script_key, part.to_s, ttl])
264
+ end
265
+
266
+ def redis_key
267
+ @redis_key ||= "distributed_jobs:#{token}"
268
+ end
269
+
270
+ def redis_script_key
271
+ return "#{redis.namespace}:#{redis_key}" if redis.respond_to?(:namespace)
272
+
273
+ redis_key
274
+ end
275
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: distributed_job
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Benjamin Vetter
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-10-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rubocop
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: redis
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Keep track of distributed jobs spanning multiple workers using redis
56
+ email:
57
+ - benjamin.vetter@wlw.de
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".github/workflows/test.yml"
63
+ - ".gitignore"
64
+ - ".rspec"
65
+ - ".rubocop.yml"
66
+ - ".travis.yml"
67
+ - CHANGELOG.md
68
+ - Gemfile
69
+ - Gemfile.lock
70
+ - LICENSE.txt
71
+ - README.md
72
+ - Rakefile
73
+ - bin/console
74
+ - bin/setup
75
+ - distributed_job.gemspec
76
+ - docker-compose.yml
77
+ - lib/distributed_job.rb
78
+ - lib/distributed_job/version.rb
79
+ homepage: https://github.com/mrkamel/distributed_job
80
+ licenses:
81
+ - MIT
82
+ metadata:
83
+ homepage_uri: https://github.com/mrkamel/distributed_job
84
+ source_code_uri: https://github.com/mrkamel/distributed_job
85
+ changelog_uri: https://github.com/mrkamel/distributed_job/blob/master/CHANGELOG.md
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: 2.5.0
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ requirements: []
101
+ rubygems_version: 3.0.3
102
+ signing_key:
103
+ specification_version: 4
104
+ summary: Keep track of distributed jobs using redis
105
+ test_files: []