procrastinator 1.0.0 → 1.1.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 +4 -4
- data/README.md +6 -1
- data/RELEASE_NOTES.md +31 -1
- data/lib/procrastinator/queue.rb +22 -6
- data/lib/procrastinator/rspec/matchers.rb +22 -15
- data/lib/procrastinator/scheduler.rb +5 -12
- data/lib/procrastinator/task.rb +3 -0
- data/lib/procrastinator/task_store/simple_comma_store.rb +2 -5
- data/lib/procrastinator/version.rb +1 -1
- metadata +2 -3
- data/.travis.yml +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 355845de0042676644aa4fd14ba5c37bc173b1f0cac9c73889dbb36aa8e5cd9c
|
4
|
+
data.tar.gz: f4faa2e3644557027c8fc08f88c973deb19cf16e43fb3312106d8d25a3c61054
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3f59116b21f6218d3ef3059e5f5532b1ef23a95e7a5f0b82a1d21a041ff84e64795e2fd7181bd277cba19e6c8fd01fa04146103ec2de9703169e9db7072a817f
|
7
|
+
data.tar.gz: 235b273d6f07e21f492e82083ea89b0f67eb4275233dddbb04d52e25a5a2f3b6a0674e5d0b8dda9b5c43359ea257b081c4d7bf1b7561b4c1a7242f24660b932c
|
data/README.md
CHANGED
@@ -514,7 +514,7 @@ Procrastinator::Rake::DaemonTasks.define do
|
|
514
514
|
end
|
515
515
|
```
|
516
516
|
|
517
|
-
You can
|
517
|
+
You can title the daemon process by specifying the pid_path with a specific .pid file. If does not end with '.pid' it is
|
518
518
|
assumed to be a directory name, and `procrastinator.pid` is appended.
|
519
519
|
|
520
520
|
```ruby
|
@@ -531,6 +531,11 @@ Procrastinator::Rake::DaemonTasks.define(pid_path: 'pids') do
|
|
531
531
|
end
|
532
532
|
```
|
533
533
|
|
534
|
+
> **Note:** There can be a distinction between process full title (`/proc/*/cmdline`) vs the shorter name
|
535
|
+
> (`/proc/*/comm`). Some tools like `ps` and `top` display the process title, while others like `pstree` show the process name.
|
536
|
+
>
|
537
|
+
> Procrastinator uses Ruby's `Process.setproctitle`, which only affects the title.
|
538
|
+
|
534
539
|
Either run the generated Rake tasks in a terminal or with your daemon monitoring tool of choice (eg. Monit, systemd)
|
535
540
|
|
536
541
|
```bash
|
data/RELEASE_NOTES.md
CHANGED
@@ -1,5 +1,35 @@
|
|
1
1
|
# Release Notes
|
2
2
|
|
3
|
+
## 1.1.0 (2022-10-16)
|
4
|
+
|
5
|
+
### Major Changes
|
6
|
+
|
7
|
+
* none
|
8
|
+
|
9
|
+
### Minor Changes
|
10
|
+
|
11
|
+
* Removed process name limit.
|
12
|
+
|
13
|
+
### Bugfixes
|
14
|
+
|
15
|
+
* have_task matcher:
|
16
|
+
* Fixed have_task handling of nested matchers like be_within
|
17
|
+
* Improved have_task handling of string queue names vs symbols
|
18
|
+
|
19
|
+
## 1.0.1 (2022-09-20)
|
20
|
+
|
21
|
+
### Major Changes
|
22
|
+
|
23
|
+
* none
|
24
|
+
|
25
|
+
### Minor Changes
|
26
|
+
|
27
|
+
* none
|
28
|
+
|
29
|
+
### Bugfixes
|
30
|
+
|
31
|
+
* Fixed integration error in rescheduling tasks
|
32
|
+
|
3
33
|
## 1.0.0 (2022-09-18)
|
4
34
|
|
5
35
|
### Major Changes
|
@@ -32,7 +62,7 @@
|
|
32
62
|
* `load_with` has been removed
|
33
63
|
* Removed `task_attr` and `Procrastinator::Task` module. Tasks is now duck-type checked for accessors instead.
|
34
64
|
* Added Rake tasks to manage process daemon
|
35
|
-
* Times are passed to Task Store as a Ruby Time object instead of an epoch time integer
|
65
|
+
* Times are passed to Task Store as a Ruby Time object instead of an epoch time integer
|
36
66
|
* `#delay` is now `#defer`
|
37
67
|
|
38
68
|
### Minor Changes
|
data/lib/procrastinator/queue.rb
CHANGED
@@ -10,11 +10,11 @@ module Procrastinator
|
|
10
10
|
# @!attribute [r] :task_class
|
11
11
|
# @return [Class] Class that defines the work to be done for jobs in this queue.
|
12
12
|
# @!attribute [r] :timeout
|
13
|
-
# @return [
|
13
|
+
# @return [Numeric] Duration (seconds) after which tasks in this queue should fail for taking too long.
|
14
14
|
# @!attribute [r] :max_attempts
|
15
|
-
# @return [
|
15
|
+
# @return [Integer] Maximum number of attempts for tasks in this queue.
|
16
16
|
# @!attribute [r] :update_period
|
17
|
-
# @return [
|
17
|
+
# @return [Numeric] Delay (seconds) between reloads of tasks from the task store.
|
18
18
|
class Queue
|
19
19
|
extend Forwardable
|
20
20
|
|
@@ -81,13 +81,18 @@ module Procrastinator
|
|
81
81
|
# Fetch a task matching the given identifier
|
82
82
|
#
|
83
83
|
# @param identifier [Hash] attributes to match
|
84
|
+
#
|
85
|
+
# @raise [NoSuchTaskError] when no task matches the identifier.
|
86
|
+
# @raise [AmbiguousTaskFilterError] when many tasks match the identifier, meaning you need to be more specific.
|
84
87
|
def fetch_task(identifier)
|
85
88
|
identifier[:data] = JSON.dump(identifier[:data]) if identifier[:data]
|
86
89
|
|
87
90
|
tasks = read(**identifier)
|
88
91
|
|
89
|
-
raise "no task found matching #{ identifier }" if tasks.nil? || tasks.empty?
|
90
|
-
|
92
|
+
raise NoSuchTaskError, "no task found matching #{ identifier }" if tasks.nil? || tasks.empty?
|
93
|
+
if tasks.size > 1
|
94
|
+
raise AmbiguousTaskFilterError, "too many (#{ tasks.size }) tasks match #{ identifier }. Found: #{ tasks }"
|
95
|
+
end
|
91
96
|
|
92
97
|
TaskMetaData.new(tasks.first.merge(queue: self))
|
93
98
|
end
|
@@ -97,6 +102,9 @@ module Procrastinator
|
|
97
102
|
# @param run_at [Time] Earliest time to attempt running the task
|
98
103
|
# @param expire_at [Time, nil] Time after which the task will not be attempted
|
99
104
|
# @param data [Hash, String, Numeric, nil] The data to save
|
105
|
+
#
|
106
|
+
# @raise [ArgumentError] when the keyword `:data` is needed by the task handler, but is missing
|
107
|
+
# @raise [MalformedTaskError] when the keyword `:data` is provided but not expected by the task handler.
|
100
108
|
def create(run_at:, expire_at:, data:)
|
101
109
|
if data.nil? && expects_data?
|
102
110
|
raise ArgumentError, "task #{ @task_class } expects to receive :data. Provide :data to #delay."
|
@@ -220,8 +228,16 @@ module Procrastinator
|
|
220
228
|
include QueueValidation
|
221
229
|
end
|
222
230
|
|
231
|
+
# Raised when a task matching certain criteria is requested but nothing matching is found
|
232
|
+
class NoSuchTaskError < RuntimeError
|
233
|
+
end
|
234
|
+
|
235
|
+
# Raised when a task matching certain criteria is requested but more than one option is found
|
236
|
+
class AmbiguousTaskFilterError < RuntimeError
|
237
|
+
end
|
238
|
+
|
223
239
|
# Raised when a Task Handler does not conform to the expected API
|
224
|
-
class MalformedTaskError <
|
240
|
+
class MalformedTaskError < RuntimeError
|
225
241
|
end
|
226
242
|
|
227
243
|
# Raised when a Task Store strategy does not conform to the expected API
|
@@ -5,23 +5,30 @@ require 'rspec/expectations'
|
|
5
5
|
# Determines if the given task store has a task that matches the expectation hash
|
6
6
|
RSpec::Matchers.define :have_task do |expected_task|
|
7
7
|
match do |task_store|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
8
|
+
expected_task[:queue] = expected_task[:queue].to_sym if expected_task[:queue]
|
9
|
+
|
10
|
+
Procrastinator::Task::TIME_FIELDS.each do |time_field|
|
11
|
+
if expected_task[time_field]&.respond_to?(:to_i)
|
12
|
+
expected_task[time_field] = Time.at(expected_task[time_field].to_i)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
expected = a_hash_including(expected_task)
|
17
|
+
|
18
|
+
actual_tasks = task_store.read.collect do |task|
|
19
|
+
task_hash = task.to_h
|
20
|
+
unless task_hash[:data].nil? || task_hash[:data].empty?
|
21
|
+
task_hash[:data] = JSON.parse(task_hash[:data], symbolize_names: true)
|
22
|
+
end
|
23
|
+
task_hash[:queue] = task_hash[:queue].to_sym if task_hash[:queue]
|
24
|
+
Procrastinator::Task::TIME_FIELDS.each do |time_field|
|
25
|
+
task_hash[time_field] = Time.at(task_hash[time_field].to_i) if task_hash[time_field]&.respond_to?(:to_i)
|
23
26
|
end
|
27
|
+
|
28
|
+
task_hash
|
24
29
|
end
|
30
|
+
|
31
|
+
values_match? a_collection_including(expected), actual_tasks
|
25
32
|
end
|
26
33
|
|
27
34
|
description do
|
@@ -3,9 +3,10 @@
|
|
3
3
|
require 'stringio'
|
4
4
|
|
5
5
|
module Procrastinator
|
6
|
-
# A Scheduler object provides the API for client applications to manage
|
6
|
+
# A Scheduler object provides the API for client applications to manage scheduled tasks.
|
7
7
|
#
|
8
|
-
# Use #
|
8
|
+
# Use Scheduler#defer to schedule new tasks, Scheduler#reschedule to alter existing tasks, and Scheduler#cancel to
|
9
|
+
# remove unwanted tasks.
|
9
10
|
#
|
10
11
|
# @author Robin Miller
|
11
12
|
class Scheduler
|
@@ -46,7 +47,7 @@ module Procrastinator
|
|
46
47
|
#
|
47
48
|
# @see TaskMetaData
|
48
49
|
def reschedule(queue, identifier)
|
49
|
-
UpdateProxy.new(@config, identifier: identifier
|
50
|
+
UpdateProxy.new(@config.queue(name: queue), identifier: identifier)
|
50
51
|
end
|
51
52
|
|
52
53
|
# Removes an existing task, as located by the given identifying information.
|
@@ -88,7 +89,7 @@ module Procrastinator
|
|
88
89
|
class UpdateProxy
|
89
90
|
def initialize(queue, identifier:)
|
90
91
|
@queue = queue
|
91
|
-
@identifier = identifier
|
92
|
+
@identifier = identifier.merge(queue: queue.name.to_sym)
|
92
93
|
end
|
93
94
|
|
94
95
|
def to(run_at: nil, expire_at: nil)
|
@@ -252,9 +253,6 @@ module Procrastinator
|
|
252
253
|
# Default directory to store PID files in.
|
253
254
|
DEFAULT_PID_DIR = Pathname.new('/tmp').freeze
|
254
255
|
|
255
|
-
# Maximum process name size. 15 chars is linux limit
|
256
|
-
MAX_PROC_LEN = 15
|
257
|
-
|
258
256
|
# Consumes the current process and turns it into a background daemon and proceed as #threaded.
|
259
257
|
# Additional logging is recorded in the directory specified by the Procrastinator.setup configuration.
|
260
258
|
#
|
@@ -362,11 +360,6 @@ module Procrastinator
|
|
362
360
|
def rename_process(pid_path)
|
363
361
|
name = pid_path.basename(PID_EXT).to_s
|
364
362
|
|
365
|
-
if name.size > MAX_PROC_LEN
|
366
|
-
@logger.warn "Process name is longer than max length (#{ MAX_PROC_LEN }). Trimming to fit."
|
367
|
-
name = name[0, MAX_PROC_LEN]
|
368
|
-
end
|
369
|
-
|
370
363
|
if system('pidof', name, out: File::NULL)
|
371
364
|
@logger.warn "Another process is already named '#{ name }'. Consider the 'name:' keyword to distinguish."
|
372
365
|
end
|
data/lib/procrastinator/task.rb
CHANGED
@@ -10,6 +10,9 @@ module Procrastinator
|
|
10
10
|
class Task
|
11
11
|
extend Forwardable
|
12
12
|
|
13
|
+
# Fields that store time information
|
14
|
+
TIME_FIELDS = [:run_at, :initial_run_at, :expire_at, :last_fail_at].freeze
|
15
|
+
|
13
16
|
def_delegators :@metadata,
|
14
17
|
:id, :run_at, :initial_run_at, :expire_at,
|
15
18
|
:attempts, :last_fail_at, :last_error,
|
@@ -19,9 +19,6 @@ module Procrastinator
|
|
19
19
|
HEADERS = [:id, :queue, :run_at, :initial_run_at, :expire_at,
|
20
20
|
:attempts, :last_fail_at, :last_error, :data].freeze
|
21
21
|
|
22
|
-
# Columns that store time information
|
23
|
-
TIME_FIELDS = [:run_at, :initial_run_at, :expire_at, :last_fail_at].freeze
|
24
|
-
|
25
22
|
# CSV file extension
|
26
23
|
EXT = 'csv'
|
27
24
|
|
@@ -34,7 +31,7 @@ module Procrastinator
|
|
34
31
|
READ_CONVERTER = proc do |value, field_info|
|
35
32
|
if field_info.header == :data
|
36
33
|
value
|
37
|
-
elsif TIME_FIELDS.include? field_info.header
|
34
|
+
elsif Task::TIME_FIELDS.include? field_info.header
|
38
35
|
value.empty? ? nil : Time.parse(value)
|
39
36
|
else
|
40
37
|
begin
|
@@ -130,7 +127,7 @@ module Procrastinator
|
|
130
127
|
# @return [String] Generated CSV string
|
131
128
|
def generate(data)
|
132
129
|
lines = data.collect do |d|
|
133
|
-
TIME_FIELDS.each do |field|
|
130
|
+
Task::TIME_FIELDS.each do |field|
|
134
131
|
d[field] = d[field]&.iso8601
|
135
132
|
end
|
136
133
|
CSV.generate_line(d, headers: HEADERS, force_quotes: true).strip
|
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: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robin Miller
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-10-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -147,7 +147,6 @@ files:
|
|
147
147
|
- ".rspec"
|
148
148
|
- ".rubocop.yml"
|
149
149
|
- ".ruby-version"
|
150
|
-
- ".travis.yml"
|
151
150
|
- CODE_OF_CONDUCT.md
|
152
151
|
- Gemfile
|
153
152
|
- LICENSE.txt
|
data/.travis.yml
DELETED