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 +4 -4
- data/.ruby-version +1 -0
- data/README.md +155 -11
- data/lib/procrastinator/environment.rb +73 -0
- data/lib/procrastinator/queue_worker.rb +60 -0
- data/lib/procrastinator/task_worker.rb +112 -0
- data/lib/procrastinator/version.rb +1 -1
- data/lib/procrastinator.rb +18 -2
- data/procrastinator.gemspec +19 -15
- metadata +38 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a9d61dca76d919b7ece43fd8b7b59c9256cb8334
|
4
|
+
data.tar.gz: 70251bb840acfa728d0fbb1cf5b457364dd655ac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
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
|
-
|
32
|
+
And then delay some tasks:
|
28
33
|
|
29
|
-
|
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
|
-
|
39
|
+
Read on for more details on each step.
|
32
40
|
|
33
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/procrastinator.rb
CHANGED
@@ -1,5 +1,21 @@
|
|
1
|
-
require
|
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
|
-
|
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
|
data/procrastinator.gemspec
CHANGED
@@ -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
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
spec.name = 'procrastinator'
|
8
|
+
spec.version = Procrastinator::VERSION
|
9
|
+
spec.authors = ['Robin Miller']
|
10
|
+
spec.email = ['robin@tenjin.ca']
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
23
|
-
|
24
|
-
|
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
|
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:
|
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.
|
128
|
+
rubygems_version: 2.2.2
|
97
129
|
signing_key:
|
98
130
|
specification_version: 4
|
99
|
-
summary:
|
131
|
+
summary: Delayed task queues made simple.
|
100
132
|
test_files: []
|