procrastinator 1.0.1 → 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: 51731c709ddfb60d6a9bd082e2fec2d2c0d71da125311dd54e77778c206f662f
4
- data.tar.gz: 95153dd8707910af0a3ee9c54cc48e74ad25e3a2eca8f7f69e7b042a838f8ba2
3
+ metadata.gz: 355845de0042676644aa4fd14ba5c37bc173b1f0cac9c73889dbb36aa8e5cd9c
4
+ data.tar.gz: f4faa2e3644557027c8fc08f88c973deb19cf16e43fb3312106d8d25a3c61054
5
5
  SHA512:
6
- metadata.gz: 076b5c80dcebb746405dc9a27992bc51665f90d5aeb40a67cd982e0b1eba66f8c4b85e65ba213d7e36ed27a7c736574e3cb396d5619d73a51e6877bc6df7dd99
7
- data.tar.gz: 02574c2182eb4f7f6c861a413b6c3764bdb772c3201740b8460381543e16d78779963b9dc64a9e11d8fc12f4bf100196de8e730cab697cb9b495fa3c9e0bd5ff
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,21 @@
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
+
3
19
  ## 1.0.1 (2022-09-20)
4
20
 
5
21
  ### Major Changes
@@ -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
@@ -253,9 +253,6 @@ module Procrastinator
253
253
  # Default directory to store PID files in.
254
254
  DEFAULT_PID_DIR = Pathname.new('/tmp').freeze
255
255
 
256
- # Maximum process name size. 15 chars is linux limit
257
- MAX_PROC_LEN = 15
258
-
259
256
  # Consumes the current process and turns it into a background daemon and proceed as #threaded.
260
257
  # Additional logging is recorded in the directory specified by the Procrastinator.setup configuration.
261
258
  #
@@ -363,11 +360,6 @@ module Procrastinator
363
360
  def rename_process(pid_path)
364
361
  name = pid_path.basename(PID_EXT).to_s
365
362
 
366
- if name.size > MAX_PROC_LEN
367
- @logger.warn "Process name is longer than max length (#{ MAX_PROC_LEN }). Trimming to fit."
368
- name = name[0, MAX_PROC_LEN]
369
- end
370
-
371
363
  if system('pidof', name, out: File::NULL)
372
364
  @logger.warn "Another process is already named '#{ name }'. Consider the 'name:' keyword to distinguish."
373
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.1'
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.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-09-28 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