postjob 0.5.11 → 0.5.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/lib/postjob/cli/cron.rb +24 -0
  3. data/lib/postjob/cli/db.rb +1 -2
  4. data/lib/postjob/cli/events.rb +2 -2
  5. data/lib/postjob/cli/heartbeat.rb +2 -2
  6. data/lib/postjob/cli/helpers.rb +28 -0
  7. data/lib/postjob/cli/hosts.rb +32 -15
  8. data/lib/postjob/cli/job.rb +2 -0
  9. data/lib/postjob/cli/ps.rb +4 -26
  10. data/lib/postjob/cli/queues.rb +66 -0
  11. data/lib/postjob/cli/run.rb +19 -6
  12. data/lib/postjob/cli/sessions.rb +5 -4
  13. data/lib/postjob/host.rb +26 -5
  14. data/lib/postjob/migrations/001_helpers.sql +19 -0
  15. data/lib/postjob/migrations/007_job_results.sql +0 -26
  16. data/lib/postjob/migrations/012_hosts.sql +48 -5
  17. data/lib/postjob/migrations/013_worker_sessions.sql +12 -1
  18. data/lib/postjob/migrations/013a_checkout_runnable.sql +47 -5
  19. data/lib/postjob/migrations/016_sessions_functions.sql +5 -3
  20. data/lib/postjob/migrations/017_zombie_check.sql +64 -18
  21. data/lib/postjob/migrations/018_heartbeat.sql +36 -3
  22. data/lib/postjob/migrations/021_cron_jobs.sql +12 -11
  23. data/lib/postjob/migrations.rb +1 -1
  24. data/lib/postjob/queue/notifications.rb +15 -7
  25. data/lib/postjob/queue.rb +21 -8
  26. data/lib/postjob/runner.rb +1 -1
  27. data/lib/postjob/worker_session.rb +9 -5
  28. data/lib/postjob.rb +62 -26
  29. data/lib/tools/heartbeat.rb +2 -1
  30. data/spec/postjob/events/job_event_spec.rb +2 -2
  31. data/spec/postjob/worker_session_spec.rb +1 -1
  32. data/spec/postjob/zombie_spec.rb +54 -0
  33. data/spec/spec_helper.rb +2 -0
  34. data/spec/support/test_helper.rb +3 -8
  35. metadata +12 -9
  36. data/spec/postjob/events/zombie_event_spec.rb +0 -61
data/lib/postjob.rb CHANGED
@@ -38,6 +38,8 @@ module Postjob
38
38
  attr_accessor :fast_mode # :nodoc:
39
39
  self.fast_mode = false
40
40
 
41
+ CRON_INTERVAL_MIN = 60
42
+
41
43
  # Enqueues a workflow.
42
44
  #
43
45
  # Options include
@@ -91,8 +93,8 @@ module Postjob
91
93
  greedy = spec.options.greedy
92
94
  end
93
95
 
94
- if cron_interval && cron_interval < 300
95
- raise "cron interval must be at least 5 minutes"
96
+ if cron_interval && cron_interval < CRON_INTERVAL_MIN
97
+ raise "cron interval must be at least #{CRON_INTERVAL_MIN} seconds"
96
98
  end
97
99
 
98
100
  # -- disable existing cron jobs -------------------------------------------
@@ -105,15 +107,18 @@ module Postjob
105
107
 
106
108
  tags = stringify_hash(tags) if tags
107
109
 
108
- job = Queue.enqueue_job current_worker_session.id, workflow, *args, queue: queue,
109
- parent_id: parent_id,
110
- max_attempts: max_attempts,
111
- timeout: timeout,
112
- tags: tags,
113
- version: version,
114
- cron_interval: cron_interval,
115
- sticky: sticky,
116
- greedy: greedy
110
+ session_id = current_session_id if current_session?
111
+ session_id ||= "00000000-0000-0000-0000-000000000000"
112
+
113
+ job = Queue.enqueue_job session_id, workflow, *args, queue: queue,
114
+ parent_id: parent_id,
115
+ max_attempts: max_attempts,
116
+ timeout: timeout,
117
+ tags: tags,
118
+ version: version,
119
+ cron_interval: cron_interval,
120
+ sticky: sticky,
121
+ greedy: greedy
117
122
 
118
123
  logger.info "Generated process #{job}"
119
124
  job.id
@@ -160,16 +165,20 @@ module Postjob
160
165
  # b) the block yielded into returns false.
161
166
  #
162
167
  # This method returns the number of processed jobs.
163
- def run(count: nil, queue: nil, &block)
168
+ def run(count: nil, queues: nil, heartbeat: true, &block)
169
+ queues ||= [ "ruby" ]
170
+
164
171
  # to run 10^12 jobs that would take 1 msecs each we would need, at least,
165
172
  # 760 years - so this default should be fine. Also, someone should update
166
173
  # the machine in the meantime :)
167
174
  count ||= 1_000_000_000_000
168
175
 
176
+ start_worker_session!(heartbeat: heartbeat, queues: queues)
177
+
169
178
  processed_jobs_count = 0
170
179
 
171
180
  loop do
172
- processed_job_id, shutdown = Postjob.step(queue: queue)
181
+ processed_job_id, shutdown = Postjob.step(queues: queues)
173
182
  processed_jobs_count += 1 if processed_job_id
174
183
 
175
184
  break if processed_jobs_count >= count
@@ -177,12 +186,31 @@ module Postjob
177
186
  break if shutdown == :shutdown
178
187
 
179
188
  next if processed_job_id
180
- Queue::Notifications.wait_for_new_job(current_worker_session.id, queue: queue)
189
+ shutdown = Queue::Notifications.wait_for_new_job(current_session_id, queues: queues)
190
+ break if shutdown == :shutdown
181
191
  end
182
192
 
183
193
  processed_jobs_count
184
194
  end
185
195
 
196
+ def start_worker_session!(heartbeat: true, queues: nil) # :nodoc:
197
+ queues ||= [ "ruby" ]
198
+
199
+ # We can't restart a new worker_session (this is currently not supported)
200
+ # and probably also unnecessary. Instead we ignore this call, as long as
201
+ # the queue setting is identical.
202
+ #
203
+ # A call to start_worker_session! is valid only during tests.
204
+ if @worker_session
205
+ raise ArgumentError, "You cannot restart a worker session" if ENV["RACK_ENV"] != "test"
206
+ raise ArgumentError, "You cannot restart a worker_session with different queues" if @worker_session.queues != queues
207
+
208
+ return @worker_session
209
+ end
210
+
211
+ @worker_session = WorkerSession.start!(Registry.runnable_workflows_with_versions, heartbeat: heartbeat, queues: queues)
212
+ end
213
+
186
214
  # Runs a single job
187
215
  #
188
216
  # This method tries to check out a runnable job. If it finds one the
@@ -195,18 +223,25 @@ module Postjob
195
223
  # self.run to terminate the run loop.
196
224
  #
197
225
  # or nil, when no job could be checked out.
198
- def step(queue: nil)
199
- job = Postjob::Queue.checkout(current_worker_session.id, queue: queue)
226
+ def step(queues: nil)
227
+ expect! queues => [Array, nil]
228
+ queues ||= [ "ruby" ]
229
+
230
+ job = Postjob::Queue.checkout(current_session_id, queues: queues)
200
231
 
201
232
  [ job.id, process_job(job) ] if job
202
233
  end
203
234
 
235
+ def current_session?
236
+ @worker_session != @nil
237
+ end
238
+
204
239
  # This method connects to the queue. This means it registers as a new worker_session,
205
240
  # if there was no worker_session yet.
206
- def current_worker_session
207
- @worker_session ||= begin
208
- WorkerSession.start!(Registry.runnable_workflows_with_versions)
209
- end
241
+ def current_session_id
242
+ raise("worker_session hasn't been started yet.") unless @worker_session
243
+
244
+ @worker_session.id
210
245
  end
211
246
 
212
247
  # Explicitely resolve a workflow.
@@ -214,7 +249,7 @@ module Postjob
214
249
  job = Queue.find_job_by_token(token)
215
250
  raise "No job with token #{token}" unless job
216
251
 
217
- Queue.set_job_result current_worker_session.id, job, result, version: nil
252
+ Queue.set_job_result current_session_id, job, result, version: nil
218
253
  end
219
254
 
220
255
  # Registers a workflow.
@@ -264,13 +299,14 @@ module Postjob
264
299
  raise "Integrity check failed: job's workflow version changed (from #{job.workflow_version} to #{version})"
265
300
  end
266
301
 
267
- worker_session_id = current_worker_session.id
302
+ session_id = current_session_id if current_session?
303
+ session_id ||= "00000000-0000-0000-0000-000000000000"
268
304
 
269
305
  case status
270
- when :failed then Queue.set_job_error worker_session_id, job, *value, status: :failed, version: version
271
- when :err then Queue.set_job_error worker_session_id, job, *value, status: :err, version: version
272
- when :pending then Queue.set_job_pending worker_session_id, job, version: version
273
- when :ok then Queue.set_job_result worker_session_id, job, value, version: version
306
+ when :failed then Queue.set_job_error session_id, job, *value, status: :failed, version: version
307
+ when :err then Queue.set_job_error session_id, job, *value, status: :err, version: version
308
+ when :pending then Queue.set_job_pending session_id, job, version: version
309
+ when :ok then Queue.set_job_result session_id, job, value, version: version
274
310
  else raise ArgumentError, "Invalid status #{status.inspect}"
275
311
  end
276
312
 
@@ -56,7 +56,8 @@ module Heartbeat # :nodoc:
56
56
  monitor = Monitor.new(cycle_length_seconds)
57
57
 
58
58
  loop do
59
- do_continue = yield monitor.measure
59
+ measurement = monitor.measure
60
+ do_continue = yield measurement
60
61
  break if do_continue == false
61
62
  monitor.sleep
62
63
  end
@@ -69,12 +69,12 @@ describe "Job Events" do
69
69
 
70
70
  it "sets the job's last_worker_session_id" do
71
71
  last_worker_session_ids = jobs.map(&:last_worker_session_id)
72
- expect(last_worker_session_ids.uniq).to eq([Postjob.current_worker_session.id])
72
+ expect(last_worker_session_ids.uniq).to eq([Postjob.current_session_id])
73
73
  end
74
74
 
75
75
  it "records the worker session id" do
76
76
  worker_session_ids = events.map(&:worker_session_id).uniq
77
- expect(worker_session_ids).to eq([Postjob.current_worker_session.id])
77
+ expect(worker_session_ids).to eq([Postjob.current_session_id])
78
78
  end
79
79
  end
80
80
  end
@@ -11,7 +11,7 @@ describe "Postjob::WorkerSession.start!" do
11
11
 
12
12
  it "creates a new session" do
13
13
  expect do
14
- session = Postjob::WorkerSession.start!(workflows_with_versions)
14
+ session = Postjob::WorkerSession.start!(workflows_with_versions, queues: [ "ruby" ])
15
15
  expect(session.id).to match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i)
16
16
  end.to change { session_count }.by(1)
17
17
  end
@@ -0,0 +1,54 @@
1
+ require "spec_helper"
2
+
3
+ module ZombieSpecWorkflow
4
+ Postjob.register_workflow self
5
+ end
6
+
7
+ describe "Zombie Detection" do
8
+ include TestHelper
9
+
10
+ before do
11
+ # announce the ZombieSpecWorkflow
12
+ # Postjob.enqueue! "ZombieSpecWorkflow"
13
+
14
+ # # Start a worker session.
15
+ session = Postjob.start_worker_session!(heartbeat: false)
16
+ @host_id = session.host_id
17
+ end
18
+
19
+ let(:host_id) { @host_id }
20
+
21
+ def send_heartbeat!
22
+ # pass in a artificial heartbeat
23
+ ::Postjob::Queue.host_heartbeat(host_id, {})
24
+ end
25
+
26
+ context "with a processing job which has the latest heartbeat from its host for > 5 minute" do
27
+ before do
28
+ @job_id = Postjob.enqueue! "ZombieSpecWorkflow"
29
+ SQL.ask "UPDATE postjob.postjobs SET status='processing'"
30
+ send_heartbeat!
31
+ SQL.ask "UPDATE postjob.events SET created_at = (now() at time zone 'utc' - interval '6 minutes')"
32
+ end
33
+
34
+ context "when sending a heartbeat" do
35
+ before do
36
+ send_heartbeat!
37
+ end
38
+
39
+ it "changes the job to zombie" do
40
+ job = SQL.ask "SELECT * FROM postjob.postjobs WHERE id= #{@job_id}", into: Postjob::Job
41
+
42
+ expect(job.error).to eq("Zombie")
43
+ expect(job.error_message).to match("host .* disappeared")
44
+ expect(job.status).to eq("err")
45
+ end
46
+
47
+ it "creates a zombie event with a zombie_count" do
48
+ event = SQL.ask "SELECT * FROM postjob.events WHERE name='zombie' ORDER BY created_at DESC LIMIT 1", into: OpenStruct
49
+ expect(event.name).to eq("zombie")
50
+ expect(event.attributes).to eq("zombie_count" => 1)
51
+ end
52
+ end
53
+ end
54
+ end
data/spec/spec_helper.rb CHANGED
@@ -29,6 +29,8 @@ if ENV["VERBOSE"]
29
29
  Simple::SQL.logger = Postjob.logger = logger
30
30
  end
31
31
 
32
+ SQL = Simple::SQL
33
+
32
34
  # Ensure there is a tmp directory for testing host id file ...
33
35
  Dir.mkdir("tmp") unless File.exist?("tmp")
34
36
 
@@ -34,15 +34,8 @@ module TestHelper
34
34
  end
35
35
  end
36
36
 
37
- def print_sql(sql, *args)
38
- require "table_print"
39
-
40
- records = Simple::SQL.all(sql, *args, into: Hash)
41
- tp records
42
- end
43
-
44
37
  def print_jobs
45
- print_sql <<~SQL
38
+ sql = <<~SQL
46
39
  SELECT
47
40
  id,
48
41
  workflow AS job,
@@ -59,5 +52,7 @@ module TestHelper
59
52
  FROM postjobs
60
53
  ORDER BY id
61
54
  SQL
55
+
56
+ Simple::SQL.print(sql)
62
57
  end
63
58
  end
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.5.11
4
+ version: 0.5.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - radiospiel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-08-17 00:00:00.000000000 Z
11
+ date: 2018-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -123,7 +123,7 @@ dependencies:
123
123
  version: '0.4'
124
124
  - - ">="
125
125
  - !ruby/object:Gem::Version
126
- version: 0.4.8
126
+ version: 0.4.15
127
127
  type: :runtime
128
128
  prerelease: false
129
129
  version_requirements: !ruby/object:Gem::Requirement
@@ -133,27 +133,27 @@ dependencies:
133
133
  version: '0.4'
134
134
  - - ">="
135
135
  - !ruby/object:Gem::Version
136
- version: 0.4.8
136
+ version: 0.4.15
137
137
  - !ruby/object:Gem::Dependency
138
138
  name: simple-cli
139
139
  requirement: !ruby/object:Gem::Requirement
140
140
  requirements:
141
141
  - - "~>"
142
142
  - !ruby/object:Gem::Version
143
- version: '0.2'
143
+ version: '0'
144
144
  - - ">="
145
145
  - !ruby/object:Gem::Version
146
- version: 0.2.4
146
+ version: 0.2.8
147
147
  type: :runtime
148
148
  prerelease: false
149
149
  version_requirements: !ruby/object:Gem::Requirement
150
150
  requirements:
151
151
  - - "~>"
152
152
  - !ruby/object:Gem::Version
153
- version: '0.2'
153
+ version: '0'
154
154
  - - ">="
155
155
  - !ruby/object:Gem::Version
156
- version: 0.2.4
156
+ version: 0.2.8
157
157
  - !ruby/object:Gem::Dependency
158
158
  name: table_print
159
159
  requirement: !ruby/object:Gem::Requirement
@@ -231,9 +231,11 @@ files:
231
231
  - lib/postjob/cli/enqueue.rb
232
232
  - lib/postjob/cli/events.rb
233
233
  - lib/postjob/cli/heartbeat.rb
234
+ - lib/postjob/cli/helpers.rb
234
235
  - lib/postjob/cli/hosts.rb
235
236
  - lib/postjob/cli/job.rb
236
237
  - lib/postjob/cli/ps.rb
238
+ - lib/postjob/cli/queues.rb
237
239
  - lib/postjob/cli/run.rb
238
240
  - lib/postjob/cli/sessions.rb
239
241
  - lib/postjob/cli/version.rb
@@ -241,6 +243,7 @@ files:
241
243
  - lib/postjob/host.rb
242
244
  - lib/postjob/job.rb
243
245
  - lib/postjob/migrations.rb
246
+ - lib/postjob/migrations/001_helpers.sql
244
247
  - lib/postjob/migrations/002_statuses.rb
245
248
  - lib/postjob/migrations/003_postjobs.sql
246
249
  - lib/postjob/migrations/003a_processing.sql
@@ -283,7 +286,6 @@ files:
283
286
  - spec/postjob/enqueue_spec.rb
284
287
  - spec/postjob/events/heartbeat_event_spec.rb
285
288
  - spec/postjob/events/job_event_spec.rb
286
- - spec/postjob/events/zombie_event_spec.rb
287
289
  - spec/postjob/full_workflow_spec.rb
288
290
  - spec/postjob/host_spec.rb
289
291
  - spec/postjob/job_control/error_status_spec.rb
@@ -297,6 +299,7 @@ files:
297
299
  - spec/postjob/step_spec.rb
298
300
  - spec/postjob/worker_session_spec.rb
299
301
  - spec/postjob/workflows/child_workflow_spec.rb
302
+ - spec/postjob/zombie_spec.rb
300
303
  - spec/spec_helper.rb
301
304
  - spec/support/configure_active_record.rb
302
305
  - spec/support/configure_database.rb
@@ -1,61 +0,0 @@
1
- require "spec_helper"
2
-
3
- module HeartbeatSpecWorkflow
4
- Postjob.register_workflow self
5
- end
6
-
7
- describe "Heartbeat Events" do
8
- include TestHelper
9
-
10
- before do
11
- Postjob.enqueue! "HeartbeatSpecWorkflow"
12
-
13
- @host_id = Simple::SQL.ask "SELECT id FROM postjob.hosts WHERE id != $1", null_host_id
14
-
15
- send_heartbeat!
16
-
17
- Simple::SQL.ask "UPDATE postjob.postjobs SET status='processing'"
18
- end
19
-
20
- let(:host_id) { @host_id || raise("host_id must be set") }
21
-
22
- def send_heartbeat!
23
- ::Postjob::Queue.host_heartbeat(host_id, {})
24
- end
25
-
26
- describe "::Postjob::Queue.host_heartbeat" do
27
- context "zombies in the database" do
28
- before do
29
- Simple::SQL.ask "UPDATE postjob.events SET created_at = (now() at time zone 'utc' - interval '2 days')"
30
- end
31
-
32
- it "does creates a zombie events" do
33
- send_heartbeat!
34
-
35
- event = Simple::SQL.ask <<~SQL, into: Hash
36
- SELECT * FROM postjob.events
37
- WHERE name='zombie' ORDER BY created_at DESC
38
- SQL
39
-
40
- expect(event).to include(
41
- attributes: { "zombie_count" => 1 },
42
- name: "zombie",
43
- postjob_id: nil
44
- )
45
- end
46
- end
47
- end
48
-
49
- context "no zombies in the database, 2" do
50
- it "does creates a zombie events" do
51
- send_heartbeat!
52
-
53
- event = Simple::SQL.ask <<~SQL, into: Hash
54
- SELECT * FROM postjob.events
55
- WHERE name='zombie' ORDER BY created_at DESC
56
- SQL
57
-
58
- expect(event).to be_nil
59
- end
60
- end
61
- end