postjob 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 041cef7de8f64274ba69bf907217d1268031c9cc
4
- data.tar.gz: 8afbfab8f0320026c7bd89fdffdbdf88d497f7e3
3
+ metadata.gz: c750b67a0ab97557768d02f7a3cf2644646a8e1e
4
+ data.tar.gz: 4b1b93d7c1a261329d92081459c58492cf5a7923
5
5
  SHA512:
6
- metadata.gz: 3d30fc8fa4578c3031d543aef08422d6680a8f6379032ce0665b320b8bf39b071a93d332c7da212590c782ae53b6c52e7b592423b2892a83c5ff3f3ea91d23a7
7
- data.tar.gz: 4ce5f6935aa6cc807ba5d9592d2762eecd932afa449578100b5f5967a94197d35995cc81bd2efe4f3b1fcbf7ed89268d31a9d165e444c5d0850e1e57bc0a5e04
6
+ metadata.gz: 1310163dededb197a2c53deb03bfc6f0b0e7ec399ab33e870edab619b085fc86633c566b9a0152a07df3c2ef8cae580ebd9f4dccb95435c203794e266946c1d1
7
+ data.tar.gz: 3e6a73a8f11f8dd828a099ce8b8380e0872a6e23379109d85681d3107937f25d9a992745c48214d0d309b4b37acc681cab5b01620bf30a96f3e3945ae238e769
@@ -1,28 +1,48 @@
1
1
  require "postjob/cli"
2
+ require "postjob/migrations"
2
3
 
3
4
  module Postjob::CLI
4
5
  def db_migrate
5
- require "postjob/migrations"
6
-
7
6
  connect_to_database!
8
7
  Postjob::Migrations.migrate!
9
8
  end
10
9
 
11
10
  def db_unmigrate
12
- require "postjob/migrations"
11
+ if !force
12
+ confirm! <<~TXT
13
+ Really unmigrating database? This will destroy all data about postjobs!
14
+ (To prevent the need to confirm run with '--force'.)
15
+ TXT
16
+ end
13
17
 
14
18
  connect_to_database!
15
19
  Postjob::Migrations.unmigrate!
16
20
  end
17
21
 
18
- def db_remigrate
19
- require "postjob/migrations"
22
+ def db_remigrate(force: false)
23
+ if !force
24
+ confirm! <<~TXT
25
+ Really remigrating database? This will destroy all data about postjobs!
26
+ (To prevent the need to confirm run with '--force'.)
27
+ TXT
28
+ end
20
29
 
21
30
  connect_to_database!
22
31
  Postjob::Migrations.unmigrate!
23
32
  Postjob::Migrations.migrate!
24
33
  end
25
34
 
35
+ def confirm!(msg)
36
+ STDERR.puts <<~TXT
37
+ #{msg.chomp}
38
+
39
+ Press return to continue, ^C to cancel...
40
+ TXT
41
+ STDIN.gets
42
+ rescue Interrupt
43
+ raise "Cancelled by user"
44
+ end
45
+
26
46
  private
27
47
 
28
48
  USE_ACTIVE_RECORD = false
@@ -4,7 +4,7 @@ module Postjob::CLI
4
4
  # Adds a workflow to the job table, with name <workflow> and the given
5
5
  # arguments.
6
6
  #
7
- # Note that the workflow will receive the arguments as strings and must be
7
+ # Note that the workflow will receive the arguments as strings and must be
8
8
  # prepared to handle these.
9
9
  def job_enqueue(workflow, *args, queue: "ruby", tags: nil)
10
10
  connect_to_database!
@@ -22,7 +22,7 @@ module Postjob::CLI
22
22
  full_job_id || logger.error("No such job: #{job_id}")
23
23
 
24
24
  job_ids = Simple::SQL.all <<~SQL
25
- SELECT id FROM postjob.postjobs
25
+ SELECT id FROM postjob.postjobs
26
26
  WHERE (full_id LIKE '#{full_job_id}.%' OR full_id='#{full_job_id}')
27
27
  AND status IN ('failed', 'err', 'timeout')
28
28
  SQL
@@ -42,7 +42,7 @@ module Postjob::CLI
42
42
  NOTIFY postjob_notifications
43
43
  SQL
44
44
 
45
- logger.warn "The following jobs have been reset: #{job_ids.join(", ")}"
45
+ logger.warn "The following jobs have been reset: #{job_ids.join(', ')}"
46
46
  end
47
47
 
48
48
  private
@@ -57,7 +57,7 @@ module Postjob::CLI
57
57
  limit = Integer(limit)
58
58
 
59
59
  unless ids.empty?
60
- ps_full *ids, limit: limit, tags: tags
60
+ ps_full(*ids, limit: limit, tags: tags)
61
61
  return
62
62
  end
63
63
 
@@ -72,10 +72,10 @@ module Postjob::CLI
72
72
 
73
73
  # Show all information about this job
74
74
  def ps_show(id, *ids)
75
- ids = ([ id ] + ids).map { |s| Integer(s) }
75
+ ids = ([id] + ids).map { |s| Integer(s) }
76
76
 
77
77
  jobs = Simple::SQL.records <<~SQL, ids, into: Postjob::Job
78
- SELECT * FROM postjob.postjobs WHERE id = ANY($1)
78
+ SELECT * FROM postjob.postjobs WHERE id = ANY($1)
79
79
  SQL
80
80
 
81
81
  jobs.each do |job|
@@ -18,6 +18,8 @@ module Postjob::Queue::Encoder
18
18
  extend self
19
19
 
20
20
  def encode(data)
21
+ return if data.nil?
22
+
21
23
  verify_encodable!(data)
22
24
  JSON.generate(data)
23
25
  end
@@ -47,26 +47,16 @@ module Postjob::Queue::Notifications
47
47
  # returns the maximum number of seconds to wait until the
48
48
  # next runnable or timeoutable job comes up.
49
49
  def time_to_next_job
50
- queries = []
51
-
52
- escaped_workflows_and_versions = Postjob::Registry.sql_escaped_workflows_and_versions
53
- if escaped_workflows_and_versions != ""
54
- queries.push <<~SQL
55
- SELECT
56
- EXTRACT(EPOCH FROM MIN(next_run_at) - (now() at time zone 'utc'))
57
- FROM #{TABLE_NAME}
58
- WHERE status = 'ready' AND ((workflow, workflow_version) IN (#{escaped_workflows_and_versions}))
59
- SQL
60
- end
61
-
62
- queries.push <<~SQL
63
- SELECT
64
- EXTRACT(EPOCH FROM MIN(timing_out_at) - (now() at time zone 'utc'))
65
- FROM #{TABLE_NAME}
66
- WHERE status IN ('ready', 'sleep')
50
+ Simple::SQL.ask <<~SQL, Postjob::Registry.workflows_with_versions
51
+ SELECT EXTRACT(EPOCH FROM (MIN(next_event_at) - (now() at time zone 'utc'))) FROM (
52
+ SELECT MIN(timing_out_at) AS next_event_at
53
+ FROM #{TABLE_NAME}
54
+ WHERE status IN ('ready', 'sleep')
55
+ UNION
56
+ SELECT MIN(next_run_at) AS next_event_at
57
+ FROM #{TABLE_NAME}
58
+ WHERE status = 'ready' AND (workflow || workflow_version = ANY ($1))
59
+ ) sq
67
60
  SQL
68
-
69
- timestamps = Simple::SQL.all(queries.join(" UNION "))
70
- timestamps.compact.min
71
61
  end
72
62
  end
data/lib/postjob/queue.rb CHANGED
@@ -99,7 +99,7 @@ module Postjob::Queue
99
99
 
100
100
  SQL.ask <<~SQL, job.id, results
101
101
  UPDATE #{TABLE_NAME}
102
- SET results=$2, status='ok', next_run_at=NULL
102
+ SET results=$2, status='ok', next_run_at=NULL, error=NULL, error_message=NULL, error_backtrace=NULL
103
103
  WHERE id=$1
104
104
  SQL
105
105
 
@@ -136,7 +136,7 @@ module Postjob::Queue
136
136
  # subtract 1, since this check runs *after* the current run was done,
137
137
  # but before it was written to the database.
138
138
  if status == :err && remaining_attempts(job) > 1
139
- [ "ready", next_run_at_fragment ]
139
+ [ "err", next_run_at_fragment ]
140
140
  elsif status == :timeout
141
141
  [ "timeout", "NULL" ]
142
142
  else
@@ -272,24 +272,6 @@ module Postjob::Queue
272
272
  [workflow, workflow_method]
273
273
  end
274
274
 
275
- def runnable_sql_fragment
276
- escaped_workflows_and_versions = Postjob::Registry.sql_escaped_workflows_and_versions
277
- return "FALSE" if escaped_workflows_and_versions == ""
278
-
279
- <<~SQL
280
- next_run_at <= (now() at time zone 'utc')
281
- AND status = 'ready'
282
- AND ((workflow, workflow_version) IN (#{escaped_workflows_and_versions}))
283
- SQL
284
- end
285
-
286
- def timing_out_sql_fragment
287
- <<~SQL
288
- timing_out_at <= (now() at time zone 'utc')
289
- AND status IN ('ready', 'sleep')
290
- SQL
291
- end
292
-
293
275
  public
294
276
 
295
277
  def checkout_runnable
@@ -299,16 +281,16 @@ module Postjob::Queue
299
281
  timing_out_at <= (now() at time zone 'utc') AS timed_out
300
282
  FROM #{TABLE_NAME}
301
283
  WHERE
302
- (#{runnable_sql_fragment})
284
+ (next_run_at <= (now() at time zone 'utc') AND status IN ('ready', 'err') AND workflow || workflow_version = ANY ($1))
303
285
  OR
304
- (#{timing_out_sql_fragment})
286
+ (timing_out_at <= (now() at time zone 'utc') AND status IN ('ready', 'err', 'sleep'))
305
287
  ORDER BY (LEAST(next_run_at, timing_out_at))
306
288
  FOR UPDATE SKIP LOCKED
307
289
  LIMIT 1
308
290
  SQL
309
291
 
310
292
  SQL.transaction do
311
- job = SQL.record sql, into: Job
293
+ job = SQL.record sql, Postjob::Registry.workflows_with_versions, into: Job
312
294
  yield job if job
313
295
  job
314
296
  end
@@ -6,35 +6,20 @@ module Postjob::Registry
6
6
  instance.values.uniq
7
7
  end
8
8
 
9
- # [TODO] - it would be nicer if Simple::SQL would properly build a Postgres version
10
- # of the workflow names and versions.
11
- #
12
- def sql_escaped_workflows_and_versions
13
- @sql_escaped_workflows_and_versions ||= begin
14
- instance.keys.map do |name, workflow_version|
15
- escaped_name = sql_escape(name)
16
- escaped_version = sql_escape(workflow_version)
17
- "(#{escaped_name}, #{escaped_version})"
18
- end.join(", ")
19
- end
9
+ def workflows_with_versions
10
+ @workflows_with_versions ||= []
20
11
  end
21
12
 
22
- private
23
-
24
- def sql_escape(s)
25
- "'" + PG::Connection.escape_string(s) + "'"
26
- end
27
-
28
- public
29
-
30
13
  # Used for tests
31
14
  def reset! # :nodoc:
32
- @instance = @sql_escaped_workflows_and_versions = nil
15
+ @instance = @workflows_with_versions = nil
33
16
  end
34
17
 
35
18
  def register(workflow, _options = {})
36
19
  instance[[workflow.name, ""]] = workflow
37
20
  instance[[workflow.name, workflow.workflow_version]] = workflow
21
+
22
+ workflows_with_versions << workflow.name << "#{workflow.name}#{workflow.workflow_version}"
38
23
  end
39
24
 
40
25
  def lookup!(name:, version:)
data/lib/postjob.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "expectation"
2
2
  require "simple/sql"
3
3
  require "timeout"
4
+ require "time"
4
5
 
5
6
  module Postjob
6
7
  end
@@ -1,5 +1,3 @@
1
- # rubocop:disable Metrics/BlockLength
2
-
3
1
  require "spec_helper"
4
2
 
5
3
  describe "Postjob.enqueue!" do
@@ -1,3 +1,5 @@
1
+ # rubocop:disable Style/MethodMissing
2
+
1
3
  require "spec_helper"
2
4
 
3
5
  #
@@ -0,0 +1,73 @@
1
+ require "spec_helper"
2
+
3
+ # DO NOT TRY THIS IN THE REAL WORLD!
4
+ #
5
+ # This workflow uses a global value, FailureWorkflow.attempts, to count the
6
+ # number of attempts to run a workflow. This turns the workflow into a stateful
7
+ # affair WHICH BREAKS THE GENERAL CONTRACT OF POSTJOB.
8
+ module FailureWorkflow
9
+ extend self
10
+
11
+ attr_accessor :attempts
12
+
13
+ def run(number_of_failing_attempts)
14
+ self.attempts += 1
15
+ if self.attempts <= number_of_failing_attempts
16
+ raise "FailureWorkflow failed"
17
+ end
18
+
19
+ "result"
20
+ end
21
+
22
+ Postjob.register_workflow self
23
+ end
24
+
25
+ describe "error states" do
26
+ before do
27
+ FailureWorkflow.attempts = 0
28
+ end
29
+
30
+ def load_job
31
+ TestHelper.load_job(job_id)
32
+ end
33
+
34
+ let!(:job_id) { Postjob.enqueue!("FailureWorkflow", 1) }
35
+
36
+ context "when running a job that runs into an exception" do
37
+ before do
38
+ Postjob.process_all
39
+ end
40
+
41
+ it "sets the job's status to err" do
42
+ expect(load_job.status).to eq("err")
43
+ end
44
+
45
+ it "sets the job's error information" do
46
+ expect(load_job.error_message).to eq("FailureWorkflow failed")
47
+ expect(load_job.error).to eq("RuntimeError")
48
+ expect(load_job.error_backtrace).to be_a(Array)
49
+ end
50
+ end
51
+
52
+ context "when rerunning the job so that it succeeds" do
53
+ before do
54
+ while Postjob.process_all > 0
55
+ sleep 0.1
56
+ end
57
+ end
58
+
59
+ it "sets the job's status to ok" do
60
+ expect(load_job.status).to eq("ok")
61
+ end
62
+
63
+ it "sets the job's result" do
64
+ expect(load_job.result).to eq("result")
65
+ end
66
+
67
+ it "cleared out the error entries" do
68
+ expect(load_job.error_message).to be_nil
69
+ expect(load_job.error).to be_nil
70
+ expect(load_job.error_backtrace).to be_nil
71
+ end
72
+ end
73
+ end
@@ -1,5 +1,3 @@
1
- # rubocop:disable Metrics/BlockLength
2
-
3
1
  require "spec_helper"
4
2
 
5
3
  module MaxAttemptWorkflow
@@ -1,7 +1,5 @@
1
1
  require "spec_helper"
2
2
 
3
- # rubocop:disable Metrics/BlockLength
4
-
5
3
  module SearchWorkflow
6
4
  def self.run
7
5
  set_workflow_status "starting-up"
@@ -1,5 +1,3 @@
1
- # rubocop:disable Metrics/BlockLength
2
-
3
1
  require "spec_helper"
4
2
 
5
3
  module TwoLevelWorkflow
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.1.7
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - radiospiel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-03-02 00:00:00.000000000 Z
11
+ date: 2018-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -236,6 +236,7 @@ files:
236
236
  - lib/postjob/workflow.rb
237
237
  - spec/postjob/enqueue_spec.rb
238
238
  - spec/postjob/full_workflow_spec.rb
239
+ - spec/postjob/job_control/error_status_spec.rb
239
240
  - spec/postjob/job_control/manual_spec.rb
240
241
  - spec/postjob/job_control/max_attempts_spec.rb
241
242
  - spec/postjob/job_control/timeout_spec.rb