postjob 0.5.5 → 0.5.6
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/lib/postjob/cli/cron.rb +16 -0
- data/lib/postjob/cli/enqueue.rb +14 -0
- data/lib/postjob/cli/job.rb +15 -19
- data/lib/postjob/cli/ps.rb +8 -2
- data/lib/postjob/host.rb +41 -0
- data/lib/postjob/job.rb +23 -23
- data/lib/postjob/migrations/006_enqueue.sql +48 -4
- data/lib/postjob/migrations/006a_processing.sql +5 -2
- data/lib/postjob/migrations/007_job_results.sql +6 -5
- data/lib/postjob/migrations/008a_childjobs.sql +33 -38
- data/lib/postjob/migrations/010_settings.sql +1 -1
- data/lib/postjob/migrations/{008_checkout_runnable.sql → 013a_checkout_runnable.sql} +11 -7
- data/lib/postjob/migrations/017_zombie_check.sql +11 -6
- data/lib/postjob/migrations/021_cron_jobs.sql +65 -0
- data/lib/postjob/migrations/023_sticky_jobs.sql +16 -0
- data/lib/postjob/migrations.rb +3 -1
- data/lib/postjob/queue.rb +23 -4
- data/lib/postjob/record.rb +4 -10
- data/lib/postjob/registry.rb +117 -20
- data/lib/postjob/runner.rb +169 -166
- data/lib/postjob/worker_session.rb +9 -25
- data/lib/postjob/workflow.rb +26 -50
- data/lib/postjob.rb +97 -23
- data/lib/tools/heartbeat.rb +2 -2
- data/lib/tools/history.rb +1 -1
- data/spec/postjob/enqueue_spec.rb +3 -0
- data/spec/postjob/events/heartbeat_event_spec.rb +5 -67
- data/spec/postjob/events/zombie_event_spec.rb +61 -0
- data/spec/postjob/worker_session_spec.rb +1 -24
- data/spec/spec_helper.rb +1 -1
- data/spec/support/configure_active_record.rb +7 -16
- data/spec/support/configure_database.rb +11 -0
- data/spec/support/configure_simple_sql.rb +0 -16
- metadata +10 -5
- data/lib/tools/atomic_store.rb +0 -17
data/lib/postjob/queue.rb
CHANGED
@@ -2,10 +2,11 @@
|
|
2
2
|
# rubocop:disable Metrics/MethodLength
|
3
3
|
# rubocop:disable Metrics/ParameterLists
|
4
4
|
# rubocop:disable Metrics/LineLength
|
5
|
+
# rubocop:disable Metrics/ModuleLength
|
5
6
|
|
6
7
|
require "securerandom"
|
7
8
|
|
8
|
-
# The Postjob::Queue manages enqueueing and fetching jobs from a job queue.
|
9
|
+
# The Postjob::Queue module manages enqueueing and fetching jobs from a job queue.
|
9
10
|
module Postjob::Queue
|
10
11
|
extend self
|
11
12
|
|
@@ -20,7 +21,7 @@ require_relative "queue/associations"
|
|
20
21
|
require_relative "queue/settings"
|
21
22
|
|
22
23
|
module Postjob::Queue
|
23
|
-
Job = ::Postjob::Job
|
24
|
+
Job = ::Postjob::Job # :nodoc:
|
24
25
|
|
25
26
|
# enqueues a new job with the given arguments
|
26
27
|
#
|
@@ -41,7 +42,8 @@ module Postjob::Queue
|
|
41
42
|
parent_id: [Integer, nil],
|
42
43
|
tags: [Hash, nil],
|
43
44
|
timeout: [Numeric, nil],
|
44
|
-
max_attempts: [Integer, nil]
|
45
|
+
max_attempts: [Integer, nil],
|
46
|
+
sticky: [true, false, nil]
|
45
47
|
}
|
46
48
|
|
47
49
|
workflow, workflow_method = parse_workflow(workflow)
|
@@ -51,7 +53,7 @@ module Postjob::Queue
|
|
51
53
|
# a) a limitation in Simple::SQL which would not be able to unpack a
|
52
54
|
# "SELECT function()" usefully when the return value is a record;
|
53
55
|
# b) and/or my inability to write better SQL functions;
|
54
|
-
SQL.ask "SELECT * FROM #{SCHEMA_NAME}.enqueue($1::uuid, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
56
|
+
SQL.ask "SELECT * FROM #{SCHEMA_NAME}.enqueue($1::uuid, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)",
|
55
57
|
worker_session_id,
|
56
58
|
options[:queue],
|
57
59
|
workflow,
|
@@ -62,9 +64,26 @@ module Postjob::Queue
|
|
62
64
|
Encoder.encode(options[:tags]),
|
63
65
|
options[:max_attempts],
|
64
66
|
options[:timeout],
|
67
|
+
options[:cron_interval],
|
68
|
+
options[:sticky],
|
65
69
|
into: Job
|
66
70
|
end
|
67
71
|
|
72
|
+
# Disable cron'ness for workflow + args combination
|
73
|
+
def disable_cron_jobs(workflow, args)
|
74
|
+
sql = <<~SQL
|
75
|
+
UPDATE postjob.postjobs
|
76
|
+
SET cron_interval = NULL
|
77
|
+
WHERE cron_interval IS NOT NULL
|
78
|
+
AND workflow=$1
|
79
|
+
AND args=$2
|
80
|
+
AND parent_id IS NULL
|
81
|
+
AND status NOT IN ('ok', 'failed', 'timeout')
|
82
|
+
SQL
|
83
|
+
|
84
|
+
Simple::SQL.ask sql, workflow, Encoder.encode(args)
|
85
|
+
end
|
86
|
+
|
68
87
|
def set_job_result(worker_session_id, job, value, version:)
|
69
88
|
value = Encoder.encode([value]) unless value.nil?
|
70
89
|
SQL.ask "SELECT #{SCHEMA_NAME}.set_job_result($1::uuid, $2, $3, $4)", worker_session_id, job.id, value, version
|
data/lib/postjob/record.rb
CHANGED
@@ -1,17 +1,11 @@
|
|
1
|
-
# rubocop:disable Style/EvalWithLocation
|
2
|
-
# rubocop:disable Security/Eval
|
3
|
-
|
4
1
|
#
|
5
2
|
# A job class in-memory representation.
|
6
3
|
#
|
7
|
-
class Postjob::Record < Hash
|
4
|
+
class Postjob::Record < Hash # :nodoc:
|
8
5
|
def initialize(hsh)
|
9
6
|
replace hsh.dup
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
eval <<~RUBY
|
14
|
-
define_method(:#{sym}) { self[:#{sym}] }
|
15
|
-
RUBY
|
7
|
+
each do |key, value|
|
8
|
+
instance_variable_set "@#{key}", value
|
9
|
+
end
|
16
10
|
end
|
17
11
|
end
|
data/lib/postjob/registry.rb
CHANGED
@@ -1,37 +1,134 @@
|
|
1
1
|
# The registry holds a list of all available workflows
|
2
|
-
|
3
|
-
|
2
|
+
class Postjob::Registry
|
3
|
+
def self.instance
|
4
|
+
@instance ||= new
|
5
|
+
end
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
+
# Used for tests
|
8
|
+
def self.reset! # :nodoc:
|
9
|
+
@instance = nil
|
7
10
|
end
|
8
11
|
|
9
|
-
def
|
10
|
-
|
12
|
+
def self.workflows
|
13
|
+
instance.workflows
|
11
14
|
end
|
12
15
|
|
13
|
-
|
14
|
-
|
15
|
-
@instance = @workflows_with_versions = nil
|
16
|
+
def self.workflow_names
|
17
|
+
instance.workflows.keys.map(&:first).uniq
|
16
18
|
end
|
17
19
|
|
18
|
-
def
|
19
|
-
|
20
|
-
|
20
|
+
def self.runnable_workflows_with_versions
|
21
|
+
workflows
|
22
|
+
.select { |_, spec| spec.runnable? }
|
23
|
+
.inject([]) do |ary, (name_and_version, _spec)|
|
24
|
+
name, version = *name_and_version
|
25
|
+
ary << name << "#{name}#{version}"
|
26
|
+
end
|
27
|
+
end
|
21
28
|
|
22
|
-
|
29
|
+
def self.workflows_with_versions
|
30
|
+
instance.workflows_with_versions
|
23
31
|
end
|
24
32
|
|
25
|
-
def
|
26
|
-
|
27
|
-
|
33
|
+
def self.register(workflow, options = {})
|
34
|
+
instance.register(workflow, options)
|
35
|
+
end
|
36
|
+
|
37
|
+
class WorkflowSpec
|
38
|
+
class Options
|
39
|
+
DEFAULTS = {
|
40
|
+
version: "0.0",
|
41
|
+
max_attempts: 5,
|
42
|
+
timeout: 15 * 60,
|
43
|
+
sticky: false,
|
44
|
+
cron_interval: nil,
|
45
|
+
queue: "q"
|
46
|
+
}
|
47
|
+
|
48
|
+
attr_reader :version
|
49
|
+
attr_reader :max_attempts
|
50
|
+
attr_reader :timeout
|
51
|
+
attr_reader :sticky
|
52
|
+
attr_reader :cron_interval
|
53
|
+
attr_reader :queue
|
54
|
+
|
55
|
+
def initialize(options)
|
56
|
+
expect! options => {
|
57
|
+
version: [ nil, /^\d+(\.\d+)*/ ],
|
58
|
+
max_attempts: [ nil, Integer ],
|
59
|
+
timeout: [ nil, Integer ],
|
60
|
+
sticky: [ nil, true, false ],
|
61
|
+
cron_interval: [ nil, Integer ],
|
62
|
+
queue: [ nil, String ]
|
63
|
+
}
|
64
|
+
|
65
|
+
options = DEFAULTS.merge(options)
|
66
|
+
|
67
|
+
@version = options[:version]
|
68
|
+
@max_attempts = options[:max_attempts]
|
69
|
+
@timeout = options[:timeout]
|
70
|
+
@sticky = options[:sticky]
|
71
|
+
@cron_interval = options[:cron_interval]
|
72
|
+
@queue = options[:queue]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
attr_reader :workflow
|
77
|
+
attr_reader :options
|
78
|
+
|
79
|
+
def runnable?
|
80
|
+
workflow.is_a?(Module)
|
81
|
+
end
|
28
82
|
|
29
|
-
|
83
|
+
def name
|
84
|
+
case workflow
|
85
|
+
when Module then workflow.name
|
86
|
+
when String then workflow
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def initialize(workflow, options)
|
91
|
+
expect! workflow => [ Module, String ]
|
92
|
+
@workflow = workflow
|
93
|
+
@options = Options.new(options)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# looks up a specific version of a specific workflow. Returns a WorkflowSpec
|
98
|
+
# object.
|
99
|
+
def self.lookup!(name:, version:)
|
100
|
+
instance.lookup! name: name, version: version
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.[](name)
|
104
|
+
lookup! name: name, version: ""
|
105
|
+
rescue KeyError
|
106
|
+
nil
|
107
|
+
end
|
108
|
+
|
109
|
+
attr_reader :workflows
|
110
|
+
attr_reader :workflows_with_versions
|
111
|
+
|
112
|
+
def initialize
|
113
|
+
@workflows = {}
|
114
|
+
@workflows_with_versions = []
|
30
115
|
end
|
31
116
|
|
32
|
-
|
117
|
+
def register(workflow, options)
|
118
|
+
spec = WorkflowSpec.new(workflow, options)
|
119
|
+
|
120
|
+
@workflows_with_versions << spec.name << "#{spec.name}#{spec.options.version}"
|
121
|
+
|
122
|
+
@workflows[[spec.name, ""]] = spec
|
123
|
+
@workflows[[spec.name, spec.options.version]] = spec
|
124
|
+
end
|
125
|
+
|
126
|
+
# looks up a specific version of a specific workflow. Returns the workflow
|
127
|
+
# module itself
|
128
|
+
def lookup!(name:, version:)
|
129
|
+
expect! name => String
|
130
|
+
expect! version => String
|
33
131
|
|
34
|
-
|
35
|
-
@instance ||= {}
|
132
|
+
@workflows.fetch([name, version])
|
36
133
|
end
|
37
134
|
end
|
data/lib/postjob/runner.rb
CHANGED
@@ -3,194 +3,197 @@
|
|
3
3
|
# rubocop:disable Lint/RescueException
|
4
4
|
# rubocop:disable Metrics/MethodLength
|
5
5
|
|
6
|
+
# Base implementations for Runners
|
7
|
+
#
|
8
|
+
# This module contains methods for runners.
|
6
9
|
module Postjob::Runner
|
7
|
-
|
10
|
+
class << self
|
11
|
+
extend Forwardable
|
12
|
+
delegate [:logger] => Postjob
|
13
|
+
|
14
|
+
Job = Postjob::Job
|
15
|
+
|
16
|
+
# returns the job that is currently running.
|
17
|
+
#
|
18
|
+
# This value is set by +process_job+ (via +with_current_job+), and
|
19
|
+
# currently only used from <tt>Postjob::Runner.async</tt>
|
20
|
+
def current_job
|
21
|
+
Thread.current[:current_job]
|
22
|
+
end
|
8
23
|
|
9
|
-
|
10
|
-
delegate [:logger] => Postjob
|
24
|
+
private
|
11
25
|
|
12
|
-
|
26
|
+
def with_current_job(job)
|
27
|
+
expect! current_job => nil
|
28
|
+
Thread.current[:current_job] = job
|
29
|
+
yield
|
30
|
+
ensure
|
31
|
+
Thread.current[:current_job] = nil
|
32
|
+
end
|
13
33
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
34
|
+
public
|
35
|
+
|
36
|
+
# returns a subjob within the current job, for a +runner+
|
37
|
+
# description and +args+.
|
38
|
+
def async(workflow, *args, timeout: nil, max_attempts:)
|
39
|
+
worker_session_id = Postjob.current_worker_session.id
|
40
|
+
|
41
|
+
# if the workflow is a symbol, then we change it into "__manual__"
|
42
|
+
# - there should never be a workflow with that name - or into
|
43
|
+
# "CurrentWorkshop.#{workflow}", denoting the \a workflow method of the
|
44
|
+
# current workflow.
|
45
|
+
case workflow
|
46
|
+
when :manual then workflow = "__manual__"
|
47
|
+
when Symbol then workflow = "#{current_job.workflow}.#{workflow}"
|
48
|
+
when Module then workflow = workflow.name
|
49
|
+
when String then :nop
|
50
|
+
else
|
51
|
+
raise ArgumentError, "Unsupported workflow spec #{workflow.inspect}. Did you run await(fun(a, b)) instead of await(:fun, a, b)"
|
52
|
+
end
|
21
53
|
|
22
|
-
|
54
|
+
::Postjob::Queue.find_or_create_childjob(worker_session_id, self.current_job, workflow, args,
|
55
|
+
timeout: timeout,
|
56
|
+
max_attempts: max_attempts)
|
57
|
+
end
|
23
58
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
59
|
+
# tries to resolve a job.
|
60
|
+
def await(job, *args, timeout: nil, max_attempts: nil)
|
61
|
+
case job
|
62
|
+
when :all
|
63
|
+
unresolved_childjobs = Postjob::Queue.unresolved_childjobs(current_job)
|
64
|
+
if unresolved_childjobs > 0
|
65
|
+
Postjob.logger.debug "await :all: Found #{unresolved_childjobs} unresolved childjobs"
|
66
|
+
throw :pending, :pending
|
67
|
+
else
|
68
|
+
childjobs = Postjob::Queue.childjobs(current_job)
|
69
|
+
childjobs.each(&:resolve).count
|
70
|
+
end
|
71
|
+
when Job
|
72
|
+
expect! args == []
|
73
|
+
expect! timeout => nil, max_attempts => nil
|
74
|
+
r = job.resolve
|
75
|
+
throw :pending, :pending if r == :pending
|
76
|
+
r
|
77
|
+
else
|
78
|
+
job = async(job, *args, timeout: timeout, max_attempts: max_attempts)
|
79
|
+
await(job)
|
80
|
+
end
|
81
|
+
end
|
31
82
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
#
|
40
|
-
# -
|
41
|
-
#
|
42
|
-
#
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
83
|
+
STATUSES = [ :sleep, :ok, :err, :failed ]
|
84
|
+
|
85
|
+
#
|
86
|
+
# runs a specific job
|
87
|
+
#
|
88
|
+
# returns a tuple [status, value], which follows the following pattern:
|
89
|
+
#
|
90
|
+
# - <tt>[ <runner-version>, :ok, value, nil ]</tt>: job completed successfully
|
91
|
+
# - <tt>[ <runner-version>, :sleep, nil, nil ]</tt>: job has to wait on a child job
|
92
|
+
# - <tt>[ <runner-version>, :err, <err>, nil ]</tt>: job errored with a recoverable error
|
93
|
+
# - <tt>[ <runner-version>, :failed, <err>, <shutdown> ]</tt>: job failed with a non-recoverable error
|
94
|
+
#
|
95
|
+
# <err> is a tuple [ error-class-name, error-message, stacktrace ].
|
96
|
+
# <shutdown> is either nil or :shutdown
|
97
|
+
#
|
98
|
+
def process_job(job)
|
99
|
+
expect! job => Job
|
100
|
+
|
101
|
+
spec = Postjob::Registry.lookup!(name: job.workflow, version: job.workflow_version)
|
102
|
+
expect! spec.runnable?
|
103
|
+
|
104
|
+
with_current_job(job) do
|
105
|
+
status, value, shutdown = invoke_workflow spec.workflow, job
|
106
|
+
log_result! job, status, value
|
107
|
+
# If the status is ok the job finished processing. In that case
|
108
|
+
# we'll wait for all child jobs to finish.
|
109
|
+
# if status == :ok
|
110
|
+
# await :all
|
111
|
+
# end
|
112
|
+
[ spec.options.version, status, value, shutdown ]
|
113
|
+
end
|
50
114
|
end
|
51
115
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
116
|
+
private
|
117
|
+
|
118
|
+
# runs a job. Returns a [ status, value, shutdown ] tuple.
|
119
|
+
#
|
120
|
+
# The shutdown value is used by a worker in run mode (i.e. process
|
121
|
+
# indefinetively) to determine whether or not it should cancel
|
122
|
+
# processing. It is usually nil; but if the worker received a
|
123
|
+
# SIGINT it will be :shutdown instead.
|
124
|
+
#
|
125
|
+
# We are catching SIGINT to allow the job status to be updated.
|
126
|
+
def invoke_workflow(workflow, job)
|
127
|
+
value = catch(:pending) {
|
128
|
+
expect! job.args => [Array, nil]
|
129
|
+
|
130
|
+
workflow_method = job.workflow_method
|
131
|
+
args = job.args
|
132
|
+
|
133
|
+
insp_args = args.map(&:inspect).join(", ")
|
134
|
+
logger.info "Running Postjob##{job.id}: #{job.workflow}.#{workflow_method}(#{insp_args})"
|
56
135
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
if unresolved_childjobs > 0
|
63
|
-
Postjob.logger.debug "await :all: Found #{unresolved_childjobs} unresolved childjobs"
|
64
|
-
throw :pending, :pending
|
136
|
+
workflow.public_send workflow_method, *args
|
137
|
+
}
|
138
|
+
|
139
|
+
if value == :pending
|
140
|
+
[ :pending, nil, nil ]
|
65
141
|
else
|
66
|
-
|
67
|
-
|
142
|
+
# Check that we can encode the value. If we can't the job returned something invalid
|
143
|
+
# i.e. something we cannot encode as JSON. This will raise a Postjob::Queue::Encoder::Error.
|
144
|
+
#
|
145
|
+
# Usually this points to a non-UTF8 string.
|
146
|
+
# This is a fix for https://github.com/mediapeers/postjob/issues/35
|
147
|
+
Postjob::Queue::Encoder.check_encodable!([value])
|
148
|
+
[ :ok, value, nil ]
|
68
149
|
end
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
job = async(job, *args, timeout: timeout, max_attempts: max_attempts)
|
77
|
-
await(job)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
STATUSES = [ :sleep, :ok, :err, :failed ]
|
82
|
-
|
83
|
-
#
|
84
|
-
# runs a specific job
|
85
|
-
#
|
86
|
-
# returns a tuple [status, value], which follows the following pattern:
|
87
|
-
#
|
88
|
-
# - <tt>[ <runner-version>, :ok, value, nil ]</tt>: job completed successfully
|
89
|
-
# - <tt>[ <runner-version>, :sleep, nil, nil ]</tt>: job has to wait on a child job
|
90
|
-
# - <tt>[ <runner-version>, :err, <err>, nil ]</tt>: job errored with a recoverable error
|
91
|
-
# - <tt>[ <runner-version>, :failed, <err>, <shutdown> ]</tt>: job failed
|
92
|
-
# with a non-recoverable error
|
93
|
-
#
|
94
|
-
# <err> is a tuple [ error-class-name, error-message, stacktrace ].
|
95
|
-
# <shutdown> is either nil or :shutdown
|
96
|
-
#
|
97
|
-
def process_job(job)
|
98
|
-
expect! job => Job
|
99
|
-
|
100
|
-
workflow = Postjob::Registry.lookup!(name: job.workflow, version: job.workflow_version)
|
101
|
-
|
102
|
-
with_current_job(job) do
|
103
|
-
status, value, shutdown = invoke_workflow workflow, job
|
104
|
-
log_result! job, status, value
|
105
|
-
# If the status is ok the job finished processing. In that case
|
106
|
-
# we'll wait for all child jobs to finish.
|
107
|
-
# if status == :ok
|
108
|
-
# await :all
|
109
|
-
# end
|
110
|
-
[ workflow.workflow_version, status, value, shutdown ]
|
150
|
+
rescue ArgumentError, LocalJumpError, NameError, RegexpError, ScriptError, TypeError
|
151
|
+
Postjob.logger.error "#{$!}, from\n\t#{$!.backtrace[0, 10].join("\n\t")}"
|
152
|
+
return_exception :failed, $!
|
153
|
+
rescue Postjob::Error::Nonrecoverable
|
154
|
+
return_exception :failed, $!
|
155
|
+
rescue Exception
|
156
|
+
return_exception :err, $!
|
111
157
|
end
|
112
|
-
end
|
113
158
|
|
114
|
-
|
115
|
-
|
116
|
-
# runs a job. Returns a [ status, value, shutdown ] tuple.
|
117
|
-
#
|
118
|
-
# The shutdown value is used by a worker in run mode (i.e. process
|
119
|
-
# indefinetively) to determine whether or not it should cancel
|
120
|
-
# processing. It is usually nil; but if the worker received a
|
121
|
-
# SIGINT it will be :shutdown instead.
|
122
|
-
#
|
123
|
-
# We are catching SIGINT to allow the job status to be updated.
|
124
|
-
def invoke_workflow(workflow, job)
|
125
|
-
value = catch(:pending) {
|
126
|
-
expect! job.args => [Array, nil]
|
127
|
-
|
128
|
-
workflow_method = job.workflow_method
|
129
|
-
args = job.args
|
130
|
-
|
131
|
-
insp_args = args.map(&:inspect).join(", ")
|
132
|
-
logger.info "Running Postjob##{job.id}: #{job.workflow}.#{workflow_method}(#{insp_args})"
|
133
|
-
|
134
|
-
workflow.public_send workflow_method, *args
|
135
|
-
}
|
136
|
-
|
137
|
-
if value == :pending
|
138
|
-
[ :pending, nil, nil ]
|
139
|
-
else
|
140
|
-
# Check that we can encode the value. If we can't the job returned something invalid
|
141
|
-
# i.e. something we cannot encode as JSON. This will raise a Postjob::Queue::Encoder::Error.
|
142
|
-
#
|
143
|
-
# Usually this points to a non-UTF8 string.
|
144
|
-
# This is a fix for https://github.com/mediapeers/postjob/issues/35
|
145
|
-
Postjob::Queue::Encoder.check_encodable!([value])
|
146
|
-
[ :ok, value, nil ]
|
159
|
+
def should_shutdown?(exception)
|
160
|
+
exception.is_a?(Interrupt)
|
147
161
|
end
|
148
|
-
rescue ArgumentError, LocalJumpError, NameError, RegexpError, ScriptError, TypeError
|
149
|
-
Postjob.logger.error "#{$!}, from\n\t#{$!.backtrace[0, 10].join("\n\t")}"
|
150
|
-
return_exception :failed, $!
|
151
|
-
rescue Postjob::Error::Nonrecoverable
|
152
|
-
return_exception :failed, $!
|
153
|
-
rescue Exception
|
154
|
-
return_exception :err, $!
|
155
|
-
end
|
156
162
|
|
157
|
-
|
158
|
-
|
159
|
-
|
163
|
+
def return_exception(state, exception)
|
164
|
+
# get and shorten backtrace.
|
165
|
+
error_backtrace = exception.backtrace[0, 10]
|
160
166
|
|
161
|
-
|
162
|
-
|
163
|
-
error_backtrace = exception.backtrace[0, 10]
|
167
|
+
curdir = "#{Dir.getwd}/"
|
168
|
+
error_backtrace = error_backtrace.map { |path| path.start_with?(curdir) ? path[curdir.length..-1] : path }
|
164
169
|
|
165
|
-
|
166
|
-
|
170
|
+
shutdown = should_shutdown?(exception) ? :shutdown : nil
|
171
|
+
[ state, [exception.class.name, exception.message, error_backtrace], shutdown ]
|
172
|
+
end
|
167
173
|
|
168
|
-
|
169
|
-
|
170
|
-
|
174
|
+
def log_result!(job, status, value)
|
175
|
+
case status
|
176
|
+
when :err
|
177
|
+
severity = job.parent_id ? :warn : :error
|
178
|
+
logger.send severity, error_message(job, status, value)
|
179
|
+
when :failed
|
180
|
+
logger.error error_message(job, status, value)
|
181
|
+
when :ok
|
182
|
+
runtime = Time.now.utc - job.created_at
|
183
|
+
runtime = "%.03f secs" % runtime
|
184
|
+
severity = job.parent_id ? :info : :warn
|
185
|
+
msg = "#{job} successful w/result #{value.inspect}: #{runtime}"
|
186
|
+
logger.send severity, msg
|
187
|
+
end
|
188
|
+
end
|
171
189
|
|
172
|
-
|
173
|
-
case status
|
174
|
-
when :err
|
175
|
-
severity = job.parent_id ? :warn : :error
|
176
|
-
logger.send severity, error_message(job, status, value)
|
177
|
-
when :failed
|
178
|
-
logger.error error_message(job, status, value)
|
179
|
-
when :ok
|
190
|
+
def error_message(job, status, value)
|
180
191
|
runtime = Time.now.utc - job.created_at
|
181
192
|
runtime = "%.03f secs" % runtime
|
182
|
-
|
183
|
-
msg = "#{job} successful w/result #{value.inspect}: #{runtime}"
|
184
|
-
logger.send severity, msg
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
def error_message(job, status, value)
|
189
|
-
runtime = Time.now.utc - job.created_at
|
190
|
-
runtime = "%.03f secs" % runtime
|
191
|
-
error_class, err_message, _error_backtrace = value
|
193
|
+
error_class, err_message, _error_backtrace = value
|
192
194
|
|
193
|
-
|
194
|
-
|
195
|
+
"#{job} #{status} #{error_class} #{err_message.inspect}: #{runtime}"
|
196
|
+
# + "\n backtrace information:\n #{error_backtrace.join("\n ")}"
|
197
|
+
end
|
195
198
|
end
|
196
199
|
end
|
@@ -3,34 +3,18 @@
|
|
3
3
|
require_relative "./record"
|
4
4
|
|
5
5
|
require "tools/heartbeat"
|
6
|
-
require "tools/atomic_store"
|
7
|
-
|
8
|
-
class Postjob::Host < Postjob::Record
|
9
|
-
def self.register(attributes = {})
|
10
|
-
Postjob.logger.debug "registering host w/#{attributes.inspect}"
|
11
|
-
::Postjob::Queue.host_register(attributes)
|
12
|
-
end
|
13
|
-
end
|
14
6
|
|
15
7
|
# A worker worker_session
|
16
8
|
class Postjob::WorkerSession < Postjob::Record
|
17
|
-
HOST_ID_PATH = ".postjob.host_id"
|
18
|
-
|
19
9
|
class << self
|
20
10
|
# Starts a worker session.
|
21
11
|
def start!(workflows_with_versions)
|
22
|
-
|
23
|
-
|
24
|
-
AtomicStore.with(HOST_ID_PATH) do |host_id|
|
25
|
-
host_id ||= ::Postjob::Host.register
|
26
|
-
Postjob.logger.debug "Starting worker_session w/host_id #{host_id.inspect}"
|
27
|
-
worker_session = ::Postjob::Queue.start_worker_session(workflows_with_versions, host_id: host_id)
|
28
|
-
host_id
|
29
|
-
end
|
12
|
+
host_id = ::Postjob.host_id
|
13
|
+
worker_session = ::Postjob::Queue.start_worker_session(workflows_with_versions, host_id: host_id)
|
30
14
|
|
31
15
|
Postjob.logger.info "Starting worker_session #{worker_session.inspect}"
|
32
16
|
|
33
|
-
start_heartbeat_monitor(
|
17
|
+
start_heartbeat_monitor(host_id)
|
34
18
|
worker_session
|
35
19
|
end
|
36
20
|
|
@@ -58,12 +42,12 @@ class Postjob::WorkerSession < Postjob::Record
|
|
58
42
|
end
|
59
43
|
end
|
60
44
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
45
|
+
attr_reader :id
|
46
|
+
attr_reader :host_id
|
47
|
+
attr_reader :client_socket
|
48
|
+
attr_reader :workflows
|
49
|
+
attr_reader :attributes
|
50
|
+
attr_reader :created_at
|
67
51
|
|
68
52
|
def to_s
|
69
53
|
"Session##{id}"
|