procrastinator 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b4fce4f790799f4157d84b40960ce894e71562b9
4
- data.tar.gz: 0384cc924d66ebe90af1585dc05ab2133d750355
3
+ metadata.gz: a9d61dca76d919b7ece43fd8b7b59c9256cb8334
4
+ data.tar.gz: 70251bb840acfa728d0fbb1cf5b457364dd655ac
5
5
  SHA512:
6
- metadata.gz: 7ae26a1c7f3bf0962b64e6598103c91f6ad4b31eeb8b8e553b6859ff73d7231385555853573162c4d5351414cc39c7ba5fc80e4037dfa12c915f4f42db2934db
7
- data.tar.gz: 8555135727bbbd608c58c5ddb1d0f0c877ce086301135426a958e134aec8717d2244e40529966959b46dff19af7b21ae6a2dfef94747b91b4d4fad474d7af1ed
6
+ metadata.gz: ffbb42494ee062144f658b11a9c936072432c411e0aae2f43ca7a7b561b8272f445d8a8017b4b708c06463fdd2bc276a8f869ee4506576f5d521fa2a17280d4f
7
+ data.tar.gz: 3afb4512571a8e49530bb05726cd4628636f10925791967fe789b11a536a2c48df87d9d0311af7e2a8066ccb41698c0d8b50f15cb9080525b9a7081de130bb00
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.1.2
data/README.md CHANGED
@@ -1,8 +1,7 @@
1
1
  # Procrastinator
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/procrastinator`. To experiment with that code, run `bin/console` for an interactive prompt.
4
-
5
- TODO: Delete this and the text above, and describe your gem
3
+ Procrastinator is a framework-independent job scheduling gem. It creates a subprocess for each queue that performs
4
+ tasks at the appropriate times.
6
5
 
7
6
  ## Installation
8
7
 
@@ -21,21 +20,166 @@ Or install it yourself as:
21
20
  $ gem install procrastinator
22
21
 
23
22
  ## Usage
23
+ Setup a procrastination environment:
24
24
 
25
- TODO: Write usage instructions here
25
+ ```ruby
26
+ procrastinator = Procrastinator.setup(TaskPersister.new) do |env| # MyTaskPersister is a class defined by you
27
+ env.define_queue(:email)
28
+ env.define_queue(:cleanup, max_attempts: 3)
29
+ end
30
+ ```
26
31
 
27
- ## Development
32
+ And then delay some tasks:
28
33
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
34
+ ```ruby
35
+ procrastinator.delay(queue: :email, task: EmailGreeting.new('bob@example.com'))
36
+ procrastinator.delay(queue: :cleanup, run_at: Time.now + 3600, task: ClearTempData.new)
37
+ ```
30
38
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
39
+ Read on for more details on each step.
32
40
 
33
- ## Contributing
41
+ ###`Procrastinator.setup`
42
+ The setup phase first defines which queues are available and the persistence strategy to use for reading
43
+ and writing tasks. It then starts up a sub process for working on each queue within that environment.
34
44
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/procrastinator. 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.
36
45
 
46
+ #### Persister Strategy
47
+ The persister instance is the last step between Procrastinator and your data storage (eg. database). As core Procrastinator is framework-agnostic, it needs you to provide an object that knows how to read and write task data.
37
48
 
38
- ## License
49
+ Your [strategy](https://en.wikipedia.org/wiki/Strategy_pattern) class is required to provide the following methods:
50
+
51
+ * `#read_tasks(queue_name)` - Returns a list of hashes from your data storage. Each hash must contains the properites of one task, as seen in the *Attributes Hash*
52
+ * `#create_task(data)` - Creates a task in your datastore. Receives a hash with keys `:queue`, `:run_at`, `:initial_run_at`, `:expire_at`, and `:task` as described in *Attributes Hash*
53
+ * `#update_task(attributes)` - Receives the Attributes Hash as the data to be saved
54
+ * `#delete_task(id)` - Deletes the task with the given id.
55
+
56
+ If the strategy does not have all of these methods, Procrastinator will explode with a `MalformedPersisterError` and you will be sad.
57
+
58
+ ***Attributes Hash***
59
+
60
+ | Hash Key | Type | Description |
61
+ |-------------------|--------| --------------------------------------------------------------------------------------|
62
+ | `:id` | int | Unique identifier for this exact task |
63
+ | `:queue` | symbol | Name of the queue the task is inside |
64
+ | `:run_at` | int | Unix timestamp of when to next attempt running the task |
65
+ | `:initial_run_at` | int | Unix timestamp of the original run_at; before the first attempt, this is equal to run_at |
66
+ | `:expire_at` | int | Unix timestamp of when to permanently fail the task because it is too late to be useful |
67
+ | `:attempts` | int | Number of times the task has tried to run; this should only be > 0 if the task fails |
68
+ | `:last_fail_at` | int | Unix timestamp of when the most recent failure happened |
69
+ | `:last_error` | string | Error message + bracktrace of the most recent failure. May be very long. |
70
+ | `:task` | string | YAML-dumped ruby object definition of the task. |
71
+
72
+ Notice that the times are all given as unix epoch timestamps. This is to avoid any confusion with timezones, and it is recommended that you store times in this manner for the same reason.
73
+
74
+ ####Defining Queues
75
+ `Procrastinator.setup` requires a block be provided, and that in the block call `#define_queue` be called on the provided environment. Define queue takes a queue name symbol and these properies as a hash
39
76
 
40
- The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
77
+ * :timeout - Time, in seconds, after which it should fail tasks in this queue for taking too long to execute.
78
+ * :max_attempts - Maximum number of attempts for tasks in this queue. If attempts is >= max_attempts, the task will be final_failed and marked to never run again
79
+ * :update_period - Delay, in seconds, between refreshing the task list from the persister
80
+ * :max_tasks - The maximum number of tasks to run concurrently with multi-threading.
81
+
82
+ **Examples**
83
+ ```ruby
84
+ Procrastinator.setup(some_persister) do |env|
85
+ env.define_queue(:email)
86
+
87
+ # with all defaults set explicitly
88
+ env.define_queue(:email, timeout: 3600, max_attempts: 20, update_period: 10, max_tasks: 10)
89
+ end
90
+ ```
91
+
92
+ #### Queue Sub-Processes
93
+ Each queue is worked in a separate process, and each process is multi-threaded to handle more than one task at a time. This should help prevent a single task from clogging up the whole queue, or a single queue clogging up the entire system. Tasks are chosen by their `:run_at` property.
94
+
95
+ The sub-processes check ervey 5 seconds that the parent process is still alive. If there is no process with the parent's process ID, the sub-process will self-exit.
96
+
97
+ ###`Environment#delay`
98
+ When there are multiple queues defined, you are required to provide a queue name:
99
+
100
+ ```ruby
101
+ procrastinator = Procrastinator.setup(task_persister) do |env|
102
+ env.define_queue(:email)
103
+ env.|env|define_queue(:cleanup)
104
+ end
105
+
106
+ procrastinator.delay(:email, task: EmailReminder.new('bob@example.com'))
107
+ ```
108
+
109
+ Otherwise, Procrastinator will let you be lazy:
110
+
111
+ ```ruby
112
+ procrastinator = Procrastinator.setup(task_persister) do |env|
113
+ env.define_queue(:email)
114
+ end
115
+
116
+ procrastinator.delay(task: EmailReminder.new('bob@example.com'))
117
+ ```
41
118
 
119
+ You can set when the particular task is to be run and/or when it should expire. Be aware that neither is guaranteed to be real-time; a task will run as soon as it possible after `run_at` is passed, and will expire once it attempts to run after `expire_at` has passed.
120
+
121
+ ```ruby
122
+ procrastinator = Procrastinator.setup(task_persister) do |env|
123
+ env.define_queue(:email)
124
+ end
125
+
126
+ # run on or after 1 January 3000
127
+ procrastinator.delay(run_at: Time.new(3000, 1, 1), task: EmailGreeting.new('philip_j_fry@example.com'))
128
+
129
+ # explicitly setting default run_at
130
+ procrastinator.delay(run_at: Time.now, task: EmailReminder.new('bob@example.com'))
131
+ ```
132
+
133
+ You can also set an `expire_at` deadline on when to run a task. If the task has not been run before `expire_at` is passed, then it will be final-failed the next time it is attempted. Setting `expire_at` to `nil` will mean it will never expire (but may still fail permanently if, say, `max_attempts` is reached).
134
+
135
+ ```ruby
136
+ procrastinator = Procrastinator.setup(task_persister) do |env|
137
+ env.define_queue(:email)
138
+ end
139
+
140
+ procrastinator.delay(expire_at: , task: EmailGreeting.new('bob@example.com'))
141
+
142
+ # explicitly setting default
143
+ procrastinator.delay(expire_at: nil, task: EmailGreeting.new('bob@example.com'))
144
+ ```
145
+
146
+ #### Task
147
+ Like the persister provided to `.setup`, your task is a strategy object that fills in the details of what to do. For this,
148
+ your task **must provide** a `#run` method:
149
+
150
+ * `#run` - Performs the core work of the task.
151
+
152
+ You may also optionally provide these hook methods, which are run during different points in the process:
153
+
154
+ * `#success` - run after the task has completed successfully
155
+ * `#fail` - run after the task has failed due to `#run` producing a `StandardError` or subclass.
156
+ * `#final_fail` - run after the task has failed for the last time because either:
157
+ 1. the number of attempts is >= the `max_attempts` defined for the queue; or
158
+ 2. the time reported by `Time.now` is past the task's `expire_at` time.
159
+
160
+ If a task reaches `#final_fail` it will be marked to never be run again.
161
+
162
+ ***Task Failure & Rescheduling***
163
+
164
+ Tasks that fail have their `run_at` rescheduled on an increasing delay according to this formula:
165
+ * 30 + attempts<sup>4</sup> **(in seconds)**
166
+
167
+ Both failing and final_failing will cause the error timestamp and reason to be stored in `:last_fail_at` and `:last_error`.
168
+
169
+ ## Contributing
170
+ Bug reports and pull requests are welcome on GitHub at
171
+ [https://github.com/TenjinInc/procrastinator](https://github.com/TenjinInc/procrastinator).
172
+
173
+ This project is intended to be a friendly space for collaboration, and contributors are expected to adhere to the
174
+ [Contributor Covenant](http://contributor-covenant.org) code of conduct.
175
+
176
+ ### Core Developers
177
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can
178
+ also run `bin/console` for an interactive prompt that will allow you to experiment.
179
+
180
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the
181
+ version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version,
182
+ push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
183
+
184
+ ## License
185
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,73 @@
1
+ module Procrastinator
2
+ class Environment
3
+ attr_reader :persister, :queues, :processes
4
+
5
+ def initialize(persister)
6
+ raise ArgumentError.new('persister cannot be nil') if persister.nil?
7
+
8
+ [:read_tasks, :create_task, :update_task, :delete_task].each do |method|
9
+ raise MalformedPersisterError.new("persister must repond to ##{method}") unless persister.respond_to? method
10
+ end
11
+
12
+ @persister = persister
13
+ @queues = {}
14
+ @processes = []
15
+ end
16
+
17
+ def define_queue(name, properties={})
18
+ raise ArgumentError.new('queue name cannot be nil') if name.nil?
19
+
20
+ @queues[name] = properties
21
+ end
22
+
23
+ def spawn_workers
24
+ @queues.each do |name, props|
25
+ pid = fork do
26
+ Process.setproctitle("#{name}-queue-worker")
27
+
28
+ worker = QueueWorker.new(props.merge(name: name, persister: @persister))
29
+
30
+ monitor_parent
31
+
32
+ worker.work
33
+ end
34
+
35
+ Process.detach(pid) unless pid.nil?
36
+ @processes << pid
37
+ end
38
+ end
39
+
40
+ def delay(queue: nil, run_at: Time.now.to_i, expire_at: nil, task:)
41
+ raise ArgumentError.new('task may not be nil') if task.nil?
42
+ raise MalformedTaskError.new('given task does not support #run method') unless task.respond_to? :run
43
+ if queue.nil? && @queues.size > 1
44
+ raise ArgumentError.new('queue must be specified when more than one is registered')
45
+ else
46
+ queue ||= @queues.keys.first
47
+ raise ArgumentError.new(%Q{there is no "#{queue}" queue registered in this environment}) if @queues[queue].nil?
48
+ end
49
+
50
+ @persister.create_task(queue: queue,
51
+ run_at: run_at.to_i,
52
+ initial_run_at: run_at.to_i,
53
+ expire_at: expire_at.nil? ? nil : expire_at.to_i,
54
+ task: YAML.dump(task))
55
+ end
56
+
57
+ private
58
+ def monitor_parent
59
+ heartbeat_thread = Thread.new(Process.ppid) do |ppid|
60
+ loop do
61
+ Process.kill 0, ppid
62
+
63
+ sleep(5)
64
+ end
65
+ end
66
+
67
+ heartbeat_thread.abort_on_exception = true
68
+ end
69
+ end
70
+
71
+ class MalformedPersisterError < StandardError
72
+ end
73
+ end
@@ -0,0 +1,60 @@
1
+ module Procrastinator
2
+ class QueueWorker
3
+ DEFAULT_TIMEOUT = 3600 # seconds = one hour
4
+ DEFAULT_MAX_ATTEMPTS = 20
5
+ DEFAULT_UPDATE_PERIOD = 10 # seconds
6
+ DEFAULT_MAX_TASKS = 10
7
+
8
+ attr_reader :name, :timeout, :max_attempts, :update_period, :max_tasks
9
+
10
+ # Timeout is in seconds
11
+ def initialize(name:,
12
+ persister:,
13
+ max_attempts: DEFAULT_MAX_ATTEMPTS,
14
+ timeout: DEFAULT_TIMEOUT,
15
+ update_period: DEFAULT_UPDATE_PERIOD,
16
+ max_tasks: DEFAULT_MAX_TASKS)
17
+ raise ArgumentError.new('Queue name may not be nil') unless name
18
+ raise ArgumentError.new('Persister may not be nil') unless persister
19
+
20
+ raise(MalformedTaskPersisterError.new('The supplied IO object must respond to #read_tasks')) unless persister.respond_to? :read_tasks
21
+ raise(MalformedTaskPersisterError.new('The supplied IO object must respond to #update_task')) unless persister.respond_to? :update_task
22
+ raise(MalformedTaskPersisterError.new('The supplied IO object must respond to #delete_task')) unless persister.respond_to? :delete_task
23
+
24
+
25
+ @name = name.to_s.gsub(/\s/, '_').to_sym
26
+ @timeout = timeout
27
+ @max_attempts = max_attempts
28
+ @update_period = update_period
29
+ @max_tasks = max_tasks
30
+ @persister = persister
31
+ end
32
+
33
+ def work
34
+ loop do
35
+ sleep(@update_period)
36
+
37
+ # shuffling and re-sorting to avoid worst case O(n^2) on quicksort
38
+ # when receiving already sorted data. Ideally, we'd use a better algo, but this will do for now
39
+ tasks = @persister.read_tasks(@name).shuffle.sort_by { |t| t[:run_at] }
40
+
41
+ tasks.first(@max_tasks).each do |task_data|
42
+ if Time.now.to_i >= task_data[:run_at].to_i
43
+ tw = TaskWorker.new(task_data)
44
+
45
+ tw.work
46
+
47
+ if tw.successful?
48
+ @persister.delete_task(task_data[:id])
49
+ else
50
+ @persister.update_task(tw.to_hash.merge(queue: @name))
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ class MalformedTaskPersisterError < StandardError
59
+ end
60
+ end
@@ -0,0 +1,112 @@
1
+ require 'yaml'
2
+
3
+ module Procrastinator
4
+ class TaskWorker
5
+ attr_reader :id, :run_at, :initial_run_at, :expire_at, :task, :attempts, :last_fail_at, :last_error
6
+
7
+ def initialize(id: nil,
8
+ run_at: nil,
9
+ initial_run_at: nil,
10
+ expire_at: nil,
11
+ attempts: 0,
12
+ timeout: nil,
13
+ max_attempts: nil,
14
+ last_fail_at: nil,
15
+ last_error: nil,
16
+ task:)
17
+ @id = id
18
+ @run_at = run_at.to_i
19
+ @initial_run_at = initial_run_at.to_i
20
+ @expire_at = expire_at.nil? ? nil : expire_at.to_i
21
+ @task = YAML.load(task)
22
+ @attempts = attempts
23
+ @max_attempts = max_attempts
24
+ @timeout = timeout
25
+ @last_fail_at = last_fail_at
26
+ @last_error = last_error
27
+
28
+ raise(MalformedTaskError.new('given task does not support #run method')) unless @task.respond_to? :run
29
+ raise(ArgumentError.new('timeout cannot be negative')) if timeout && timeout < 0
30
+ end
31
+
32
+ def work
33
+ @attempts += 1
34
+
35
+ begin
36
+ raise(TaskExpiredError.new("task is over its expiry time of #{@expire_at}")) if expired?
37
+
38
+ Timeout::timeout(@timeout) do
39
+ @task.run
40
+ end
41
+
42
+ try_hook(:success)
43
+ @last_error = nil
44
+ @last_fail_at = nil
45
+ rescue StandardError => e
46
+ @last_fail_at = Time.now.to_i
47
+
48
+ if too_many_fails? || expired?
49
+ try_hook(:final_fail, e)
50
+
51
+ @run_at = nil
52
+ @last_error = "#{expired? ? 'Task expired' : 'Task failed too many times'}: #{e.backtrace.join("\n")}"
53
+ else
54
+ try_hook(:fail, e)
55
+
56
+ @last_error = 'Task failed: ' + e.backtrace.join("\n")
57
+
58
+ reschedule
59
+ end
60
+ end
61
+ end
62
+
63
+ def successful?
64
+ if !expired? && @attempts <= 0
65
+ raise(RuntimeError, 'you cannot check for success before running #work')
66
+ end
67
+
68
+ @last_error.nil? && @last_fail_at.nil?
69
+ end
70
+
71
+ def too_many_fails?
72
+ !@max_attempts.nil? && @attempts >= @max_attempts
73
+ end
74
+
75
+ def expired?
76
+ !@expire_at.nil? && Time.now.to_i > @expire_at
77
+ end
78
+
79
+ def to_hash
80
+ {id: @id,
81
+ run_at: @run_at,
82
+ initial_run_at: @initial_run_at,
83
+ expire_at: @expire_at,
84
+ attempts: @attempts,
85
+ last_fail_at: @last_fail_at,
86
+ last_error: @last_error,
87
+ task: YAML.dump(@task)}
88
+ end
89
+
90
+ private
91
+ def try_hook(method, *params)
92
+ begin
93
+ @task.send(method, *params) if @task.respond_to? method
94
+ rescue StandardError => e
95
+ $stderr.puts "#{method.to_s.capitalize} hook error: #{e.message}"
96
+ end
97
+ end
98
+
99
+ def reschedule
100
+ # (30 + n_attempts^4) seconds is chosen to rapidly expand
101
+ # but with the baseline of 30s to avoid hitting the disc too frequently.
102
+
103
+ @run_at += 30 + (@attempts**4)
104
+ end
105
+ end
106
+
107
+ class TaskExpiredError < StandardError
108
+ end
109
+
110
+ class MalformedTaskError < StandardError
111
+ end
112
+ end
@@ -1,3 +1,3 @@
1
1
  module Procrastinator
2
- VERSION = "0.1.0"
2
+ VERSION = '0.2.1'
3
3
  end
@@ -1,5 +1,21 @@
1
- require "procrastinator/version"
1
+ require 'procrastinator/version'
2
+ require 'procrastinator/queue_worker'
3
+ require 'procrastinator/task_worker'
4
+ require 'procrastinator/environment'
5
+
2
6
 
3
7
  module Procrastinator
4
- # Your code goes here...
8
+ def self.setup(persister, &block)
9
+ raise ArgumentError.new('Procrastinator.setup must be given a block') if block.nil?
10
+
11
+ env = Environment.new(persister)
12
+
13
+ yield(env)
14
+
15
+ raise RuntimeError.new('setup block did not define any queues') if env.queues.empty?
16
+
17
+ env.spawn_workers
18
+
19
+ env
20
+ end
5
21
  end
@@ -4,22 +4,26 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'procrastinator/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "procrastinator"
8
- spec.version = Procrastinator::VERSION
9
- spec.authors = ["Robin Miller"]
10
- spec.email = ["robin@tenjin.ca"]
7
+ spec.name = 'procrastinator'
8
+ spec.version = Procrastinator::VERSION
9
+ spec.authors = ['Robin Miller']
10
+ spec.email = ['robin@tenjin.ca']
11
11
 
12
- spec.summary = %q{Simple generalized job queues.}
13
- spec.description = %q{A strightforward job queue that is not dependent on Rails or any particular database or persistence mechanism.}
14
- spec.homepage = "https://github.com/TenjinInc/procrastinator"
15
- spec.license = "MIT"
12
+ spec.summary = %q{Delayed task queues made simple.}
13
+ spec.description = %q{A strightforward job queue that is not dependent on Rails or any particular database or persistence mechanism.}
14
+ spec.homepage = 'https://github.com/TenjinInc/procrastinator'
15
+ spec.license = 'MIT'
16
16
 
17
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
- spec.bindir = "exe"
19
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
- spec.require_paths = ["lib"]
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = 'exe'
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ['lib']
21
21
 
22
- spec.add_development_dependency "bundler", "~> 1.11"
23
- spec.add_development_dependency "rake", "~> 10.0"
24
- spec.add_development_dependency "rspec", "~> 3.0"
22
+ spec.required_ruby_version = '> 2.0'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.11'
25
+ spec.add_development_dependency 'rake', '~> 10.0'
26
+ spec.add_development_dependency 'rspec', '~> 3.0'
27
+ spec.add_development_dependency 'timecop', '~> 0.8'
28
+ spec.add_development_dependency 'simplecov', '~> 0.11'
25
29
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: procrastinator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robin Miller
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-12-20 00:00:00.000000000 Z
11
+ date: 2016-02-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,6 +52,34 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: timecop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.8'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.8'
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.11'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.11'
55
83
  description: A strightforward job queue that is not dependent on Rails or any particular
56
84
  database or persistence mechanism.
57
85
  email:
@@ -62,6 +90,7 @@ extra_rdoc_files: []
62
90
  files:
63
91
  - ".gitignore"
64
92
  - ".rspec"
93
+ - ".ruby-version"
65
94
  - ".travis.yml"
66
95
  - CODE_OF_CONDUCT.md
67
96
  - Gemfile
@@ -71,6 +100,9 @@ files:
71
100
  - bin/console
72
101
  - bin/setup
73
102
  - lib/procrastinator.rb
103
+ - lib/procrastinator/environment.rb
104
+ - lib/procrastinator/queue_worker.rb
105
+ - lib/procrastinator/task_worker.rb
74
106
  - lib/procrastinator/version.rb
75
107
  - procrastinator.gemspec
76
108
  homepage: https://github.com/TenjinInc/procrastinator
@@ -83,9 +115,9 @@ require_paths:
83
115
  - lib
84
116
  required_ruby_version: !ruby/object:Gem::Requirement
85
117
  requirements:
86
- - - ">="
118
+ - - ">"
87
119
  - !ruby/object:Gem::Version
88
- version: '0'
120
+ version: '2.0'
89
121
  required_rubygems_version: !ruby/object:Gem::Requirement
90
122
  requirements:
91
123
  - - ">="
@@ -93,8 +125,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
93
125
  version: '0'
94
126
  requirements: []
95
127
  rubyforge_project:
96
- rubygems_version: 2.4.6
128
+ rubygems_version: 2.2.2
97
129
  signing_key:
98
130
  specification_version: 4
99
- summary: Simple generalized job queues.
131
+ summary: Delayed task queues made simple.
100
132
  test_files: []