procrastinator 0.9.0 → 1.0.0.pre.rc3
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 +5 -5
- data/.gitignore +6 -1
- data/.rubocop.yml +20 -1
- data/README.md +327 -333
- data/RELEASE_NOTES.md +44 -0
- data/lib/procrastinator/config.rb +93 -129
- data/lib/procrastinator/logged_task.rb +50 -0
- data/lib/procrastinator/queue.rb +168 -12
- data/lib/procrastinator/queue_worker.rb +52 -97
- data/lib/procrastinator/rake/daemon_tasks.rb +86 -0
- data/lib/procrastinator/rake/tasks.rb +3 -0
- data/lib/procrastinator/scheduler.rb +297 -78
- data/lib/procrastinator/task.rb +46 -28
- data/lib/procrastinator/task_meta_data.rb +96 -52
- data/lib/procrastinator/task_store/file_transaction.rb +76 -0
- data/lib/procrastinator/task_store/simple_comma_store.rb +164 -0
- data/lib/procrastinator/test/mocks.rb +35 -0
- data/lib/procrastinator/version.rb +1 -1
- data/lib/procrastinator.rb +9 -24
- data/procrastinator.gemspec +13 -9
- metadata +43 -26
- data/lib/procrastinator/loaders/csv_loader.rb +0 -107
- data/lib/procrastinator/queue_manager.rb +0 -201
- data/lib/procrastinator/task_worker.rb +0 -100
- data/lib/rake/procrastinator_task.rb +0 -34
@@ -1,133 +1,88 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Procrastinator
|
4
|
-
# A QueueWorker checks for tasks to run from the
|
5
|
-
#
|
4
|
+
# A QueueWorker checks for tasks to run from the task store and executes them, updating information in the task
|
5
|
+
# store as necessary.
|
6
6
|
#
|
7
7
|
# @author Robin Miller
|
8
8
|
class QueueWorker
|
9
9
|
extend Forwardable
|
10
10
|
|
11
|
-
def_delegators :@queue, :name
|
11
|
+
def_delegators :@queue, :name, :next_task
|
12
12
|
|
13
13
|
# expected methods for all persistence strategies
|
14
14
|
PERSISTER_METHODS = [:read, :update, :delete].freeze
|
15
15
|
|
16
|
-
def initialize(queue:, config
|
17
|
-
|
18
|
-
|
19
|
-
@scheduler = scheduler
|
16
|
+
def initialize(queue:, config:)
|
17
|
+
raise ArgumentError, ':queue cannot be nil' if queue.nil?
|
18
|
+
raise ArgumentError, ':config cannot be nil' if config.nil?
|
20
19
|
|
21
|
-
@
|
22
|
-
end
|
23
|
-
|
24
|
-
def work
|
25
|
-
start_log
|
20
|
+
@config = config
|
26
21
|
|
27
|
-
|
28
|
-
|
29
|
-
|
22
|
+
@queue = if queue.is_a? Symbol
|
23
|
+
config.queue(name: queue)
|
24
|
+
else
|
25
|
+
queue
|
26
|
+
end
|
30
27
|
|
31
|
-
|
32
|
-
|
33
|
-
rescue StandardError => e
|
34
|
-
raise if @config.test_mode? || !@logger
|
35
|
-
|
36
|
-
@logger.fatal(e)
|
37
|
-
end
|
28
|
+
@scheduler = Scheduler.new(config)
|
29
|
+
@logger = Logger.new(StringIO.new)
|
38
30
|
end
|
39
31
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
32
|
+
# Works on jobs forever
|
33
|
+
def work!
|
34
|
+
@logger = open_log!("#{ name }-queue-worker", @config)
|
35
|
+
@logger.info("Started worker thread to consume queue: #{ name }")
|
44
36
|
|
45
|
-
|
46
|
-
|
37
|
+
loop do
|
38
|
+
sleep(@queue.update_period)
|
47
39
|
|
48
|
-
|
49
|
-
|
50
|
-
if tw.successful?
|
51
|
-
persister.delete(metadata.id)
|
52
|
-
else
|
53
|
-
persister.update(metadata.id, tw.to_h.merge(queue: @queue.name.to_s))
|
54
|
-
end
|
40
|
+
work_one
|
55
41
|
end
|
56
|
-
|
57
|
-
|
58
|
-
def long_name
|
59
|
-
name = "#{ @queue.name }-queue-worker"
|
60
|
-
|
61
|
-
name = "#{ @config.prefix }-#{ name }" if @config.prefix
|
42
|
+
rescue StandardError => e
|
43
|
+
@logger.fatal(e)
|
62
44
|
|
63
|
-
|
45
|
+
raise
|
64
46
|
end
|
65
47
|
|
66
|
-
#
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
48
|
+
# Performs exactly one task on the queue
|
49
|
+
def work_one
|
50
|
+
task = next_task(logger: @logger,
|
51
|
+
container: @config.container,
|
52
|
+
scheduler: @scheduler) || return
|
71
53
|
|
72
|
-
|
73
|
-
|
74
|
-
msg = <<~MSG
|
75
|
-
======================================================================
|
76
|
-
Started worker process, #{ long_name }, to work off queue #{ @queue.name }.
|
77
|
-
Worker pid=#{ Process.pid }; parent pid=#{ Process.ppid }.
|
78
|
-
======================================================================
|
79
|
-
MSG
|
80
|
-
|
81
|
-
@logger.info("\n#{ msg }")
|
82
|
-
end
|
83
|
-
|
84
|
-
private
|
85
|
-
|
86
|
-
def build_worker(metadata)
|
87
|
-
start_log
|
88
|
-
|
89
|
-
TaskWorker.new(metadata: metadata,
|
90
|
-
queue: @queue,
|
91
|
-
scheduler: @scheduler,
|
92
|
-
context: @config.context,
|
93
|
-
logger: @logger)
|
94
|
-
end
|
95
|
-
|
96
|
-
def log_target
|
97
|
-
return $stdout if @config.test_mode?
|
98
|
-
|
99
|
-
log_path = @config.log_dir + "#{ long_name }.log"
|
100
|
-
|
101
|
-
write_log_file(log_path)
|
54
|
+
begin
|
55
|
+
task.run
|
102
56
|
|
103
|
-
|
104
|
-
|
57
|
+
@queue.delete(task.id)
|
58
|
+
rescue StandardError => e
|
59
|
+
task.fail(e)
|
105
60
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
f.write ''
|
61
|
+
task_info = task.to_h
|
62
|
+
id = task_info.delete(:id)
|
63
|
+
@queue.update(id, **task_info)
|
110
64
|
end
|
111
65
|
end
|
112
66
|
|
113
|
-
def
|
114
|
-
|
67
|
+
def halt
|
68
|
+
@logger&.info("Halted worker on queue: #{ name }")
|
69
|
+
@logger&.close
|
70
|
+
end
|
115
71
|
|
116
|
-
|
72
|
+
# Starts a log file and returns the created Logger
|
73
|
+
def open_log!(name, config)
|
74
|
+
return @logger unless config.log_level
|
117
75
|
|
118
|
-
|
119
|
-
TaskMetaData.new(t.delete_if { |key| !TaskMetaData::EXPECTED_DATA.include?(key) })
|
120
|
-
end
|
76
|
+
log_path = config.log_dir / "#{ name }.log"
|
121
77
|
|
122
|
-
|
123
|
-
|
78
|
+
config.log_dir.mkpath
|
79
|
+
FileUtils.touch(log_path)
|
124
80
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
tasks.shuffle.sort_by { |t| t[:run_at] }.first(@queue.max_tasks)
|
81
|
+
Logger.new(log_path.to_path,
|
82
|
+
config.log_shift_age, config.log_shift_size,
|
83
|
+
level: config.log_level || Logger::FATAL,
|
84
|
+
progname: name,
|
85
|
+
formatter: Config::DEFAULT_LOG_FORMATTER)
|
131
86
|
end
|
132
87
|
end
|
133
88
|
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
|
5
|
+
module Procrastinator
|
6
|
+
module Rake
|
7
|
+
# RakeTask builder. Provide this in your Rakefile:
|
8
|
+
#
|
9
|
+
# require 'procrastinator/rake/task'
|
10
|
+
# Procrastinator::RakeTask.new('/var/run') do
|
11
|
+
# # return your Procrastinator::Scheduler here or construct it using Procrastinator.config
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
class DaemonTasks
|
15
|
+
include ::Rake::Cloneable
|
16
|
+
include ::Rake::DSL
|
17
|
+
|
18
|
+
# Shorthand for DaemonTasks.new.define
|
19
|
+
#
|
20
|
+
# @param (see #define)
|
21
|
+
# @see DaemonTasks#define
|
22
|
+
def self.define(**args, &block)
|
23
|
+
new.define(**args, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Defines procrastinator:start and procrastinator:stop Rake tasks that operate on the given scheduler.
|
27
|
+
#
|
28
|
+
# @param pid_path [Pathname, File, String, nil] The pid file path
|
29
|
+
# @yieldreturn scheduler [Procrastinator::Scheduler]
|
30
|
+
#
|
31
|
+
# @see Scheduler::DaemonWorking#daemonized!
|
32
|
+
def define(pid_path: nil)
|
33
|
+
raise ArgumentError, 'must provide a scheduler builder block' unless block_given?
|
34
|
+
|
35
|
+
@pid_path = Scheduler::DaemonWorking.normalize_pid pid_path
|
36
|
+
|
37
|
+
namespace :procrastinator do
|
38
|
+
desc 'Start the Procrastinator daemon'
|
39
|
+
task :start do
|
40
|
+
start(yield)
|
41
|
+
end
|
42
|
+
|
43
|
+
desc 'Show Procrastinator daemon status'
|
44
|
+
task :status do
|
45
|
+
status
|
46
|
+
end
|
47
|
+
|
48
|
+
desc 'Stop the Procrastinator daemon'
|
49
|
+
task stop: [:status] do
|
50
|
+
stop
|
51
|
+
end
|
52
|
+
|
53
|
+
desc 'Restart Procrastinator daemon'
|
54
|
+
task restart: [:stop, :start]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def start(scheduler)
|
61
|
+
warn 'Starting Procrastinator'
|
62
|
+
scheduler.work.daemonized!(@pid_path)
|
63
|
+
end
|
64
|
+
|
65
|
+
def status
|
66
|
+
warn "Checking #{ @pid_path }..."
|
67
|
+
msg = if Scheduler::DaemonWorking.running?(@pid_path)
|
68
|
+
"Procrastinator pid #{ File.read(@pid_path) } instance running."
|
69
|
+
elsif File.exist?(@pid_path)
|
70
|
+
"Procrastinator pid #{ File.read(@pid_path) } is not running. Maybe it crashed?"
|
71
|
+
else
|
72
|
+
"Procrastinator is not running (No such file - #{ @pid_path })"
|
73
|
+
end
|
74
|
+
|
75
|
+
warn msg
|
76
|
+
end
|
77
|
+
|
78
|
+
def stop
|
79
|
+
return unless Scheduler::DaemonWorking.running?(@pid_path)
|
80
|
+
|
81
|
+
Scheduler::DaemonWorking.halt!(@pid_path)
|
82
|
+
warn "Procrastinator pid #{ File.read(@pid_path) } halted."
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|