postjob 0.5.15 → 0.5.16

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
- SHA256:
3
- metadata.gz: 4d12e6c2c4ea007c423fd80afd3fbd7647cf4a8d506cfe9361e6ecc315d1dddb
4
- data.tar.gz: 0c821070b2d90969ee26282baa3b09358e4903f8d0b338acff884d07cd874aa7
2
+ SHA1:
3
+ metadata.gz: b740f5e877d921e9581484fd0f4e43d7e5dc6152
4
+ data.tar.gz: 736ef026988aa18e7d9e1f23c11a0e1876b62815
5
5
  SHA512:
6
- metadata.gz: 34eb6abcfbae71dcec0f58d15d03992a2d9f20bd85b3901d8942d8a12a06fb55c648fbf9ee954c8eb4a5e599461022b1a6bf5ef55f73f89f30fa534887e99b2f
7
- data.tar.gz: a092bb0cceea9e583177d017d43184188fc161c2c7e26f13be892ae3291d17c819a6f12518dfec2042893f5a25c1519befbf5b5f0c954a6753ef7b300bde6c9f
6
+ metadata.gz: 539d5be9482e50c5b911cb65f5ad689e2dd4a25dbada7adc50c8b96f9d6d06a2527edc09df285aae11185589d6918db6f63352145ba77f7bee0706a49321a48a
7
+ data.tar.gz: eab7349a9dab72cdf7231d0d88cd2b7ec83085eb12be8063645eda74a6f16dd5d471e843f7eb042008311768a15b987224520d67741ec190d42550342457ee53
@@ -28,6 +28,10 @@ module Postjob
28
28
  attr_accessor :logger
29
29
  extend self
30
30
 
31
+ def env
32
+ ENV["POSTJOB_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
33
+ end
34
+
31
35
  def host_id
32
36
  Host.host_id
33
37
  end
@@ -22,7 +22,7 @@ module Postjob::CLI
22
22
  def load_environment(path)
23
23
  return unless File.exist?(path)
24
24
 
25
- logger.info "#{path}: loading Postjob configuration"
25
+ logger.debug "#{path}: loading Postjob configuration"
26
26
  load path
27
27
  end
28
28
 
@@ -10,12 +10,12 @@ module Postjob::CLI
10
10
  SELECT
11
11
  name,
12
12
  postjob_id AS job_id,
13
- host_id,
14
- (attributes->>'uptime')::interval AS uptime,
15
- to_char((attributes->>'cpu_load_1min')::float, '99D99') AS cpu_load,
16
- attributes->>'net_in_1min' AS net_in,
17
- attributes->>'net_out_1min' AS net_out,
18
- attributes->>'net_errors_1min' AS net_errors,
13
+ events.host_id,
14
+ (events.attributes->>'uptime')::interval AS uptime,
15
+ to_char((events.attributes->>'cpu_load_1min')::float, '99D99') AS cpu_load,
16
+ events.attributes->>'net_in_1min' AS net_in,
17
+ events.attributes->>'net_out_1min' AS net_out,
18
+ events.attributes->>'net_errors_1min' AS net_errors,
19
19
  now() at time zone 'utc' - events.created_at AS age
20
20
  FROM postjob.events events
21
21
  LEFT JOIN postjob.worker_sessions worker_sessions ON events.worker_session_id=worker_sessions.id
@@ -72,7 +72,7 @@ module Postjob::CLI
72
72
  def host_shutdown
73
73
  connect_to_database!
74
74
 
75
- Simple::SQL.ask "UPDATE postjob.hosts SET status='shutdown' WHERE id=$1::uuid", ::Postjob.host_id
75
+ Postjob::Host.shutdown!(host_id: ::Postjob.host_id)
76
76
  end
77
77
 
78
78
  # Set the host to running again
@@ -1,11 +1,39 @@
1
- # rubocop:disable Metrics/PerceivedComplexity
2
-
3
1
  module Postjob::CLI
4
2
  # Run a single job
5
3
  def step(count: 1, queue: nil, host_id: nil)
6
4
  run count: count, queue: queue, host_id: host_id, heartbeat: false
7
5
  end
8
6
 
7
+ # Start the control connection
8
+ #
9
+ # The control connection runs the heartbeat, and checks for jobs on the
10
+ # control queue.
11
+ def run_control(host_id: nil)
12
+ # By running the "control" queue this code is quite efficient, it does
13
+ # not poll the database.
14
+ _run(queue: "control", host_id: host_id, heartbeat: true)
15
+
16
+ host_id ||= Postjob.host_id
17
+
18
+ # Poll until there are no more working sessions.
19
+ # [TODO] - improve by listening.
20
+ wait_for_no_running_session(host_id: host_id)
21
+
22
+ run_control_shutdown(host_id: host_id)
23
+ end
24
+
25
+ private
26
+
27
+ # This is called after the control connection did shut down
28
+ #
29
+ # This method can be reimplemented in a plugin to allow shutting down
30
+ # worker machines.
31
+ def run_control_shutdown(host_id: nil)
32
+ Postjob.logger.success "postjob control:shutdown host_id: #{host_id}"
33
+ end
34
+
35
+ public
36
+
9
37
  # Run postjobs.
10
38
  #
11
39
  # This method runs jobs as they become ready.
@@ -16,12 +44,23 @@ module Postjob::CLI
16
44
  # - --queue=queue1,queue2,queue3 run only the specified queues.
17
45
  # - --heartbeat=no don't start heartbeat process.
18
46
  #
19
- def run(count: nil, queue: nil, fast: false, host_id: nil, heartbeat: true)
20
- expect! Integer(host_id, 16) => 1..0xffffffff if host_id
47
+ def run(count: nil, queue: nil, fast: false, host_id: nil)
21
48
  count = Integer(count) if count
49
+ processed = _run(count: count, queue: queue, fast: fast, host_id: host_id, heartbeat: false)
50
+
51
+ if !count || processed < count
52
+ # The runner has been shut down externally. Wait for interrupt.
53
+ Postjob.logger.success "External shut down initiated."
54
+ STDERR.puts "External shut down initiated. Press ^C to terminate process."
55
+ STDIN.read
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def _run(count: nil, queue:, fast: false, host_id:, heartbeat:)
62
+ expect! Integer(host_id, 16) => 1..0xffffffff if host_id
22
63
 
23
- expect! heartbeat => [ "yes", "no" ] if heartbeat.is_a?(String)
24
- heartbeat = %w(yes true).include?(heartbeat) if heartbeat.is_a?(String)
25
64
  expect! heartbeat => [ true, false ]
26
65
 
27
66
  Postjob.fast_mode = (fast ? true : false)
@@ -33,12 +72,25 @@ module Postjob::CLI
33
72
  Postjob.logger.info "Using host_id: #{Postjob::Host.host_id}"
34
73
  end
35
74
 
36
- logger.success "Starting runner with pid #{$$}"
37
-
38
75
  processed = Postjob.run(count: count, queues: queue&.split(","), heartbeat: heartbeat) do |job_id|
39
76
  logger.info "Processed job w/id #{job_id}" if job_id
40
77
  end
41
78
 
42
79
  logger.info "Processed #{processed} jobs"
80
+
81
+ processed
82
+ end
83
+
84
+ def wait_for_no_running_session(host_id:)
85
+ Postjob.logger.debug "Waiting for shutdown"
86
+ loop do
87
+ count = Simple::SQL.ask "SELECT COUNT (*) FROM postjob.worker_sessions WHERE id=$1", host_id
88
+ Postjob.logger.info "#{host_id}: #{count} running sessions"
89
+
90
+ break if count == 0
91
+ sleep 0.2
92
+ end
93
+
94
+ Postjob.logger.info "#{host_id}: no more running sessions"
43
95
  end
44
96
  end
@@ -11,6 +11,7 @@ module Postjob::CLI
11
11
  SELECT
12
12
  worker_sessions.id,
13
13
  (substring(worker_sessions.host_id::varchar for 9) || '...') AS host_id,
14
+ worker_sessions.status,
14
15
  array_to_string(worker_sessions.queues, ', ') AS queues,
15
16
  worker_sessions.client_socket,
16
17
  worker_sessions.workflows,
@@ -1,5 +1,6 @@
1
1
  require_relative "./record"
2
2
  require "tempfile"
3
+ require "zlib"
3
4
 
4
5
  class Postjob::Host < Postjob::Record
5
6
  attr_reader :id
@@ -29,6 +30,10 @@ class Postjob::Host < Postjob::Record
29
30
  @host_id ||= atomic_set_and_get(storage_path) { register_host(host_id: nil) }
30
31
  end
31
32
 
33
+ def shutdown!(host_id:)
34
+ Simple::SQL.ask "UPDATE postjob.hosts SET status='shutdown' WHERE id=$1::uuid", host_id
35
+ end
36
+
32
37
  private
33
38
 
34
39
  # This method returns the path to a file which will hold the host_id. Two runners
@@ -40,28 +45,32 @@ class Postjob::Host < Postjob::Record
40
45
  # would be even better - however, our systems do not have a user-writable /var).
41
46
  def storage_path
42
47
  @storage_path ||= begin
43
- env = ENV["POSTJOB_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
44
- here = Dir.getwd
45
- storage_path = File.join Dir.tmpdir, "postjob.#{env}.#{Process.uid}.#{here.hash.abs.to_s(36)}.host_id"
46
- Simple::SQL.logger.info "Keeping host identifier in #{storage_path}"
47
- storage_path
48
+ here_hash = Zlib.crc32(Dir.getwd).to_s(36)
49
+ File.join Dir.tmpdir, "postjob.#{Postjob.env}.#{Process.uid}.#{here_hash}.host_id"
48
50
  end
49
51
  end
50
52
 
51
53
  def atomic_set_and_get(path)
52
- value = nil
53
-
54
54
  File.open(path, File::RDWR | File::CREAT, 0644) do |f|
55
55
  f.flock(File::LOCK_EX)
56
56
 
57
57
  value = f.read
58
- value = yield if value == "" || value.nil?
59
- f.rewind
60
- f.write(value)
61
- f.flush
62
- f.truncate(f.pos)
58
+
59
+ if value == "" || value.nil?
60
+ value = yield
61
+
62
+ f.rewind
63
+ f.write(value)
64
+ f.flush
65
+ f.truncate(f.pos)
66
+
67
+ Postjob.logger.info "Registering new host with host_id #{value}, in #{path}"
68
+ else
69
+ Postjob.logger.info "Reusing host_id #{value}, from #{path}"
70
+ end
71
+
72
+ value
63
73
  end
64
- value
65
74
  end
66
75
 
67
76
  def register_host(host_id:)
@@ -62,8 +62,8 @@ AS $$
62
62
  v_id := gen_random_uuid();
63
63
  END IF;
64
64
 
65
- INSERT INTO {SCHEMA_NAME}.hosts (id, attributes)
66
- VALUES (v_id, p_attrs)
65
+ INSERT INTO {SCHEMA_NAME}.hosts (id, attributes, status)
66
+ VALUES (v_id, p_attrs, 'running')
67
67
  ON CONFLICT(id) DO UPDATE SET attributes=p_attrs;
68
68
  RETURN v_id;
69
69
  END;
@@ -13,13 +13,48 @@ BEGIN
13
13
  INSERT INTO {SCHEMA_NAME}.worker_sessions (host_id, client_socket, workflows, queues)
14
14
  VALUES (p_host_id, v_client_socket, p_workflows, p_queues) RETURNING id INTO v_worker_session_id;
15
15
 
16
+ UPDATE {SCHEMA_NAME}.hosts
17
+ SET status = 'running'
18
+ WHERE id=p_host_id;
19
+
16
20
  RETURN QUERY SELECT * FROM {SCHEMA_NAME}.worker_sessions WHERE id = v_worker_session_id;
17
21
  END;
18
22
  $$ LANGUAGE plpgsql;
19
23
 
20
24
  CREATE OR REPLACE FUNCTION {SCHEMA_NAME}.worker_session_stop(p_worker_session_id UUID)
21
25
  RETURNS VOID AS $$
26
+ DECLARE
27
+ v_host_id uuid;
22
28
  BEGIN
23
- UPDATE {SCHEMA_NAME}.worker_sessions SET status='stopped' WHERE id=p_worker_session_id;
29
+ UPDATE {SCHEMA_NAME}.worker_sessions
30
+ SET status='stopped' WHERE id=p_worker_session_id;
24
31
  END;
25
32
  $$ LANGUAGE plpgsql;
33
+
34
+ -- wakeup runners after changing hosts
35
+
36
+ -- when a host changes its status to shutdown, all of its runners should
37
+ -- shutdown quickly.
38
+
39
+ CREATE OR REPLACE FUNCTION {SCHEMA_NAME}._recalculate_host_status() RETURNS TRIGGER AS $$
40
+ BEGIN
41
+ IF NOT EXISTS (
42
+ SELECT 1 FROM {SCHEMA_NAME}.worker_sessions WHERE id=NEW.host_id AND status = 'running'
43
+ ) THEN
44
+ UPDATE {SCHEMA_NAME}.hosts
45
+ SET status = 'stopped'
46
+ WHERE id=NEW.host_id;
47
+ END IF;
48
+
49
+ RETURN NEW;
50
+ END;
51
+ $$ LANGUAGE plpgsql;
52
+
53
+ BEGIN;
54
+ DROP TRIGGER IF EXISTS _recalculate_host_status ON {SCHEMA_NAME}.worker_sessions;
55
+
56
+ CREATE TRIGGER _recalculate_host_status AFTER UPDATE
57
+ ON {SCHEMA_NAME}.worker_sessions
58
+ FOR EACH ROW
59
+ EXECUTE PROCEDURE {SCHEMA_NAME}._recalculate_host_status();
60
+ COMMIT;
@@ -20,7 +20,7 @@ module Postjob::Queue::Notifications
20
20
  return if wait_time && wait_time <= 0
21
21
 
22
22
  if !wait_time && ::Postjob::Queue.should_shutdown?(worker_session_id)
23
- Postjob.logger.warn "Shutting down runner: host is set to 'shutdown'"
23
+ Postjob.logger.debug "Shutting down runner: host is set to 'shutdown'"
24
24
  return :shutdown
25
25
  end
26
26
 
@@ -50,6 +50,18 @@ class Postjob::Registry
50
50
  instance.register(workflow, options)
51
51
  end
52
52
 
53
+ def self.load(glob_pattern)
54
+ Dir.glob(glob_pattern).sort.each do |path|
55
+ before = Postjob::Registry.workflow_names
56
+ Kernel.load path
57
+ after = Postjob::Registry.workflow_names
58
+ new_workflows = after - before
59
+ next if new_workflows.empty?
60
+
61
+ Postjob.logger.debug "#{path}: registered workflow(s) #{new_workflows.join(', ')}"
62
+ end
63
+ end
64
+
53
65
  class WorkflowSpec
54
66
  class Options
55
67
  DEFAULTS = {
@@ -12,7 +12,7 @@ class Postjob::WorkerSession < Postjob::Record
12
12
  host_id = ::Postjob.host_id
13
13
  worker_session = ::Postjob::Queue.worker_session_start(workflows_with_versions, host_id: host_id, queues: queues)
14
14
 
15
- Postjob.logger.info "Starting worker_session #{worker_session.inspect}"
15
+ Postjob.logger.info "Starting worker_session #{worker_session.inspect}, on pid #{$$}"
16
16
 
17
17
  start_heartbeat_monitor(host_id) if heartbeat
18
18
  worker_session
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.15
4
+ version: 0.5.16
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-22 00:00:00.000000000 Z
11
+ date: 2018-08-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -327,7 +327,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
327
327
  version: '0'
328
328
  requirements: []
329
329
  rubyforge_project:
330
- rubygems_version: 2.7.7
330
+ rubygems_version: 2.5.1
331
331
  signing_key:
332
332
  specification_version: 4
333
333
  summary: restartable, asynchronous, and distributed processes