postjob 0.2.2 → 0.3.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.
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