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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e3d2c026b206d52c22ad9cb182fa7ea48ff2569eb99647a5cf75aa20e56bde5e
4
- data.tar.gz: 1a82220d0b7485bb3de7263fa6bc2265f1612601eab1769eb306c9a5930073ca
3
+ metadata.gz: 355845de0042676644aa4fd14ba5c37bc173b1f0cac9c73889dbb36aa8e5cd9c
4
+ data.tar.gz: f4faa2e3644557027c8fc08f88c973deb19cf16e43fb3312106d8d25a3c61054
5
5
  SHA512:
6
- metadata.gz: bc20963d1541d72024af63effe933823162012deea562f04bbe4303c6c380b26476d8305b6a94551ac50424101e0d7a072d705d01dcbc1ba1f231cd5c51f3184
7
- data.tar.gz: 670bc2fe498c40a7c32d2557956321237d9307941397a5bd1c1c7c1718b24e3ceaaa24ee2b8e5960b9967120467b46ad3c3df313ed00ab3a6e77830e626937fb
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 name the daemon process by specifying the pid_path with a specific .pid file. If does not end with '.pid' it is
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
@@ -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 [Object] Duration (seconds) after which tasks in this queue should fail for taking too long.
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 [Object] Maximum number of attempts for tasks in this queue.
15
+ # @return [Integer] Maximum number of attempts for tasks in this queue.
16
16
  # @!attribute [r] :update_period
17
- # @return [Pathname] Delay (seconds) between reloads of tasks from the task store.
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
- raise "too many (#{ tasks.size }) tasks match #{ identifier }. Found: #{ tasks }" if tasks.size > 1
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 < StandardError
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
- task_store.read.any? do |task|
9
- task_hash = task.to_h
10
- task_hash[:data] = JSON.parse(task_hash[:data], symbolize_names: true) unless task_hash[:data].empty?
11
-
12
- expected_task.all? do |field, expected_value|
13
- expected_value = case field
14
- when :queue
15
- expected_value.to_sym
16
- when :run_at, :initial_run_at, :expire_at, :last_fail_at
17
- Time.at(expected_value.to_i)
18
- else
19
- expected_value
20
- end
21
-
22
- values_match? expected_value, task_hash[field]
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 delayed tasks.
6
+ # A Scheduler object provides the API for client applications to manage scheduled tasks.
7
7
  #
8
- # Use #delay to schedule new tasks, #reschedule to alter existing tasks, and #cancel to remove unwanted tasks.
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.merge(queue: queue.to_s))
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
@@ -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
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Procrastinator
4
4
  # Version number of this release
5
- VERSION = '1.0.0'
5
+ VERSION = '1.1.0'
6
6
  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: 1.0.0
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-09-19 00:00:00.000000000 Z
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
@@ -1,4 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.2.1
4
- before_install: gem install bundler -v 1.11.2