procrastinator 1.0.1 → 1.1.0

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
  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