postjob 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/lib/postjob/cli/db.rb +3 -0
  3. data/lib/postjob/cli/job.rb +15 -7
  4. data/lib/postjob/cli/ps.rb +31 -4
  5. data/lib/postjob/cli/run.rb +4 -2
  6. data/lib/postjob/migrations/002_statuses.rb +18 -0
  7. data/lib/postjob/migrations/003_postjobs.sql +41 -0
  8. data/lib/postjob/migrations/003a_processing.sql +3 -0
  9. data/lib/postjob/migrations/003b_processing_columns.sql +31 -0
  10. data/lib/postjob/migrations/004_tokens.sql +9 -0
  11. data/lib/postjob/migrations/005_helpers.sql +28 -0
  12. data/lib/postjob/migrations/006_enqueue.sql +44 -0
  13. data/lib/postjob/migrations/006a_processing.sql +48 -0
  14. data/lib/postjob/migrations/007_job_results.sql +157 -0
  15. data/lib/postjob/migrations/008_checkout_runnable.sql +78 -0
  16. data/lib/postjob/migrations/008a_childjobs.sql +82 -0
  17. data/lib/postjob/migrations/009_tokens.sql +25 -0
  18. data/lib/postjob/migrations.rb +30 -77
  19. data/lib/postjob/queue/encoder.rb +1 -1
  20. data/lib/postjob/queue/notifications.rb +6 -21
  21. data/lib/postjob/queue/search.rb +4 -4
  22. data/lib/postjob/queue.rb +47 -209
  23. data/lib/postjob/runner.rb +22 -13
  24. data/lib/postjob.rb +21 -19
  25. data/spec/postjob/enqueue_spec.rb +26 -14
  26. data/spec/postjob/job_control/error_status_spec.rb +2 -2
  27. data/spec/postjob/job_control/manual_spec.rb +4 -6
  28. data/spec/postjob/job_control/max_attempts_spec.rb +3 -1
  29. data/spec/postjob/process_job_spec.rb +3 -2
  30. data/spec/postjob/queue/encoder_spec.rb +4 -0
  31. data/spec/postjob/run_spec.rb +1 -1
  32. data/spec/postjob/step_spec.rb +2 -2
  33. data/spec/postjob/{sub_workflow_spec.rb → workflows/child_workflow_spec.rb} +2 -2
  34. data/spec/spec_helper.rb +1 -1
  35. data/spec/support/configure_database.rb +1 -0
  36. data/spec/support/test_helper.rb +4 -4
  37. metadata +19 -7
@@ -41,9 +41,6 @@ module Postjob::Runner
41
41
  when :manual then workflow = "__manual__"
42
42
  when Symbol then workflow = "#{current_job.workflow}.#{workflow}"
43
43
  when Module then workflow = workflow.name
44
- when String then :nop
45
- else
46
- raise ArgumentError, "Unsupported workflow spec #{workflow.inspect}. Did you run await(fun(a, b)) instead of await(:fun, a, b)"
47
44
  end
48
45
 
49
46
  ::Postjob::Queue.find_or_create_childjob(self.current_job, workflow, args,
@@ -81,12 +78,14 @@ module Postjob::Runner
81
78
  #
82
79
  # returns a tuple [status, value], which follows the following pattern:
83
80
  #
84
- # - <tt>[ <runner-version>, :ok, value ]</tt>: job completed successfully
85
- # - <tt>[ <runner-version>, :sleep, nil ]</tt>: job has to wait on a child job
86
- # - <tt>[ <runner-version>, :err, <err> ]</tt>: job errored with a recoverable error
87
- # - <tt>[ <runner-version>, :failed, <err> ]</tt>: job failed with a non-recoverable error
81
+ # - <tt>[ <runner-version>, :ok, value, nil ]</tt>: job completed successfully
82
+ # - <tt>[ <runner-version>, :sleep, nil, nil ]</tt>: job has to wait on a child job
83
+ # - <tt>[ <runner-version>, :err, <err>, nil ]</tt>: job errored with a recoverable error
84
+ # - <tt>[ <runner-version>, :failed, <err>, <shutdown> ]</tt>: job failed
85
+ # with a non-recoverable error
88
86
  #
89
87
  # <err> is a tuple [ error-class-name, error-message, stacktrace ].
88
+ # <shutdown> is either nil or :shutdown
90
89
  #
91
90
  def process_job(job)
92
91
  expect! job => Job
@@ -94,15 +93,15 @@ module Postjob::Runner
94
93
  workflow = Postjob::Registry.lookup!(name: job.workflow, version: job.workflow_version)
95
94
 
96
95
  with_current_job(job) do
97
- status, value = invoke_workflow workflow, job
96
+ status, value, shutdown = invoke_workflow workflow, job
98
97
  log_result! job, status, value
99
- [ workflow.workflow_version, status, value ]
98
+ [ workflow.workflow_version, status, value, shutdown ]
100
99
  end
101
100
  end
102
101
 
103
102
  private
104
103
 
105
- # runs a job. Returns a [ runner, status, value ] tuple.
104
+ # runs a job. Returns a [ status, value, shutdown ] tuple.
106
105
  def invoke_workflow(workflow, job)
107
106
  value = catch(:pending) {
108
107
  expect! job.args => [Array, nil]
@@ -117,8 +116,8 @@ module Postjob::Runner
117
116
  }
118
117
 
119
118
  case value
120
- when :pending then [ :pending, nil ]
121
- else [ :ok, value ]
119
+ when :pending then [ :pending, nil, nil ]
120
+ else [ :ok, value, nil ]
122
121
  end
123
122
  rescue ArgumentError, LocalJumpError, NameError, RegexpError, ScriptError, TypeError
124
123
  Postjob.logger.error "#{$!}, from\n\t#{$!.backtrace[0, 10].join("\n\t")}"
@@ -127,9 +126,19 @@ module Postjob::Runner
127
126
  return_exception :err, $!
128
127
  end
129
128
 
129
+ def should_shutdown?(exception)
130
+ exception.is_a?(Interrupt)
131
+ end
132
+
130
133
  def return_exception(state, exception)
134
+ # get and shorten backtrace.
131
135
  error_backtrace = exception.backtrace[0, 10]
132
- [ state, [exception.class.name, exception.to_s, error_backtrace] ]
136
+
137
+ curdir = "#{Dir.getwd}/"
138
+ error_backtrace = error_backtrace.map { |path| path.start_with?(curdir) ? path[curdir.length..-1] : path }
139
+
140
+ shutdown = should_shutdown?(exception) ? :shutdown : nil
141
+ [ state, [exception.class.name, exception.to_s, error_backtrace], shutdown ]
133
142
  end
134
143
 
135
144
  def log_result!(job, status, value)
data/lib/postjob.rb CHANGED
@@ -101,14 +101,14 @@ module Postjob
101
101
  processed_jobs_count = 0
102
102
 
103
103
  loop do
104
- processed_job = Postjob.step
105
- processed_jobs_count += 1 if processed_job
104
+ processed_job_id, shutdown = Postjob.step
105
+ processed_jobs_count += 1 if processed_job_id
106
106
 
107
107
  break if processed_jobs_count >= count
108
- break if block && (yield(processed_job) == false)
109
-
110
- next if processed_job
108
+ break if block && yield(processed_job_id) == false
109
+ break if shutdown == :shutdown
111
110
 
111
+ next if processed_job_id
112
112
  Queue::Notifications.wait_for_new_job
113
113
  end
114
114
 
@@ -118,42 +118,44 @@ module Postjob
118
118
  # Runs a single job
119
119
  #
120
120
  # This method tries to check out a runnable job. If it finds one the
121
- # job is processed (via Postjob.process_job) and returned. If not,
122
- # this method just returns nil.
121
+ # job is processed (via Postjob.process_job).
122
+ #
123
+ # This method returns a tuple [ <job>, <shutdown> ], where
124
+ #
125
+ # - <job-id> is the id of the job which has been processed;
126
+ # - <shutdown> is a flag, either :shutdown or nil. :shutdown notifies
127
+ # self.run to terminate the run loop.
128
+ #
129
+ # or nil, when no job could be checked out.
123
130
  def step
124
- Queue.checkout_runnable do |job|
125
- process_job job
126
- end
131
+ job = Queue.checkout(Registry.workflows_with_versions)
132
+ [ job.id, process_job(job) ] if job
127
133
  end
128
134
 
129
135
  private
130
136
 
131
137
  # This method is called from tests. Otherwise it is supposed to be private.
138
+ #
139
+ # It returns either nil or :shutdown
132
140
  def process_job(job) # :nodoc:
133
141
  expect! job => Job
134
142
 
135
- version, status, value = if job.timed_out
136
- [job.workflow_version, :timeout, nil]
137
- else
138
- Runner.process_job(job)
139
- end
143
+ version, status, value, shutdown = Runner.process_job(job)
140
144
 
145
+ expect! version => String
141
146
  if job.workflow_version != "" && version != job.workflow_version
142
147
  raise "Integrity check failed: job's workflow version changed (from #{job.workflow_version} to #{version})"
143
148
  end
144
149
 
145
- expect! version => String
146
-
147
150
  case status
148
151
  when :failed then Queue.set_job_error job, *value, status: :failed, version: version
149
152
  when :err then Queue.set_job_error job, *value, status: :err, version: version
150
- when :timeout then Queue.set_job_error job, "Timeout", "Timeout", status: :timeout, version: version
151
153
  when :pending then Queue.set_job_pending job, version: version
152
154
  when :ok then Queue.set_job_result job, value, version: version
153
155
  else raise ArgumentError, "Invalid status #{status.inspect}"
154
156
  end
155
157
 
156
- [status, value]
158
+ shutdown
157
159
  end
158
160
 
159
161
  public
@@ -1,24 +1,36 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe "Postjob.enqueue!" do
4
+ let(:workflow_name) { "WorkflowName" }
5
+
4
6
  include TestHelper
5
7
 
6
8
  context "with valid arguments" do
7
9
  it "creates a job" do
8
- Postjob.enqueue! "FooishWorkflow"
10
+ Postjob.enqueue! workflow_name
9
11
  expect(TestHelper.jobs_count).to eq(1)
10
12
  end
11
13
 
12
14
  it "returns the created job" do
13
- id = Postjob.enqueue! "FooishWorkflow"
15
+ id = Postjob.enqueue! workflow_name
14
16
  newest_job = TestHelper.newest_job
15
17
  expect(newest_job.id).to eq(id)
16
18
  end
17
19
 
20
+ it "uses the parent id and sets a full id with two jobs" do
21
+ id1 = Postjob.enqueue! workflow_name
22
+ id2 = Postjob.enqueue! workflow_name, parent_id: id1
23
+
24
+ job1 = load_job id1
25
+ job2 = load_job id2
26
+
27
+ expect(job2.full_id).to eq("#{job1.id}.#{job2.id}")
28
+ end
29
+
18
30
  it "uses the parent id and sets a full id" do
19
- id1 = Postjob.enqueue! "FooishWorkflow"
20
- id2 = Postjob.enqueue! "FooishWorkflow", parent_id: id1
21
- id3 = Postjob.enqueue! "FooishWorkflow", parent_id: id2
31
+ id1 = Postjob.enqueue! workflow_name
32
+ id2 = Postjob.enqueue! workflow_name, parent_id: id1
33
+ id3 = Postjob.enqueue! workflow_name, parent_id: id2
22
34
 
23
35
  job1 = load_job id1
24
36
  job2 = load_job id2
@@ -29,37 +41,37 @@ describe "Postjob.enqueue!" do
29
41
  end
30
42
 
31
43
  it "sets arguments" do
32
- id1 = Postjob.enqueue! "FooishWorkflow", 1, "two", "three"
44
+ id1 = Postjob.enqueue! workflow_name, 1, "two", "three"
33
45
  job1 = load_job id1
34
46
  expect(job1.args).to eq([1, "two", "three"])
35
47
 
36
- id2 = Postjob.enqueue! "FooishWorkflow"
48
+ id2 = Postjob.enqueue! workflow_name
37
49
  job2 = load_job id2
38
50
  expect(job2.args).to eq([])
39
51
  end
40
52
 
41
53
  it "sets queue" do
42
- id1 = Postjob.enqueue! "FooishWorkflow"
54
+ id1 = Postjob.enqueue! workflow_name
43
55
  job1 = load_job id1
44
56
  expect(job1.queue).to eq("q")
45
57
 
46
- id1 = Postjob.enqueue! "FooishWorkflow", queue: "bla"
58
+ id1 = Postjob.enqueue! workflow_name, queue: "bla"
47
59
  job1 = load_job id1
48
60
  expect(job1.queue).to eq("bla")
49
61
  end
50
62
 
51
63
  it "sets max_attempts" do
52
- id1 = Postjob.enqueue! "FooishWorkflow"
64
+ id1 = Postjob.enqueue! workflow_name
53
65
  job1 = load_job id1
54
66
  expect(job1.max_attempts).to eq(5)
55
67
 
56
- id1 = Postjob.enqueue! "FooishWorkflow", max_attempts: 2
68
+ id1 = Postjob.enqueue! workflow_name, max_attempts: 2
57
69
  job1 = load_job id1
58
70
  expect(job1.max_attempts).to eq(2)
59
71
  end
60
72
 
61
73
  it "sets the status to ready" do
62
- id1 = Postjob.enqueue! "FooishWorkflow", 1, "two", "three"
74
+ id1 = Postjob.enqueue! workflow_name, 1, "two", "three"
63
75
  job1 = load_job id1
64
76
  expect(job1.status).to eq("ready")
65
77
  end
@@ -76,9 +88,9 @@ describe "Postjob.enqueue!" do
76
88
  end
77
89
 
78
90
  it "raises an error with invalid parent_id" do
79
- id1 = Postjob.enqueue! "FooishWorkflow"
91
+ id1 = Postjob.enqueue! workflow_name
80
92
 
81
- expect { Postjob.enqueue! "FooishWorkflow", parent_id: id1 - 1 }.to raise_error(PG::ForeignKeyViolation)
93
+ expect { Postjob.enqueue! workflow_name, parent_id: id1 - 1 }.to raise_error(PG::ForeignKeyViolation)
82
94
  end
83
95
  end
84
96
  end
@@ -13,7 +13,7 @@ module FailureWorkflow
13
13
  def run(number_of_failing_attempts)
14
14
  self.attempts += 1
15
15
  if self.attempts <= number_of_failing_attempts
16
- raise "FailureWorkflow failed"
16
+ raise "Failure/Workflow failed"
17
17
  end
18
18
 
19
19
  "result"
@@ -43,7 +43,7 @@ describe "error states" do
43
43
  end
44
44
 
45
45
  it "sets the job's error information" do
46
- expect(load_job.error_message).to eq("FailureWorkflow failed")
46
+ expect(load_job.error_message).to eq("Failure/Workflow failed")
47
47
  expect(load_job.error).to eq("RuntimeError")
48
48
  expect(load_job.error_backtrace).to be_a(Array)
49
49
  end
@@ -24,15 +24,13 @@ describe "manual processing" do
24
24
  TestHelper.load_job "SELECT * FROM postjobs WHERE parent_id=$1", id
25
25
  end
26
26
 
27
- def token
28
- load_token(load_child_job)
29
- end
30
-
31
- it "creates a token" do
27
+ it "creates a UUID token" do
28
+ token = load_token(load_child_job)
32
29
  expect! token => /[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[a-fA-F0-9]{4}-[A-F0-9]{12}/i
33
30
  end
34
31
 
35
- it "the token can be used to resolve the load_child_job" do
32
+ it "the token can be used to resolve the child job" do
33
+ token = load_token(load_child_job)
36
34
  Postjob.resolve token: token, result: "foobar"
37
35
  expect(load_child_job.status).to eq("ok")
38
36
  expect(load_child_job.result).to eq("foobar")
@@ -52,7 +52,9 @@ describe "max_attempts" do
52
52
  let!(:id) { Postjob.enqueue!("MaxAttemptWorkflow", 5, max_attempts: 1) }
53
53
 
54
54
  it "fails the childjob with a timeout" do
55
- sleep 0.03 while Postjob.process_all > 0
55
+ while Postjob.process_all > 0
56
+ sleep 0.1
57
+ end
56
58
 
57
59
  expect(load_child_job.failed_attempts).to eq(5)
58
60
  expect(load_child_job.status).to eq("failed")
@@ -13,8 +13,9 @@ describe "Postjob.process_job" do
13
13
 
14
14
  let!(:job) { Postjob.enqueue! "BarWorkflow" }
15
15
 
16
- it "runs the job returning the result" do
17
- expect(Postjob.process_job(newest_job)).to eq([:ok, "Foo"])
16
+ it "runs the job returning either nil or :shutdown" do
17
+ # Note: the :shutdown case is really hard to test.
18
+ expect(Postjob.process_job(newest_job)).to eq(nil)
18
19
  end
19
20
 
20
21
  it "updates the job version" do
@@ -16,6 +16,10 @@ describe "Postjob::Queue::Encoder" do
16
16
  job.result
17
17
  end
18
18
 
19
+ it "encodes nil into nil" do
20
+ expect(run_workflow(input: nil, output: nil)).to eq(nil)
21
+ end
22
+
19
23
  it "allows encoding of numbers" do
20
24
  expect(run_workflow(input: 10, output: 12)).to eq(12)
21
25
  end
@@ -17,7 +17,7 @@ module TwoLevelWorkflow
17
17
  Postjob.register_workflow self
18
18
  end
19
19
 
20
- describe "Postjob.Postjob.process_all" do
20
+ describe "Postjob.process_all" do
21
21
  include TestHelper
22
22
 
23
23
  let!(:id) { Postjob.enqueue! "TwoLevelWorkflow" }
@@ -14,8 +14,8 @@ describe "Postjob.step" do
14
14
  let!(:job_id) { Postjob.enqueue! "StepWorkflow" }
15
15
 
16
16
  it "runs the job is there is one" do
17
- processed_job = Postjob.step
18
- expect(processed_job.id).to eq(job_id)
17
+ rv = Postjob.step
18
+ expect(rv).to eq([job_id, nil])
19
19
  end
20
20
 
21
21
  it "returns nil if there is none" do
@@ -1,6 +1,6 @@
1
1
  require "spec_helper"
2
2
 
3
- module SubWorkflow
3
+ module WorkflowWithChild
4
4
  def self.my_string
5
5
  "my string"
6
6
  end
@@ -16,7 +16,7 @@ end
16
16
  describe "Sub Workflows" do
17
17
  include TestHelper
18
18
 
19
- let!(:id) { Postjob.enqueue! "SubWorkflow" }
19
+ let!(:id) { Postjob.enqueue! "WorkflowWithChild" }
20
20
 
21
21
  it "runs the job returning the result" do
22
22
  Postjob.process_all
data/spec/spec_helper.rb CHANGED
@@ -7,7 +7,7 @@ require "awesome_print"
7
7
 
8
8
  unless ENV["SKIP_SIMPLE_COV"]
9
9
  SimpleCov.start do
10
- minimum_coverage 88
10
+ minimum_coverage 90
11
11
  add_filter "/spec/"
12
12
  end
13
13
  end
@@ -7,6 +7,7 @@ end
7
7
  RSpec.configure do |config|
8
8
  config.around(:each) do |example|
9
9
  Simple::SQL.ask "DELETE FROM postjob.postjobs"
10
+ Simple::SQL.ask "DELETE FROM postjob.tokens"
10
11
  example.run
11
12
  end
12
13
  end
@@ -8,15 +8,15 @@ module TestHelper
8
8
  end
9
9
 
10
10
  def newest_job
11
- Simple::SQL.ask "SELECT * FROM postjobs ORDER BY id DESC", into: Postjob::Job
11
+ Simple::SQL.record "SELECT * FROM postjobs ORDER BY id DESC", into: Postjob::Job
12
12
  end
13
13
 
14
14
  def load_job(id_or_sql, *args)
15
15
  case id_or_sql
16
16
  when Integer
17
- Simple::SQL.ask "SELECT * FROM postjobs WHERE id=$1", id_or_sql, into: Postjob::Job
17
+ Simple::SQL.record "SELECT * FROM postjobs WHERE id=$1", id_or_sql, into: Postjob::Job
18
18
  else
19
- Simple::SQL.ask id_or_sql, *args, into: Postjob::Job
19
+ Simple::SQL.record id_or_sql, *args, into: Postjob::Job
20
20
  end
21
21
  end
22
22
 
@@ -27,7 +27,7 @@ module TestHelper
27
27
  def print_sql(sql, *args)
28
28
  require "table_print"
29
29
 
30
- records = Simple::SQL.all(sql, *args)
30
+ records = Simple::SQL.records(sql, *args)
31
31
  tp records
32
32
  end
33
33
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: postjob
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - radiospiel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-04-04 00:00:00.000000000 Z
11
+ date: 2018-03-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -120,20 +120,20 @@ dependencies:
120
120
  requirements:
121
121
  - - "~>"
122
122
  - !ruby/object:Gem::Version
123
- version: '0.3'
123
+ version: '0.2'
124
124
  - - ">="
125
125
  - !ruby/object:Gem::Version
126
- version: 0.3.7
126
+ version: 0.2.5
127
127
  type: :runtime
128
128
  prerelease: false
129
129
  version_requirements: !ruby/object:Gem::Requirement
130
130
  requirements:
131
131
  - - "~>"
132
132
  - !ruby/object:Gem::Version
133
- version: '0.3'
133
+ version: '0.2'
134
134
  - - ">="
135
135
  - !ruby/object:Gem::Version
136
- version: 0.3.7
136
+ version: 0.2.5
137
137
  - !ruby/object:Gem::Dependency
138
138
  name: simple-cli
139
139
  requirement: !ruby/object:Gem::Requirement
@@ -213,6 +213,18 @@ files:
213
213
  - lib/postjob/error.rb
214
214
  - lib/postjob/job.rb
215
215
  - lib/postjob/migrations.rb
216
+ - lib/postjob/migrations/002_statuses.rb
217
+ - lib/postjob/migrations/003_postjobs.sql
218
+ - lib/postjob/migrations/003a_processing.sql
219
+ - lib/postjob/migrations/003b_processing_columns.sql
220
+ - lib/postjob/migrations/004_tokens.sql
221
+ - lib/postjob/migrations/005_helpers.sql
222
+ - lib/postjob/migrations/006_enqueue.sql
223
+ - lib/postjob/migrations/006a_processing.sql
224
+ - lib/postjob/migrations/007_job_results.sql
225
+ - lib/postjob/migrations/008_checkout_runnable.sql
226
+ - lib/postjob/migrations/008a_childjobs.sql
227
+ - lib/postjob/migrations/009_tokens.sql
216
228
  - lib/postjob/queue.rb
217
229
  - lib/postjob/queue/encoder.rb
218
230
  - lib/postjob/queue/notifications.rb
@@ -232,7 +244,7 @@ files:
232
244
  - spec/postjob/queue/search_spec.rb
233
245
  - spec/postjob/run_spec.rb
234
246
  - spec/postjob/step_spec.rb
235
- - spec/postjob/sub_workflow_spec.rb
247
+ - spec/postjob/workflows/child_workflow_spec.rb
236
248
  - spec/spec_helper.rb
237
249
  - spec/support/configure_active_record.rb
238
250
  - spec/support/configure_database.rb