postjob 0.1.7 → 0.1.8

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 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