procrastinator 0.6.1 → 0.9.0
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 +4 -4
- data/.rubocop.yml +7 -0
- data/.ruby-version +1 -1
- data/Gemfile +2 -0
- data/README.md +482 -128
- data/Rakefile +5 -3
- data/lib/procrastinator.rb +39 -18
- data/lib/procrastinator/config.rb +185 -0
- data/lib/procrastinator/loaders/csv_loader.rb +107 -0
- data/lib/procrastinator/queue.rb +50 -0
- data/lib/procrastinator/queue_manager.rb +201 -0
- data/lib/procrastinator/queue_worker.rb +91 -71
- data/lib/procrastinator/scheduler.rb +171 -0
- data/lib/procrastinator/task.rb +46 -0
- data/lib/procrastinator/task_meta_data.rb +128 -0
- data/lib/procrastinator/task_worker.rb +66 -86
- data/lib/procrastinator/version.rb +3 -1
- data/lib/rake/procrastinator_task.rb +34 -0
- data/procrastinator.gemspec +11 -9
- metadata +40 -19
- data/lib/procrastinator/environment.rb +0 -148
@@ -1,120 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'yaml'
|
4
|
+
require 'ostruct'
|
5
|
+
require 'timeout'
|
2
6
|
|
3
7
|
module Procrastinator
|
8
|
+
# Works on a given task by creating a new instance of the queue's task class and running the appropriate hooks.
|
9
|
+
#
|
10
|
+
# The behaviour outside of the actual user-defined task is guided by the provided metadata.
|
11
|
+
#
|
12
|
+
# @author Robin Miller
|
13
|
+
#
|
14
|
+
# @see TaskMetaData
|
4
15
|
class TaskWorker
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
run_at:
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
@
|
19
|
-
|
20
|
-
@
|
21
|
-
@
|
22
|
-
|
23
|
-
@
|
24
|
-
@
|
25
|
-
@
|
26
|
-
@
|
27
|
-
|
28
|
-
@logger
|
29
|
-
|
30
|
-
|
31
|
-
raise
|
16
|
+
extend Forwardable
|
17
|
+
|
18
|
+
def_delegators :@metadata,
|
19
|
+
:id, :run_at, :initial_run_at, :expire_at,
|
20
|
+
:attempts, :last_fail_at, :last_error,
|
21
|
+
:data,
|
22
|
+
:to_h, :successful?
|
23
|
+
|
24
|
+
def initialize(metadata:,
|
25
|
+
queue:,
|
26
|
+
logger: Logger.new(StringIO.new),
|
27
|
+
context: nil,
|
28
|
+
scheduler: nil)
|
29
|
+
@queue = queue
|
30
|
+
|
31
|
+
@metadata = metadata
|
32
|
+
@task = queue.task_class.new
|
33
|
+
|
34
|
+
@task.data = @metadata.data if @task.respond_to?(:data=)
|
35
|
+
@task.context = context if @task.respond_to?(:context=)
|
36
|
+
@task.logger = logger if @task.respond_to?(:logger=)
|
37
|
+
@task.scheduler = scheduler if @task.respond_to?(:scheduler=)
|
38
|
+
|
39
|
+
@logger = logger
|
40
|
+
@context = context
|
41
|
+
|
42
|
+
raise MalformedTaskError, "task #{ @task.class } does not support #run method" unless @task.respond_to? :run
|
32
43
|
end
|
33
44
|
|
34
45
|
def work
|
35
|
-
@
|
46
|
+
@metadata.add_attempt
|
36
47
|
|
37
48
|
begin
|
38
|
-
|
49
|
+
@metadata.verify_expiry!
|
39
50
|
|
40
|
-
Timeout
|
51
|
+
result = Timeout.timeout(@queue.timeout) do
|
41
52
|
@task.run
|
42
53
|
end
|
43
54
|
|
44
|
-
|
55
|
+
@logger&.debug("Task completed: #{ @task.class } [#{ @metadata.serialized_data }]")
|
45
56
|
|
46
|
-
@
|
57
|
+
@metadata.clear_fails
|
47
58
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
if too_many_fails? || expired?
|
54
|
-
try_hook(:final_fail, @logger, e)
|
55
|
-
|
56
|
-
@run_at = nil
|
57
|
-
@last_error = "#{expired? ? 'Task expired' : 'Task failed too many times'}: #{e.backtrace.join("\n")}"
|
58
|
-
|
59
|
-
@logger.debug("Task failed permanently: #{YAML.dump(@task)}")
|
59
|
+
try_hook(:success, result)
|
60
|
+
rescue StandardError => error
|
61
|
+
if @metadata.final_fail?(@queue)
|
62
|
+
handle_final_failure(error)
|
60
63
|
else
|
61
|
-
|
62
|
-
|
63
|
-
@last_error = %Q[Task failed: #{e.message}\n#{e.backtrace.join("\n")}]
|
64
|
-
@logger.debug("Task failed: #{YAML.dump(@task)}")
|
65
|
-
|
66
|
-
reschedule
|
64
|
+
handle_failure(error)
|
67
65
|
end
|
68
66
|
end
|
69
67
|
end
|
70
68
|
|
71
|
-
|
72
|
-
if !expired? && @attempts <= 0
|
73
|
-
raise(RuntimeError, 'you cannot check for success before running #work')
|
74
|
-
end
|
69
|
+
private
|
75
70
|
|
76
|
-
|
71
|
+
def try_hook(method, *params)
|
72
|
+
@task.send(method, *params) if @task.respond_to? method
|
73
|
+
rescue StandardError => e
|
74
|
+
warn "#{ method.to_s.capitalize } hook error: #{ e.message }"
|
77
75
|
end
|
78
76
|
|
79
|
-
def
|
80
|
-
|
81
|
-
|
77
|
+
def handle_failure(error)
|
78
|
+
@metadata.fail(%[Task failed: #{ error.message }\n#{ error.backtrace.join("\n") }])
|
79
|
+
@logger&.debug("Task failed: #{ @queue.name } with #{ @metadata.serialized_data }")
|
82
80
|
|
83
|
-
|
84
|
-
!@expire_at.nil? && Time.now.to_i > @expire_at
|
85
|
-
end
|
81
|
+
@metadata.reschedule
|
86
82
|
|
87
|
-
|
88
|
-
{id: @id,
|
89
|
-
run_at: @run_at,
|
90
|
-
initial_run_at: @initial_run_at,
|
91
|
-
expire_at: @expire_at,
|
92
|
-
attempts: @attempts,
|
93
|
-
last_fail_at: @last_fail_at,
|
94
|
-
last_error: @last_error,
|
95
|
-
task: YAML.dump(@task)}
|
83
|
+
try_hook(:fail, error)
|
96
84
|
end
|
97
85
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
@task.send(method, *params) if @task.respond_to? method
|
102
|
-
rescue StandardError => e
|
103
|
-
$stderr.puts "#{method.to_s.capitalize} hook error: #{e.message}"
|
104
|
-
end
|
105
|
-
end
|
86
|
+
def handle_final_failure(error)
|
87
|
+
trace = error.backtrace.join("\n")
|
88
|
+
msg = "#{ @metadata.expired? ? 'Task expired' : 'Task failed too many times' }: #{ trace }"
|
106
89
|
|
107
|
-
|
108
|
-
# (30 + n_attempts^4) seconds is chosen to rapidly expand
|
109
|
-
# but with the baseline of 30s to avoid hitting the disc too frequently.
|
90
|
+
@metadata.fail(msg, final: true)
|
110
91
|
|
111
|
-
@
|
112
|
-
end
|
113
|
-
end
|
92
|
+
@logger&.debug("Task failed permanently: #{ YAML.dump(@task) }")
|
114
93
|
|
115
|
-
|
94
|
+
try_hook(:final_fail, error)
|
95
|
+
end
|
116
96
|
end
|
117
97
|
|
118
98
|
class MalformedTaskError < StandardError
|
119
99
|
end
|
120
|
-
end
|
100
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
namespace :procrastinator do
|
5
|
+
desc 'Halt all Procrastinator processes'
|
6
|
+
task :stop, [:pid_dir] do |task, args|
|
7
|
+
pid_dir = args[:pid_dir] || Procrastinator::Config::DEFAULT_PID_DIRECTORY
|
8
|
+
|
9
|
+
if !pid_dir.exist? || pid_dir.empty?
|
10
|
+
raise <<~ERR
|
11
|
+
Default PID directory does not exist or is empty. Run:
|
12
|
+
rake procrastinator:stop[directory]
|
13
|
+
with the directory to search for pid files.
|
14
|
+
ERR
|
15
|
+
end
|
16
|
+
|
17
|
+
pid_dir.each_child do |file|
|
18
|
+
pid = file.read.to_i
|
19
|
+
|
20
|
+
begin
|
21
|
+
name = `ps -p #{pid} -o command`
|
22
|
+
print "Halting worker process #{name} (pid: #{ pid })... "
|
23
|
+
Process.kill('KILL', pid)
|
24
|
+
puts 'halted'
|
25
|
+
rescue Errno::ESRCH
|
26
|
+
warn "Expected worker process pid=#{ pid }, but none was found. Continuing."
|
27
|
+
end
|
28
|
+
|
29
|
+
file.delete
|
30
|
+
end
|
31
|
+
|
32
|
+
puts '<= procrastinator:stop executed'
|
33
|
+
end
|
34
|
+
end
|
data/procrastinator.gemspec
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require 'procrastinator/version'
|
5
6
|
|
@@ -9,8 +10,8 @@ Gem::Specification.new do |spec|
|
|
9
10
|
spec.authors = ['Robin Miller']
|
10
11
|
spec.email = ['robin@tenjin.ca']
|
11
12
|
|
12
|
-
spec.summary =
|
13
|
-
spec.description =
|
13
|
+
spec.summary = 'For apps that put work off until later'
|
14
|
+
spec.description = 'A straightforward, customizable Ruby job queue with zero dependencies.'
|
14
15
|
spec.homepage = 'https://github.com/TenjinInc/procrastinator'
|
15
16
|
spec.license = 'MIT'
|
16
17
|
|
@@ -19,12 +20,13 @@ Gem::Specification.new do |spec|
|
|
19
20
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
21
|
spec.require_paths = ['lib']
|
21
22
|
|
22
|
-
spec.required_ruby_version = '
|
23
|
+
spec.required_ruby_version = '>= 2.3'
|
23
24
|
|
24
25
|
spec.add_development_dependency 'bundler', '~> 1.11'
|
25
|
-
spec.add_development_dependency 'rake', '~> 10.0'
|
26
|
-
spec.add_development_dependency 'rspec', '~> 3.0'
|
27
|
-
spec.add_development_dependency 'timecop', '~> 0.8'
|
28
|
-
spec.add_development_dependency 'simplecov', '~> 0.11'
|
29
26
|
spec.add_development_dependency 'fakefs', '~> 0.10'
|
27
|
+
spec.add_development_dependency 'rake', '~> 12.3'
|
28
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
29
|
+
spec.add_development_dependency 'rubocop', '~> 0.58'
|
30
|
+
spec.add_development_dependency 'simplecov', '~> 0.16.1'
|
31
|
+
spec.add_development_dependency 'timecop', '~> 0.9'
|
30
32
|
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: 0.
|
4
|
+
version: 0.9.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:
|
11
|
+
date: 2018-07-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -24,20 +24,34 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.11'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: fakefs
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.10'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.10'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: rake
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
30
44
|
requirements:
|
31
45
|
- - "~>"
|
32
46
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
47
|
+
version: '12.3'
|
34
48
|
type: :development
|
35
49
|
prerelease: false
|
36
50
|
version_requirements: !ruby/object:Gem::Requirement
|
37
51
|
requirements:
|
38
52
|
- - "~>"
|
39
53
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
54
|
+
version: '12.3'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: rspec
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -53,49 +67,48 @@ dependencies:
|
|
53
67
|
- !ruby/object:Gem::Version
|
54
68
|
version: '3.0'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
70
|
+
name: rubocop
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
58
72
|
requirements:
|
59
73
|
- - "~>"
|
60
74
|
- !ruby/object:Gem::Version
|
61
|
-
version: '0.
|
75
|
+
version: '0.58'
|
62
76
|
type: :development
|
63
77
|
prerelease: false
|
64
78
|
version_requirements: !ruby/object:Gem::Requirement
|
65
79
|
requirements:
|
66
80
|
- - "~>"
|
67
81
|
- !ruby/object:Gem::Version
|
68
|
-
version: '0.
|
82
|
+
version: '0.58'
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: simplecov
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
72
86
|
requirements:
|
73
87
|
- - "~>"
|
74
88
|
- !ruby/object:Gem::Version
|
75
|
-
version:
|
89
|
+
version: 0.16.1
|
76
90
|
type: :development
|
77
91
|
prerelease: false
|
78
92
|
version_requirements: !ruby/object:Gem::Requirement
|
79
93
|
requirements:
|
80
94
|
- - "~>"
|
81
95
|
- !ruby/object:Gem::Version
|
82
|
-
version:
|
96
|
+
version: 0.16.1
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
98
|
+
name: timecop
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
86
100
|
requirements:
|
87
101
|
- - "~>"
|
88
102
|
- !ruby/object:Gem::Version
|
89
|
-
version: '0.
|
103
|
+
version: '0.9'
|
90
104
|
type: :development
|
91
105
|
prerelease: false
|
92
106
|
version_requirements: !ruby/object:Gem::Requirement
|
93
107
|
requirements:
|
94
108
|
- - "~>"
|
95
109
|
- !ruby/object:Gem::Version
|
96
|
-
version: '0.
|
97
|
-
description: A
|
98
|
-
database or persistence mechanism.
|
110
|
+
version: '0.9'
|
111
|
+
description: A straightforward, customizable Ruby job queue with zero dependencies.
|
99
112
|
email:
|
100
113
|
- robin@tenjin.ca
|
101
114
|
executables: []
|
@@ -104,6 +117,7 @@ extra_rdoc_files: []
|
|
104
117
|
files:
|
105
118
|
- ".gitignore"
|
106
119
|
- ".rspec"
|
120
|
+
- ".rubocop.yml"
|
107
121
|
- ".ruby-version"
|
108
122
|
- ".travis.yml"
|
109
123
|
- CODE_OF_CONDUCT.md
|
@@ -114,10 +128,17 @@ files:
|
|
114
128
|
- bin/console
|
115
129
|
- bin/setup
|
116
130
|
- lib/procrastinator.rb
|
117
|
-
- lib/procrastinator/
|
131
|
+
- lib/procrastinator/config.rb
|
132
|
+
- lib/procrastinator/loaders/csv_loader.rb
|
133
|
+
- lib/procrastinator/queue.rb
|
134
|
+
- lib/procrastinator/queue_manager.rb
|
118
135
|
- lib/procrastinator/queue_worker.rb
|
136
|
+
- lib/procrastinator/scheduler.rb
|
137
|
+
- lib/procrastinator/task.rb
|
138
|
+
- lib/procrastinator/task_meta_data.rb
|
119
139
|
- lib/procrastinator/task_worker.rb
|
120
140
|
- lib/procrastinator/version.rb
|
141
|
+
- lib/rake/procrastinator_task.rb
|
121
142
|
- procrastinator.gemspec
|
122
143
|
homepage: https://github.com/TenjinInc/procrastinator
|
123
144
|
licenses:
|
@@ -129,9 +150,9 @@ require_paths:
|
|
129
150
|
- lib
|
130
151
|
required_ruby_version: !ruby/object:Gem::Requirement
|
131
152
|
requirements:
|
132
|
-
- - "
|
153
|
+
- - ">="
|
133
154
|
- !ruby/object:Gem::Version
|
134
|
-
version: '2.
|
155
|
+
version: '2.3'
|
135
156
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
157
|
requirements:
|
137
158
|
- - ">="
|
@@ -139,8 +160,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
139
160
|
version: '0'
|
140
161
|
requirements: []
|
141
162
|
rubyforge_project:
|
142
|
-
rubygems_version: 2.
|
163
|
+
rubygems_version: 2.6.13
|
143
164
|
signing_key:
|
144
165
|
specification_version: 4
|
145
|
-
summary:
|
166
|
+
summary: For apps that put work off until later
|
146
167
|
test_files: []
|
@@ -1,148 +0,0 @@
|
|
1
|
-
module Procrastinator
|
2
|
-
class Environment
|
3
|
-
attr_reader :persister, :queue_definitions, :queue_workers, :processes, :test_mode
|
4
|
-
|
5
|
-
DEFAULT_LOG_DIRECTORY = 'log/'
|
6
|
-
|
7
|
-
def initialize(test_mode: false)
|
8
|
-
@test_mode = test_mode
|
9
|
-
@queue_definitions = {}
|
10
|
-
@queue_workers = []
|
11
|
-
@processes = []
|
12
|
-
@log_dir = DEFAULT_LOG_DIRECTORY
|
13
|
-
@log_level = Logger::INFO
|
14
|
-
end
|
15
|
-
|
16
|
-
def persister_factory(&block)
|
17
|
-
@persister_factory = block
|
18
|
-
|
19
|
-
build_persister
|
20
|
-
end
|
21
|
-
|
22
|
-
def define_queue(name, properties={})
|
23
|
-
raise ArgumentError.new('queue name cannot be nil') if name.nil?
|
24
|
-
|
25
|
-
@queue_definitions[name] = properties
|
26
|
-
end
|
27
|
-
|
28
|
-
def spawn_workers
|
29
|
-
if @test_mode
|
30
|
-
@queue_definitions.each do |name, props|
|
31
|
-
@queue_workers << QueueWorker.new(props.merge(name: name,
|
32
|
-
persister: @persister))
|
33
|
-
end
|
34
|
-
else
|
35
|
-
@queue_definitions.each do |name, props|
|
36
|
-
pid = fork do
|
37
|
-
build_persister
|
38
|
-
worker = QueueWorker.new(props.merge(name: name,
|
39
|
-
persister: @persister,
|
40
|
-
log_dir: @log_dir,
|
41
|
-
log_level: @log_level))
|
42
|
-
|
43
|
-
Process.setproctitle("#{@process_prefix ? "#{@process_prefix}-" : ''}#{worker.long_name}") # tODO: add an app name prefix
|
44
|
-
|
45
|
-
monitor_parent(worker)
|
46
|
-
|
47
|
-
worker.work
|
48
|
-
end
|
49
|
-
|
50
|
-
Process.detach(pid) unless pid.nil?
|
51
|
-
@processes << pid
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def act(*queue_names)
|
57
|
-
unless @test_mode
|
58
|
-
raise RuntimeError.new('Procrastinator.act called outside Test Mode. Enable test mode by setting Procrastinator.test_mode = true before running setup')
|
59
|
-
end
|
60
|
-
|
61
|
-
if queue_names.empty?
|
62
|
-
@queue_workers.each do |worker|
|
63
|
-
worker.act
|
64
|
-
end
|
65
|
-
else
|
66
|
-
queue_names.each do |name|
|
67
|
-
@queue_workers.find { |worker| worker.name == name }.act
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def delay(queue: nil, run_at: Time.now.to_i, expire_at: nil, task:)
|
73
|
-
raise ArgumentError.new('task may not be nil') if task.nil?
|
74
|
-
raise MalformedTaskError.new('the provided task does not support #run method') unless task.respond_to? :run
|
75
|
-
|
76
|
-
# We're checking these on init because it's one of those extremely rare cases where you'd want to know
|
77
|
-
# incredibly early, because of the sub-processing. It's a bit belt-and suspenders, but UX is important for
|
78
|
-
# devs, too.
|
79
|
-
[:success, :fail, :final_fail].each do |method_name|
|
80
|
-
if task.respond_to?(method_name) && task.method(method_name).arity <= 0
|
81
|
-
raise MalformedTaskError.new("the provided task must accept a parameter to its ##{method_name} method")
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
if queue.nil? && @queue_definitions.size > 1
|
86
|
-
raise ArgumentError.new("queue must be specified when more than one is registered. Defined queues are: #{queue_definitions.keys.map { |k| ':' + k.to_s }.join(', ')}")
|
87
|
-
else
|
88
|
-
queue ||= @queue_definitions.keys.first
|
89
|
-
raise ArgumentError.new(%Q{there is no "#{queue}" queue registered in this environment}) if @queue_definitions[queue].nil?
|
90
|
-
end
|
91
|
-
|
92
|
-
@persister.create_task(queue: queue,
|
93
|
-
run_at: run_at.to_i,
|
94
|
-
initial_run_at: run_at.to_i,
|
95
|
-
expire_at: expire_at.nil? ? nil : expire_at.to_i,
|
96
|
-
task: YAML.dump(task))
|
97
|
-
end
|
98
|
-
|
99
|
-
def enable_test_mode
|
100
|
-
@test_mode = true
|
101
|
-
end
|
102
|
-
|
103
|
-
def log_dir(path)
|
104
|
-
@log_dir = path
|
105
|
-
end
|
106
|
-
|
107
|
-
def log_level(lvl)
|
108
|
-
@log_level = lvl
|
109
|
-
end
|
110
|
-
|
111
|
-
def process_prefix(prefix)
|
112
|
-
@process_prefix = prefix
|
113
|
-
end
|
114
|
-
|
115
|
-
private
|
116
|
-
def monitor_parent(worker)
|
117
|
-
parent_pid = Process.ppid
|
118
|
-
|
119
|
-
heartbeat_thread = Thread.new(parent_pid) do |ppid|
|
120
|
-
loop do
|
121
|
-
begin
|
122
|
-
Process.kill(0, ppid) # kill with 0 flag checks if the process exists & has permissions
|
123
|
-
rescue Errno::ESRCH
|
124
|
-
worker.log_parent_exit(ppid: ppid, pid: Process.pid)
|
125
|
-
exit
|
126
|
-
end
|
127
|
-
|
128
|
-
sleep(5)
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
heartbeat_thread.abort_on_exception = true
|
133
|
-
end
|
134
|
-
|
135
|
-
def build_persister
|
136
|
-
@persister = @persister_factory.call
|
137
|
-
|
138
|
-
raise ArgumentError.new('persister cannot be nil') if @persister.nil?
|
139
|
-
|
140
|
-
[:read_tasks, :create_task, :update_task, :delete_task].each do |method|
|
141
|
-
raise MalformedPersisterError.new("persister must repond to ##{method}") unless @persister.respond_to? method
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
class MalformedPersisterError < StandardError
|
147
|
-
end
|
148
|
-
end
|