procrastinator 0.9.0 → 1.0.0.pre.rc2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|