kinetic 0.0.2 → 0.0.3

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: b7a08f594cae0d5c01c00f340386df7c7788a745
4
- data.tar.gz: 280fe43699041a8a233b455887d795b6c47e42f8
3
+ metadata.gz: f720debb00d4ab62ce3b3e8d7e4f7eaf422be407
4
+ data.tar.gz: a01b254919820ca788f0aa9ee8d42c06a7a7ca06
5
5
  SHA512:
6
- metadata.gz: 65871da55313e959082177229600037b1ffdf5e94b1886bebb525e9099effe4b249fb7b581f54d45ed908fa68eac60e0ce7e44841e96f0738d7ae1fc731a8c3f
7
- data.tar.gz: e8452cb376cab92b21796269deff0ac5ab24306099675eca52eeb9d64071cced80ba649e0b6399748f77d113b13e6bdd7f7aecbcf1a4bc1cbf386faa30be73ff
6
+ metadata.gz: 6243b0b4ae9062a16aeda4bf06626088fd0cfaf80ed2de1b4a385f827ff491c063a69e168ef90cd46d35ba8f6b0ca0a1909a261c99670dd38634b730ede822d2
7
+ data.tar.gz: be6f7059f4fb70d65e6d1cab62357d9f082f783a249e3e7e40694d93de2a7cf71b182795d26d184d26588ab524d93d2aab6b0c4535723013a095cfcf4fbae731
data/README.md CHANGED
@@ -14,19 +14,53 @@ Kinetic is an AMQP worker framework designed in vein of microframeworks such as
14
14
  gem install kinetic
15
15
  ```
16
16
 
17
- ### Basic Usage
17
+ ## Basic Usage
18
18
 
19
19
  Kinetic follows a pattern similar to that of Sinatra, so most of the conventions should be recognizable.
20
20
 
21
21
 
22
22
  Example: simple_worker.rb
23
23
 
24
+ ```ruby
25
+ on 'message' do |message|
26
+ puts "Received #{message}"
27
+ end
24
28
  ```
29
+
30
+ This code will create a queue on the amqp server listening for messages on the simple_worker.rb.direct exchange with
31
+ the routing key 'message'. By default the message is expected to be in JSON format, however in the future other
32
+ serializers may be available.
33
+
34
+ ## Configuration
35
+ Kinetic configuration follows a similar DSL to that of sinatra. Configuration can be set by calling the `set` method,
36
+ and can be retrieved by referencing the `config` variable or the `get` or `get!` methods.
37
+
38
+ ### set
39
+ To set a configuration value use set:
40
+
41
+ ```ruby
25
42
  set :host, 'localhost'
26
- set :port, 5678
27
43
 
28
44
  on 'message' do |message|
29
45
  puts "Received #{message}"
30
46
  end
31
47
  ```
32
48
 
49
+ ### Defaults
50
+ [Kinetic::Configuration](http://rdoc.info/github/fugufish/kinetic/master/Kinetic/Configuration)
51
+
52
+
53
+
54
+ ## Changelog
55
+
56
+ ### 0.0.3
57
+ * Fix bug where process shut down does not kill children
58
+ * Add ability to set process name for master and workers
59
+ * Add after fork block capability allowing code to be written to run after the worker forks
60
+
61
+ ### 0.0.1
62
+ * Initial release
63
+
64
+
65
+
66
+
data/lib/kinetic/base.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # ruby dependencies
2
2
  require 'logger'
3
3
  require 'fileutils'
4
+ require 'yaml'
4
5
 
5
6
  # external dependencies
6
7
  require 'amqp'
@@ -8,6 +9,7 @@ require 'confstruct'
8
9
  require 'kgio'
9
10
  require 'active_support/core_ext/module/delegation'
10
11
  require 'active_support/core_ext/hash/reverse_merge'
12
+ require 'active_support/inflections'
11
13
  require 'awesome_print'
12
14
  require 'awesome_print/core_ext/logger'
13
15
  require 'msgpack'
@@ -24,7 +26,7 @@ module Kinetic
24
26
 
25
27
  module Delegator
26
28
 
27
- %w{on config logging set}.each { |m| delegate m.to_sym, to: :application }
29
+ %w{on config logging set config_file deserialize}.each { |m| delegate m.to_sym, to: :application }
28
30
 
29
31
  def application
30
32
  Application
@@ -38,22 +40,38 @@ module Kinetic
38
40
  class << self
39
41
  include Kinetic::DSL
40
42
 
43
+
44
+ def after_fork_procs
45
+ @after_fork_procs ||= []
46
+ end
47
+
41
48
  def exchanges
42
49
  @exchanges ||= {}
43
50
  end
44
51
 
52
+ def deserialize(message)
53
+ @serializer.deserialize(message)
54
+ end
55
+
45
56
  private
46
57
 
58
+ def set_serializer(serializer)
59
+ logger.debug "Loading serializer #{serializer}"
60
+ require "kinetic/serializers/#{serializer.downcase}"
61
+ @serializer = Object.const_get("Kinetic::Serializers::#{serializer}")
62
+ end
63
+
47
64
  def direct
48
65
  exchanges[:direct] ||= {}
49
66
  end
50
67
 
68
+ end
51
69
 
52
-
53
-
70
+ def initialize
71
+ self.class.send(:set_serializer, config.serializer)
54
72
  end
55
73
 
56
- delegate :logger, :set, :get, :get!, :exchanges, to: :class
74
+ delegate :logger, :set, :get, :get!, :exchanges, :after_fork_procs, :deserialize, :config, to: :class
57
75
 
58
76
  end
59
77
 
data/lib/kinetic/cli.rb CHANGED
@@ -9,13 +9,14 @@ module Kinetic
9
9
  end
10
10
 
11
11
  def self.parse
12
- if ARGV[0] && COMMANDS.include?(ARGV[0])
12
+ command = nil
13
+ if ARGV.empty? || (ARGV[0] && COMMANDS.include?(ARGV[0]))
13
14
  command = ARGV.shift
14
15
  else
15
16
  puts PARSER.banner
16
- exit 1
17
+ exit! 1
17
18
  end
18
- command
19
+ command || 'run'
19
20
  end
20
21
 
21
22
  end
@@ -24,7 +25,7 @@ module Kinetic
24
25
  end
25
26
 
26
27
 
27
- at_exit { Master.new(Application.new).send(CLI.parse) }
28
+ at_exit { Master.new(Application.new).send(CLI.parse) if $!.nil? }
28
29
 
29
30
  end
30
31
 
@@ -7,11 +7,15 @@ module Kinetic
7
7
  app_file: $0,
8
8
  workers: 1,
9
9
  log_file: STDOUT,
10
+ host: 'localhost',
11
+ port: 5672,
12
+ serializer: :JSON,
13
+ timeout: 1
10
14
  }
11
15
 
12
16
  def initialize
13
17
  super
14
- self.reverse_merge!(DEFAULTS)
18
+ self.deep_merge!(DEFAULTS)
15
19
  end
16
20
 
17
21
  end
data/lib/kinetic/dsl.rb CHANGED
@@ -2,6 +2,11 @@ module Kinetic
2
2
 
3
3
  module DSL
4
4
 
5
+ def after_fork(&block)
6
+ raise Kinetic::Errors::BlockMissing unless block_given?
7
+ after_fork_procs << block
8
+ end
9
+
5
10
  # Sets a configuration value
6
11
  #
7
12
  # @param [Symbol] key the configuration key to set
@@ -9,10 +14,19 @@ module Kinetic
9
14
  #
10
15
  # @return [Boolean] returns true
11
16
  def set(key, value)
17
+ logger.debug "Setting '#{key}' to '#{value}'"
12
18
  config[key.to_sym] = value
13
19
  true
14
20
  end
15
21
 
22
+ # Set configuration file.
23
+ #
24
+ # @param [String] path to configuration file
25
+ def config_file(file)
26
+ set :config_file, File.expand_path(file)
27
+ config.configure(YAML.load(File.new(get!(:config_file), 'r')))
28
+ end
29
+
16
30
  # Gets a configuration value
17
31
  #
18
32
  # @param [Symbol] key the key of the value to get
@@ -57,12 +71,12 @@ module Kinetic
57
71
  @logger ||= reopen_logger
58
72
  end
59
73
 
60
- private
61
-
62
74
  def config
63
75
  @config ||= Kinetic::Configuration.new
64
76
  end
65
77
 
78
+ protected
79
+
66
80
  def reopen_logger
67
81
  @logger = Logger.new(config.log_file)
68
82
  end
@@ -3,7 +3,8 @@ module Kinetic
3
3
 
4
4
  class KineticError < StandardError; end
5
5
 
6
- class NoSubscriberBlock < KineticError; end
6
+ class BlockMissing < KineticError; end
7
+ class NoSubscriberBlock < BlockMissing; end
7
8
  class KeyMustBeString < KineticError; end
8
9
 
9
10
  class MissingConfigurationValue < KineticError
@@ -7,10 +7,10 @@ module Kinetic
7
7
  WORKERS = {}
8
8
  SIG_QUEUE = []
9
9
 
10
- delegate :logger, :get, :get!, :set, to: :app
10
+ delegate :logger, :get, :get!, :set, :after_fork_procs, to: :app
11
11
 
12
12
 
13
- attr_reader :app
13
+ attr_reader :app, :reexecpid
14
14
 
15
15
  def initialize(app)
16
16
  @app = app
@@ -24,6 +24,7 @@ module Kinetic
24
24
  logger.info "Starting Kinetic #{Kinetic::VERSION} with PID #{Process.pid}"
25
25
  logger.debug 'Configuration:'
26
26
  logger.ap app.class.send(:config).to_hash
27
+ proc_name 'master'
27
28
  write_pidfile!
28
29
  initialize_self_pipe!
29
30
  initialize_signal_traps!
@@ -38,22 +39,6 @@ module Kinetic
38
39
  end
39
40
  end
40
41
 
41
- # Sets or gets the before fork block. This is called just before a worker is forked.
42
- #
43
- # @return [Proc] the before_block
44
- def before_fork(&block)
45
- @before_fork = block if block_given?
46
- @before_fork || proc {}
47
- end
48
-
49
- # Sets or gets the after fork block. This is called immediately after a worker is forked.
50
- #
51
- # @return [Proc] the after_fork block
52
- def after_fork(&block)
53
- @after_fork = block if block_given?
54
- @after_fork || proc {}
55
- end
56
-
57
42
  private
58
43
 
59
44
  def master_sleep(time)
@@ -67,13 +52,11 @@ module Kinetic
67
52
  when :QUIT
68
53
  break
69
54
  when :TERM, :INT
70
- logger.warn "#{get(:name)} is shutting down immediately!"
71
- exit!(1)
55
+ stop(false)
72
56
  break
73
57
  else
74
58
  master_sleep(0.5)
75
59
  end while true
76
- logger.warn "#{get(:name)} is going down!"
77
60
  end
78
61
 
79
62
  # Based on Unicorn's self-pipe
@@ -110,12 +93,13 @@ module Kinetic
110
93
  WORKERS.value?(worker_nr) and next
111
94
  #noinspection RubyArgCount
112
95
  worker = Worker.new(worker_nr, @app)
113
- logger.debug 'Calling before_fork block'
114
- before_fork.call(self, worker)
115
96
  logger.debug 'Forking worker'
116
97
  parent = Process.pid
117
98
  pid = fork do
118
99
  begin
100
+ proc_name "worker-#{worker_nr}"
101
+ logger.debug 'Calling after fork procs'
102
+ after_fork_procs.each { |p| p.call }
119
103
  #noinspection RubyArgCount
120
104
  worker.run
121
105
  rescue => e
@@ -125,6 +109,7 @@ module Kinetic
125
109
  end
126
110
  end
127
111
  logger.debug "Worker #{worker_nr} started on #{pid}"
112
+ WORKERS[pid] = worker
128
113
  end
129
114
  rescue => e
130
115
  logger.error(e)
@@ -148,7 +133,59 @@ module Kinetic
148
133
  WORKERS.clear
149
134
 
150
135
  after_fork.call(self, worker)
136
+ end
137
+
138
+ # Terminates all workers, but does not exit master process
139
+ def stop(graceful = true)
140
+ logger.warn graceful ? 'Process is shutting down gracefully' : 'Process is shutting down immediately!'
141
+ limit = Time.now + get!(:timeout)
142
+ until WORKERS.empty? || Time.now > limit
143
+ if graceful
144
+ soft_kill_each_worker(:QUIT)
145
+ else
146
+ kill_each_worker(:TERM)
147
+ end
148
+ sleep(0.1)
149
+ reap_all_workers
150
+ end
151
+ kill_each_worker(:KILL)
152
+ end
153
+
154
+ def kill_each_worker(signal)
155
+ WORKERS.keys.each { |wpid| kill_worker(signal, wpid) }
156
+ end
157
+
158
+ # delivers a signal to a worker and fails gracefully if the worker
159
+ # is no longer running.
160
+ def kill_worker(signal, wpid)
161
+ Process.kill(signal, wpid)
162
+ rescue Errno::ESRCH
163
+ worker = WORKERS.delete(wpid) and worker.close rescue nil
164
+ end
165
+
166
+ # reaps all unreaped workers
167
+ def reap_all_workers
168
+
169
+ begin
170
+ wpid, status = Process.waitpid2(-1, Process::WNOHANG)
171
+ wpid or return
172
+ if reexec_pid == wpid
173
+ logger.error "reaped #{status.inspect} exec()-ed"
174
+ self.reexec_pid = 0
175
+ self.pid = pid.chomp('.oldbin') if pid
176
+ proc_name 'master'
177
+ else
178
+ worker = WORKERS.delete(wpid) and worker.close rescue nil
179
+ m = "reaped #{status.inspect} worker=#{worker.nr rescue 'unknown'}"
180
+ status.success? ? logger.info(m) : logger.error(m)
181
+ end
182
+ rescue Errno::ECHILD
183
+ break
184
+ end while true
185
+ end
151
186
 
187
+ def proc_name(tag)
188
+ $0 = "#{get!(:name)} [#{tag}]"
152
189
  end
153
190
 
154
191
  end
@@ -0,0 +1,19 @@
1
+ require 'json'
2
+
3
+ module Kinetic
4
+ module Serializers
5
+
6
+ module JSON
7
+
8
+ def self.serialize(message)
9
+ ::JSON.generate(message)
10
+ end
11
+
12
+ def self.deserialize(message)
13
+ ::JSON.parse(message)
14
+ end
15
+
16
+ end
17
+
18
+ end
19
+ end
@@ -1,3 +1,3 @@
1
1
  module Kinetic
2
- VERSION = '0.0.2'
2
+ VERSION = '0.0.3'
3
3
  end
@@ -1,7 +1,7 @@
1
1
  module Kinetic
2
2
  class Worker
3
3
 
4
- delegate :logger, :get, :get!, :set, to: :app
4
+ delegate :logger, :get, :get!, :set, :deserialize, to: :app
5
5
 
6
6
  attr_reader :id, :app, :channel, :exchanges
7
7
 
@@ -61,7 +61,11 @@ module Kinetic
61
61
  q.bind(exchanges[name], routing_key: queue)
62
62
  logger.debug " Subscribing to messages for '#{queue}'"
63
63
  q.subscribe do |meta, payload|
64
- block.call(MessagePack.unpack(payload))
64
+ begin
65
+ block.call(deserialize(payload))
66
+ rescue Exception => e
67
+ logger.error e
68
+ end
65
69
  end
66
70
  end
67
71
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kinetic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jarod Reid
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-12-17 00:00:00.000000000 Z
11
+ date: 2014-01-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: confstruct
@@ -187,6 +187,7 @@ files:
187
187
  - lib/kinetic/errors.rb
188
188
  - lib/kinetic/master.rb
189
189
  - lib/kinetic/publisher.rb
190
+ - lib/kinetic/serializers/json.rb
190
191
  - lib/kinetic/version.rb
191
192
  - lib/kinetic/worker.rb
192
193
  - spec/base_spec.rb