postjob 0.5.5 → 0.5.6
Sign up to get free protection for your applications and to get access to all the features.
- 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}"
|