procrastinator 1.0.0.pre.rc4 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,74 +3,72 @@
3
3
  require 'pathname'
4
4
 
5
5
  module Procrastinator
6
- module TaskStore
7
- # The general idea is that there may be two threads that need to do these actions on the same file:
8
- # thread A: read
9
- # thread B: read
10
- # thread A/B: write
11
- # thread A/B: write
12
- #
13
- # When this sequence happens, the second file write is based on old information and loses the info from
14
- # the prior write. Using a global mutex per file path prevents this case.
15
- #
16
- # This situation can also occur with multi processing, so file locking is also used for solitary access.
17
- # File locking is only advisory in some systems, though, so it may only work against other applications
18
- # that request a lock.
19
- #
20
- # @author Robin Miller
21
- class FileTransaction
22
- # Holds the mutual exclusion locks for file paths by name
23
- @file_mutex = {}
6
+ # The general idea is that there may be two threads that need to do these actions on the same file:
7
+ # thread A: read
8
+ # thread B: read
9
+ # thread A/B: write
10
+ # thread A/B: write
11
+ #
12
+ # When this sequence happens, the second file write is based on old information and loses the info from
13
+ # the prior write. Using a global mutex per file path prevents this case.
14
+ #
15
+ # This situation can also occur with multi processing, so file locking is also used for solitary access.
16
+ # File locking is only advisory in some systems, though, so it may only work against other applications
17
+ # that request a lock.
18
+ #
19
+ # @author Robin Miller
20
+ class FileTransaction
21
+ # Holds the mutual exclusion locks for file paths by name
22
+ @file_mutex = {}
24
23
 
25
- class << self
26
- attr_reader :file_mutex
27
- end
24
+ class << self
25
+ attr_reader :file_mutex
26
+ end
28
27
 
29
- def initialize(path)
30
- @path = ensure_path(path)
31
- end
28
+ def initialize(path)
29
+ @path = ensure_path(path)
30
+ end
32
31
 
33
- # Alias for transact(writable: false)
34
- def read(&block)
35
- transact(writable: false, &block)
36
- end
32
+ # Alias for transact(writable: false)
33
+ def read(&block)
34
+ transact(writable: false, &block)
35
+ end
37
36
 
38
- # Alias for transact(writable: true)
39
- def write(&block)
40
- transact(writable: true, &block)
41
- end
37
+ # Alias for transact(writable: true)
38
+ def write(&block)
39
+ transact(writable: true, &block)
40
+ end
42
41
 
43
- # Completes the given block as an atomic transaction locked using a global mutex table.
44
- # The block is provided the current file contents.
45
- # The block's result is written to the file.
46
- def transact(writable: false)
47
- semaphore = FileTransaction.file_mutex[@path.to_s] ||= Mutex.new
42
+ # Completes the given block as an atomic transaction locked using a global mutex table.
43
+ # The block is provided the current file contents.
44
+ # The block's result is written to the file.
45
+ def transact(writable: false)
46
+ semaphore = FileTransaction.file_mutex[@path.to_s] ||= Mutex.new
48
47
 
49
- semaphore.synchronize do
50
- @path.open(writable ? 'r+' : 'r') do |file|
51
- file.flock(File::LOCK_EX)
48
+ semaphore.synchronize do
49
+ @path.open(writable ? 'r+' : 'r') do |file|
50
+ file.flock(File::LOCK_EX)
52
51
 
53
- yield_result = yield(file.read)
54
- if writable
55
- file.rewind
56
- file.write yield_result
57
- file.truncate(file.pos)
58
- end
59
- yield_result
52
+ yield_result = yield(file.read)
53
+ if writable
54
+ file.rewind
55
+ file.write yield_result
56
+ file.truncate(file.pos)
60
57
  end
58
+ yield_result
61
59
  end
62
60
  end
61
+ end
63
62
 
64
- private
63
+ private
65
64
 
66
- def ensure_path(path)
67
- path = Pathname.new path
68
- unless path.exist?
69
- path.dirname.mkpath
70
- FileUtils.touch path
71
- end
72
- path
65
+ def ensure_path(path)
66
+ path = Pathname.new path
67
+ unless path.exist?
68
+ path.dirname.mkpath
69
+ FileUtils.touch path
73
70
  end
71
+ path
74
72
  end
75
73
  end
76
74
  end
@@ -4,25 +4,33 @@ require 'csv'
4
4
  require 'pathname'
5
5
 
6
6
  module Procrastinator
7
+ # Task storage strategies.
8
+ #
9
+ # All task stores must implement the API #read, #create, #update, #delete.
7
10
  module TaskStore
8
11
  # Simple Task I/O adapter that writes task information (ie. TaskMetaData attributes) to a CSV file.
9
12
  #
10
- # SimpleCommaStore is not designed for efficiency or large loads (10,000+ tasks).
11
- #
12
- # For critical production environments, it is strongly recommended to use a more robust storage mechanism like a
13
- # proper database.
13
+ # SimpleCommaStore is not designed for efficiency or large loads (10,000+ tasks). For critical production
14
+ # environments, it is strongly recommended to use a more robust storage mechanism like a proper database.
14
15
  #
15
16
  # @author Robin Miller
16
17
  class SimpleCommaStore
17
- # ordered
18
+ # Ordered list of CSV column headers
18
19
  HEADERS = [:id, :queue, :run_at, :initial_run_at, :expire_at,
19
20
  :attempts, :last_fail_at, :last_error, :data].freeze
20
21
 
21
- EXT = 'csv'
22
- DEFAULT_FILE = Pathname.new("procrastinator-tasks.#{ EXT }").freeze
23
-
22
+ # Columns that store time information
24
23
  TIME_FIELDS = [:run_at, :initial_run_at, :expire_at, :last_fail_at].freeze
25
24
 
25
+ # CSV file extension
26
+ EXT = 'csv'
27
+
28
+ # Default filename
29
+ DEFAULT_FILE = Pathname.new("procrastinator-tasks.#{ EXT }").freeze
30
+
31
+ # CSV Converter lambda
32
+ #
33
+ # @see CSV
26
34
  READ_CONVERTER = proc do |value, field_info|
27
35
  if field_info.header == :data
28
36
  value
@@ -53,6 +61,10 @@ module Procrastinator
53
61
  freeze
54
62
  end
55
63
 
64
+ # Parses the CSV file for data matching the given filter, or all if no filter provided.
65
+ #
66
+ # @param filter [Hash] Specified attributes to match.
67
+ # @return [Array<Hash>]
56
68
  def read(filter = {})
57
69
  CSVFileTransaction.new(@path).read do |existing_data|
58
70
  existing_data.select do |row|
@@ -69,7 +81,7 @@ module Procrastinator
69
81
  # @param run_at [Time, nil] time to run the task at
70
82
  # @param initial_run_at [Time, nil] first time to run the task at. Defaults to run_at.
71
83
  # @param expire_at [Time, nil] time to expire the task
72
- def create(queue:, run_at:, expire_at:, data: '', initial_run_at: nil)
84
+ def create(queue:, run_at:, expire_at: nil, data: '', initial_run_at: nil)
73
85
  CSVFileTransaction.new(@path).write do |tasks|
74
86
  max_id = tasks.collect { |task| task[:id] }.max || 0
75
87
 
@@ -87,6 +99,10 @@ module Procrastinator
87
99
  end
88
100
  end
89
101
 
102
+ # Updates an existing task in the CSV file.
103
+ #
104
+ # @param id [Integer] task ID number
105
+ # @param data [Hash] new data to save
90
106
  def update(id, data)
91
107
  CSVFileTransaction.new(@path).write do |tasks|
92
108
  task_data = tasks.find do |task|
@@ -99,12 +115,19 @@ module Procrastinator
99
115
  end
100
116
  end
101
117
 
118
+ # Removes an existing task from the CSV file.
119
+ #
120
+ # @param id [Integer] task ID number
102
121
  def delete(id)
103
122
  CSVFileTransaction.new(@path).write do |existing_data|
104
123
  generate(existing_data.reject { |task| task[:id] == id })
105
124
  end
106
125
  end
107
126
 
127
+ # Generates a CSV string from the given data.
128
+ #
129
+ # @param data [Array] list of data to convert into CSV
130
+ # @return [String] Generated CSV string
108
131
  def generate(data)
109
132
  lines = data.collect do |d|
110
133
  TIME_FIELDS.each do |field|
@@ -120,6 +143,7 @@ module Procrastinator
120
143
 
121
144
  # Adds CSV parsing to the file reading
122
145
  class CSVFileTransaction < FileTransaction
146
+ # (see FileTransaction#transact)
123
147
  def transact(writable: nil)
124
148
  super(writable: writable) do |file_str|
125
149
  yield(parse(file_str))
@@ -1,6 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Procrastinator
4
+ # Reusable Test classes for mocking out object queues.
5
+ #
6
+ # require 'procrastinator/test/mocks'
7
+ #
8
+ # Procrastinator.setup do |config|
9
+ # config.define_queue :test_queue, Procrastinator::Test::Mock
10
+ # end
4
11
  module Test
5
12
  # Testing mock Task class
6
13
  #
@@ -16,10 +23,12 @@ module Procrastinator
16
23
  class MockTask
17
24
  attr_accessor :container, :logger, :scheduler
18
25
 
26
+ # Records that the mock task was run.
19
27
  def run
20
28
  @run = true
21
29
  end
22
30
 
31
+ # @return [Boolean] Whether the task was run
23
32
  def run?
24
33
  @run
25
34
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Procrastinator
4
- VERSION = '1.0.0-rc4'
4
+ # Version number of this release
5
+ VERSION = '1.0.1'
5
6
  end
@@ -33,4 +33,5 @@ Gem::Specification.new do |spec|
33
33
  spec.add_development_dependency 'rubocop-performance', '~> 1.10'
34
34
  spec.add_development_dependency 'simplecov', '~> 0.18.0'
35
35
  spec.add_development_dependency 'timecop', '~> 0.9'
36
+ spec.add_development_dependency 'yard', '~> 0.9'
36
37
  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.pre.rc4
4
+ version: 1.0.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: 2022-09-17 00:00:00.000000000 Z
11
+ date: 2022-09-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -122,6 +122,20 @@ dependencies:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0.9'
125
+ - !ruby/object:Gem::Dependency
126
+ name: yard
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.9'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0.9'
125
139
  description: A flexible pure Ruby job queue. Tasks are reschedulable after failures.
126
140
  email:
127
141
  - robin@tenjin.ca
@@ -133,7 +147,6 @@ files:
133
147
  - ".rspec"
134
148
  - ".rubocop.yml"
135
149
  - ".ruby-version"
136
- - ".travis.yml"
137
150
  - CODE_OF_CONDUCT.md
138
151
  - Gemfile
139
152
  - LICENSE.txt
@@ -149,6 +162,7 @@ files:
149
162
  - lib/procrastinator/queue_worker.rb
150
163
  - lib/procrastinator/rake/daemon_tasks.rb
151
164
  - lib/procrastinator/rake/tasks.rb
165
+ - lib/procrastinator/rspec/matchers.rb
152
166
  - lib/procrastinator/scheduler.rb
153
167
  - lib/procrastinator/task.rb
154
168
  - lib/procrastinator/task_meta_data.rb
@@ -173,9 +187,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
173
187
  version: '2.4'
174
188
  required_rubygems_version: !ruby/object:Gem::Requirement
175
189
  requirements:
176
- - - ">"
190
+ - - ">="
177
191
  - !ruby/object:Gem::Version
178
- version: 1.3.1
192
+ version: '0'
179
193
  requirements: []
180
194
  rubygems_version: 3.1.2
181
195
  signing_key:
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