procrastinator 0.9.0 → 1.0.0.pre.rc2
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 +54 -0
- data/lib/procrastinator/rake/tasks.rb +3 -0
- data/lib/procrastinator/scheduler.rb +299 -77
- 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 +161 -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,54 @@
|
|
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)
|
23
|
+
new.define(**args)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Defines procrastinator:start and procrastinator:stop Rake tasks that operate on the given scheduler.
|
27
|
+
# If provided a block, that block will run in the daemon process.
|
28
|
+
#
|
29
|
+
# @param scheduler [Procrastinator::Scheduler]
|
30
|
+
# @param pid_path [Pathname, File, String, nil]
|
31
|
+
def define(scheduler:, pid_path: nil, &block)
|
32
|
+
pid_path = Scheduler::DaemonWorking.normalize_pid(pid_path)
|
33
|
+
|
34
|
+
namespace :procrastinator do
|
35
|
+
task :start do
|
36
|
+
scheduler.work.daemonized!(pid_path, &block)
|
37
|
+
end
|
38
|
+
|
39
|
+
task :status do
|
40
|
+
if Scheduler::DaemonWorking.running?(pid_path)
|
41
|
+
warn "Procrastinator instance running (pid #{ File.read(pid_path) })"
|
42
|
+
else
|
43
|
+
warn "No Procrastinator instance detected for #{ pid_path }"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
task :stop do
|
48
|
+
Scheduler::DaemonWorking.halt!(pid_path)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|