daemonizer 0.1.6 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -43,7 +43,6 @@ Usage
43
43
 
44
44
  **Demfile example:**
45
45
 
46
- engine :fork
47
46
  workers 2
48
47
 
49
48
  pool :daemonizer do
@@ -51,11 +50,11 @@ Usage
51
50
  poll_period 5
52
51
  log_file "log/daemonizer.log" #relative to Demfile
53
52
 
54
- before_init do |logger, block|
53
+ prepare do |logger, block|
55
54
  block.call
56
55
  end
57
56
 
58
- after_init do |logger, worker_id, workers_count|
57
+ start do |logger, worker_id, workers_count|
59
58
  logger.info "Started #{worker_id} from #{workers_count}"
60
59
 
61
60
  exit = false
@@ -97,6 +96,11 @@ Usage
97
96
 
98
97
  #lambda-option (transparent for daemonizer, fully processed by handler)
99
98
  set_option :on_error, lambda { |object| object.logger.fatal "epic fail"}
99
+
100
+ #executes after worker forked but before start block invoked
101
+ after_fork do |logger, worker_id, workers_count|
102
+ #reconnect to db, etc.
103
+ end
100
104
  end
101
105
 
102
106
 
@@ -126,7 +130,7 @@ Who are the authors
126
130
 
127
131
  This gem has been created in qik.com for our internal use and then
128
132
  the sources were opened for other people to use. All the code in this package
129
- has been developed by Gleb Pomykalov, and is based on
130
- [http://github.com/kovyrin/loops](loops) code written by
131
- Alexey Kovyrin. The gem is released under the MIT license. For more details,
132
- see the LICENSE file.
133
+ has been developed by Gleb Pomykalov. As for the first versions, it was mostly based
134
+ on [http://github.com/kovyrin/loops](loops) code written by Alexey Kovyrin. Now
135
+ most of it is heavily refactored. The gem is released under the MIT license. For
136
+ more details, see the LICENSE file.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.6
1
+ 0.2.0
data/daemonizer.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{daemonizer}
8
- s.version = "0.1.6"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Gleb Pomykalov"]
12
- s.date = %q{2010-07-13}
12
+ s.date = %q{2010-07-14}
13
13
  s.default_executable = %q{daemonizer}
14
14
  s.description = %q{Inspired by bundler and rack. Mostly built on top of Alexey Kovyrin's loops code. http://github.com/kovyrin/loops}
15
15
  s.email = %q{glebpom@gmail.com}
data/lib/daemonizer.rb CHANGED
@@ -32,6 +32,25 @@ module Daemonizer
32
32
  "Demfile"
33
33
  end
34
34
  end
35
+
36
+ def self.init_logger(name, log_file)
37
+ @@logger = Logger.new name
38
+ outputter = FileOutputter.new('log', :filename => log_file)
39
+ outputter.formatter = PatternFormatter.new :pattern => "%d - %l %g - %m"
40
+ @@logger.outputters = outputter
41
+ @@logger.level = INFO
42
+ end
43
+
44
+ def self.init_console_logger(name)
45
+ @@logger = Logger.new name
46
+ outputter = Outputter.stdout
47
+ outputter.formatter = PatternFormatter.new :pattern => "%d - %l %g - %m"
48
+ @@logger.outputters = outputter
49
+ end
50
+
51
+ def self.logger
52
+ @@logger
53
+ end
35
54
 
36
55
  def self.[](pool)
37
56
  find_pools(pool).first or nil
@@ -87,7 +87,7 @@ module Daemonizer
87
87
  desc "debug", "Debug pool (do not demonize)"
88
88
  def debug(pool_name = nil)
89
89
  puts "You should supply pool_name to debug" if pool_name.nil?
90
- control_pools_loop(pool_name, "execution ended") do |pool|
90
+ control_pools_loop(pool_name, "execution ended", true) do |pool|
91
91
  STDOUT.sync = true
92
92
  print_pool pool.name, "Debugging pool: "
93
93
 
@@ -101,9 +101,14 @@ module Daemonizer
101
101
  end
102
102
 
103
103
  private
104
- def control_pools_loop(pool_name, message = nil, &block)
104
+ def control_pools_loop(pool_name, message = nil, debug = false, &block)
105
105
  Daemonizer.find_pools(pool_name).each do |pool|
106
106
  Process.fork do
107
+ if debug
108
+ Daemonizer.init_console_logger(pool.name.to_s)
109
+ else
110
+ Daemonizer.init_logger(pool.name.to_s, pool.log_file)
111
+ end
107
112
  yield(pool)
108
113
  end
109
114
  Process.wait
@@ -11,28 +11,19 @@ module Daemonizer
11
11
  validate
12
12
  initialize_handler
13
13
  end
14
-
15
- def option(key)
16
- if handler
17
- handler.option(key)
18
- else
19
- raise ConfigError, "handler is not initialized"
20
- end
21
- end
22
14
 
23
15
  def initialize_handler
24
- if @options[:after_init]
25
- @handler = FakeHandler.new(@options[:before_init], @options[:after_init], @options)
26
- @options[:after_init] = @options[:before_init] = nil
16
+ if @options[:start]
17
+ @handler = FakeHandler.new(@options[:prepare], @options[:start], @options)
18
+ @options[:start] = @options[:prepare] = nil
27
19
  elsif
28
20
  @handler = @options[:handler].new(@options[:handler_options])
29
21
  end
30
- @handler.logger = @logger
31
22
  end
32
23
 
33
24
  def init_defaults
34
- @options[:before_init] ||= nil
35
- @options[:after_init] ||= nil
25
+ @options[:prepare] ||= nil
26
+ @options[:start] ||= nil
36
27
  @options[:workers] ||= 1
37
28
  @options[:log_file] ||= "log/#{@pool}.log"
38
29
  @options[:poll_period] ||= 5
@@ -47,14 +38,14 @@ module Daemonizer
47
38
  raise ConfigError, "Poll period should be more then zero" if @options[:poll_period] < 1
48
39
  if @options[:handler]
49
40
  raise ConfigError, "Handler should be a class" unless @options[:handler].is_a?(Class)
50
- raise ConfigError, "Handler should respond to :after_init" unless @options[:handler].public_instance_methods.include?('after_init')
51
- raise ConfigError, "Handler set. Don't use :after_init and :before init in Demfile" if @options[:before_init] || @options[:after_init]
41
+ raise ConfigError, "Handler should respond to :start" unless @options[:handler].public_instance_methods.include?('start')
42
+ raise ConfigError, "Handler set. Don't use :start and :before init in Demfile" if @options[:prepare] || @options[:start]
52
43
  else
53
- if @options[:before_init]
54
- raise ConfigError, "before_init should have block" unless @options[:before_init].is_a?(Proc)
44
+ if @options[:prepare]
45
+ raise ConfigError, "prepare should have block" unless @options[:prepare].is_a?(Proc)
55
46
  end
56
- raise ConfigError, "after_init should be set" if @options[:after_init].nil?
57
- raise ConfigError, "after_init should have block" unless @options[:after_init].is_a?(Proc)
47
+ raise ConfigError, "start should be set" if @options[:start].nil?
48
+ raise ConfigError, "start should have block" unless @options[:start].is_a?(Proc)
58
49
  end
59
50
  end
60
51
 
@@ -24,6 +24,10 @@ class Daemonizer::Dsl
24
24
  raise DslError, "you should supply block or value to :set_option"
25
25
  end
26
26
  end
27
+
28
+ def after_fork(&block)
29
+ set_option :after_fork, block
30
+ end
27
31
 
28
32
  def not_cow_friendly
29
33
  @options[:cow_friendly] = false
@@ -45,12 +49,12 @@ class Daemonizer::Dsl
45
49
  @options[:workers] = num.to_i
46
50
  end
47
51
 
48
- def before_init(&blk)
49
- @options[:before_init] = blk
52
+ def prepare(&blk)
53
+ @options[:prepare] = blk
50
54
  end
51
55
 
52
- def after_init(&blk)
53
- @options[:after_init] = blk
56
+ def start(&blk)
57
+ @options[:start] = blk
54
58
  end
55
59
 
56
60
  def pid_file(pid)
@@ -1,27 +1,22 @@
1
1
  module Daemonizer
2
2
  class Engine
3
3
  attr_reader :config
4
- attr_reader :logger
5
4
 
6
5
  def initialize(config, debug = false)
7
6
  @config = config
8
- @logger = Logger.new @config.name.to_s
9
- outputter = FileOutputter.new('log', :filename => @config.log_file)
10
- outputter.formatter = PatternFormatter.new :pattern => "%d - %l %g - %m"
11
- @logger.outputters = outputter
12
- @logger.level = INFO
13
7
  GDC.set "#{Process.pid}/monitor"
14
8
  end
15
-
9
+
16
10
  def start!
17
11
  @pm = ProcessManager.new(@config)
18
12
 
19
13
  init_block = Proc.new do
20
14
  begin
21
- @pm.start_workers do |process_id|
15
+ @pm.start_workers do |process_id|
22
16
  @config.handler.worker_id = process_id
23
17
  @config.handler.workers_count = @config.workers
24
- @config.handler.after_init
18
+ @config.handler.after_fork
19
+ @config.handler.start
25
20
  end
26
21
  rescue Exception => e
27
22
  log_error(e)
@@ -32,13 +27,14 @@ module Daemonizer
32
27
  if @config.cow_friendly
33
28
  if GC.respond_to?(:copy_on_write_friendly=)
34
29
  GC.copy_on_write_friendly = true
35
- logger.info "Enabling COW-friendly feature"
30
+ Daemonizer.logger.info "Enabling COW-friendly feature"
36
31
  else
37
- logger.info "COW-friendly feature is not supported by currently used ruby version"
32
+ Daemonizer.logger.info "COW-friendly feature is not supported by currently used ruby version"
38
33
  end
39
34
  end
40
- @config.handler.logger = logger
41
- @config.handler.before_init(init_block)
35
+ Daemonizer.logger.info "Workers count is #{config.workers}"
36
+ @config.handler.logger = Daemonizer.logger
37
+ @config.handler.prepare(init_block)
42
38
  rescue Exception => e
43
39
  log_error(e)
44
40
  end
@@ -49,37 +45,34 @@ module Daemonizer
49
45
  end
50
46
 
51
47
  def debug!
52
- outputter = Outputter.stdout
53
- outputter.formatter = PatternFormatter.new :pattern => "%d - %l %g - %m"
54
- logger.outputters = outputter
55
-
48
+ Daemonizer.init_console_logger
49
+
56
50
  init_block = Proc.new do
57
51
  begin
58
52
  @config.handler.worker_id = 1
59
53
  @config.handler.workers_count = 1
60
- @config.handler.after_init
54
+ @config.handler.start
61
55
  rescue Exception => e
62
56
  log_error(e)
63
57
  end
64
58
  end
65
59
 
66
60
  begin
67
- @config.handler.logger = logger
68
- @config.handler.before_init(init_block)
61
+ @config.handler.prepare(init_block)
69
62
  rescue Exception => e
70
63
  log_error(e)
71
64
  end
72
65
  end
73
66
 
74
67
  def log_error(e)
75
- logger.fatal e.to_s
76
- logger.fatal "#{e.class}: #{e}\n" + e.backtrace.join("\n")
68
+ Daemonizer.logger.fatal e.to_s
69
+ Daemonizer.logger.fatal "#{e.class}: #{e}\n" + e.backtrace.join("\n")
77
70
  end
78
71
 
79
72
  private
80
73
  def setup_signals
81
74
  stop = proc {
82
- @config.logger.info "Received a signal... stopping..."
75
+ Daemonizer.logger.info "Received a signal... stopping..."
83
76
  @pm.start_shutdown!
84
77
  }
85
78
 
@@ -4,40 +4,46 @@ module Daemonizer
4
4
  attr_accessor :workers_count
5
5
  attr_accessor :logger
6
6
 
7
- def initialize(options = {})
8
- @options = options
7
+ def initialize(handler_options = {})
8
+ @handler_options = handler_options
9
9
  end
10
10
 
11
- def before_init(block)
11
+ def prepare(block)
12
12
  block.call
13
13
  end
14
14
 
15
15
  def option(key)
16
- if option = @options[key.to_sym]
17
- option.value(self)
16
+ if handler_option = @handler_options[key.to_sym]
17
+ handler_option.value(self)
18
18
  else
19
19
  nil
20
20
  end
21
21
  end
22
+
23
+ def after_fork
24
+ if block = option(:after_fork)
25
+ block.call(Daemonizer.logger, @worker_id, @workers_count)
26
+ end
27
+ end
22
28
  end
23
29
 
24
30
  class FakeHandler < Handler
25
- def initialize(before_init, after_init, options = {})
26
- @before_init = before_init
27
- @after_init = after_init
28
- super(options)
31
+ def initialize(prepare, start, handler_options = {})
32
+ @prepare = prepare
33
+ @start = start
34
+ super(handler_options)
29
35
  end
30
36
 
31
- def before_init(block)
32
- if @before_init
33
- @before_init.call(@logger, block)
37
+ def prepare(block)
38
+ if @prepare
39
+ @prepare.call(Daemonizer.logger, block)
34
40
  else
35
41
  super
36
42
  end
37
43
  end
38
44
 
39
- def after_init
40
- @after_init.call(@logger, @worker_id, @workers_count)
45
+ def start
46
+ @start.call(Daemonizer.logger, @worker_id, @workers_count)
41
47
  end
42
48
  end
43
49
  end
@@ -5,14 +5,11 @@ module Daemonizer
5
5
  @shutdown = false
6
6
  @config = config
7
7
  end
8
-
9
- def logger
10
- @config.logger
11
- end
8
+
12
9
 
13
10
  def start_workers(&blk)
14
11
  raise ArgumentError, "Need a worker block!" unless block_given?
15
-
12
+ Daemonizer.logger.info "starting all workers"
16
13
  @worker_pools[@config.pool] = WorkerPool.new(@config.pool, self, &blk)
17
14
  @worker_pools[@config.pool].start_workers(@config.workers)
18
15
  end
@@ -20,26 +17,26 @@ module Daemonizer
20
17
  def monitor_workers
21
18
  setup_signals
22
19
 
23
- logger.debug 'Starting workers monitoring code...'
20
+ Daemonizer.logger.debug 'Starting workers monitoring code...'
24
21
  loop do
25
- logger.debug "Checking workers' health..."
22
+ Daemonizer.logger.debug "Checking workers' health..."
26
23
  @worker_pools.each do |name, pool|
27
24
  break if shutdown?
28
25
  pool.check_workers
29
26
  end
30
27
 
31
28
  break if shutdown?
32
- logger.debug "Sleeping for #{@config.poll_period} seconds..."
29
+ Daemonizer.logger.debug "Sleeping for #{@config.poll_period} seconds..."
33
30
  sleep(@config.poll_period)
34
31
  end
35
32
  ensure
36
- logger.debug "Workers monitoring loop is finished, starting shutdown..."
33
+ Daemonizer.logger.debug "Workers monitoring loop is finished, starting shutdown..."
37
34
  # Send out stop signals
38
35
  stop_workers(false)
39
36
 
40
37
  # Wait for all the workers to die
41
38
  unless wait_for_workers(10)
42
- logger.warn "Some workers are still alive after 10 seconds of waiting. Killing them..."
39
+ Daemonizer.logger.warn "Some workers are still alive after 10 seconds of waiting. Killing them..."
43
40
  stop_workers(true)
44
41
  wait_for_workers(5)
45
42
  end
@@ -53,7 +50,7 @@ module Daemonizer
53
50
 
54
51
  def wait_for_workers(seconds)
55
52
  seconds.times do
56
- logger.debug "Shutting down... waiting for workers to die (we have #{seconds} seconds)..."
53
+ Daemonizer.logger.debug "Shutting down... waiting for workers to die (we have #{seconds} seconds)..."
57
54
  running_total = 0
58
55
 
59
56
  @worker_pools.each do |name, pool|
@@ -61,11 +58,11 @@ module Daemonizer
61
58
  end
62
59
 
63
60
  if running_total.zero?
64
- logger.debug "All workers are dead. Exiting..."
61
+ Daemonizer.logger.debug "All workers are dead. Exiting..."
65
62
  return true
66
63
  end
67
64
 
68
- logger.debug "#{running_total} workers are still running! Sleeping for a second..."
65
+ Daemonizer.logger.debug "#{running_total} workers are still running! Sleeping for a second..."
69
66
  sleep(1)
70
67
  end
71
68
 
@@ -74,7 +71,7 @@ module Daemonizer
74
71
 
75
72
  def stop_workers(force = false)
76
73
  # Set shutdown flag
77
- logger.debug "Stopping workers#{force ? ' (forced)' : ''}..."
74
+ Daemonizer.logger.debug "Stopping workers#{force ? ' (forced)' : ''}..."
78
75
 
79
76
  # Termination loop
80
77
  @worker_pools.each do |name, pool|
@@ -87,7 +84,7 @@ module Daemonizer
87
84
  end
88
85
 
89
86
  def start_shutdown!
90
- logger.debug "Starting shutdown (shutdown flag set)..."
87
+ Daemonizer.logger.debug "Starting shutdown (shutdown flag set)..."
91
88
  @shutdown = true
92
89
  end
93
90
  end
@@ -8,21 +8,17 @@ module Daemonizer
8
8
 
9
9
  @name = name
10
10
  @pm = pm
11
- @engine = engine.to_s
12
11
  @worker_block = blk
13
12
  @worker_id = worker_id
14
13
  end
15
14
 
16
- def logger
17
- @pm.logger
18
- end
19
-
20
15
  def shutdown?
21
16
  @pm.shutdown?
22
17
  end
23
18
 
24
19
  def run
25
20
  return if shutdown?
21
+ Daemonizer.logger.info "Forking..."
26
22
  @pid = Kernel.fork do
27
23
  Dir.chdir '/'
28
24
  File.umask 0000
@@ -32,7 +28,9 @@ module Daemonizer
32
28
  STDERR.reopen STDOUT
33
29
 
34
30
  @pid = Process.pid
31
+
35
32
  GDC.set "#{@pid}/#{@worker_id}"
33
+ Daemonizer.logger.info "Forked..."
36
34
  normal_exit = false
37
35
  begin
38
36
  $0 = "#{@name} worker: instance #{@worker_id}\0"
@@ -43,18 +41,18 @@ module Daemonizer
43
41
  message = SystemExit === e ? "exit(#{e.status})" : e.to_s
44
42
  if SystemExit === e and e.success?
45
43
  if normal_exit
46
- logger.info("Worker finished: normal return")
44
+ Daemonizer.logger.info("Worker finished: normal return")
47
45
  else
48
- logger.error("Worker exited: #{message} at #{e.backtrace.first}")
46
+ Daemonizer.logger.error("Worker exited: #{message} at #{e.backtrace.first}")
49
47
  end
50
48
  else
51
- logger.error("Worker exited with error: #{message}\n #{e.backtrace.join("\n ")}")
49
+ Daemonizer.logger.error("Worker exited with error: #{message}\n #{e.backtrace.join("\n ")}")
52
50
  end
53
- logger.debug("Terminating #{@name} worker: #{@pid}")
51
+ Daemonizer.logger.debug("Terminating #{@name} worker: #{@pid}")
54
52
  end
55
53
  end
56
54
  rescue Exception => e
57
- logger.error("Exception from worker: #{e} at #{e.backtrace.first}")
55
+ Daemonizer.logger.error("Exception from worker: #{e} at #{e.backtrace.first}")
58
56
  end
59
57
 
60
58
  def running?(verbose = false)
@@ -62,10 +60,10 @@ module Daemonizer
62
60
  begin
63
61
  Process.waitpid(@pid, Process::WNOHANG)
64
62
  res = Process.kill(0, @pid)
65
- logger.debug("KILL(#{@pid}) = #{res}") if verbose
63
+ Daemonizer.logger.debug("KILL(#{@pid}) = #{res}") if verbose
66
64
  return true
67
65
  rescue Errno::ESRCH, Errno::ECHILD, Errno::EPERM => e
68
- logger.error("Exception from kill: #{e} at #{e.backtrace.first}") if verbose
66
+ Daemonizer.logger.error("Exception from kill: #{e} at #{e.backtrace.first}") if verbose
69
67
  return false
70
68
  end
71
69
  end
@@ -73,10 +71,10 @@ module Daemonizer
73
71
  def stop(force = false)
74
72
  begin
75
73
  sig = force ? 'SIGKILL' : 'SIGTERM'
76
- logger.debug("Sending #{sig} to ##{@pid}")
74
+ Daemonizer.logger.debug("Sending #{sig} to ##{@pid}")
77
75
  Process.kill(sig, @pid)
78
76
  rescue Errno::ESRCH, Errno::ECHILD, Errno::EPERM=> e
79
- logger.error("Exception from kill: #{e} at #{e.backtrace.first}")
77
+ Daemonizer.logger.error("Exception from kill: #{e} at #{e.backtrace.first}")
80
78
  end
81
79
  end
82
80
  end
@@ -1,13 +1,12 @@
1
1
  module Daemonizer
2
2
  class WorkerPool
3
- attr_reader :name, :logger
3
+ attr_reader :name
4
4
 
5
5
  def initialize(name, pm, &blk)
6
6
  @name = name
7
7
  @pm = pm
8
8
  @worker_block = blk
9
9
  @workers = []
10
- @logger = @pm.logger
11
10
  end
12
11
 
13
12
  def shutdown?
@@ -15,17 +14,17 @@ module Daemonizer
15
14
  end
16
15
 
17
16
  def start_workers(number)
18
- logger.debug "Creating #{number} workers for #{name} pool..."
17
+ Daemonizer.logger.debug "Creating #{number} workers for #{name} pool..."
19
18
  number.times do |i|
20
19
  @workers << Worker.new(name, @pm, i+1, &@worker_block)
21
20
  end
22
21
  end
23
22
 
24
23
  def check_workers
25
- logger.debug "Checking loop #{name} workers..."
24
+ Daemonizer.logger.debug "Checking loop #{name} workers..."
26
25
  @workers.each do |worker|
27
26
  next if worker.running? || worker.shutdown?
28
- logger.warn "Worker #{worker.name} is not running. Restart!"
27
+ Daemonizer.logger.warn "Worker #{worker.name} is not running. Restart!"
29
28
  worker.run
30
29
  end
31
30
  end
@@ -35,13 +34,13 @@ module Daemonizer
35
34
  @workers.each do |worker|
36
35
  next unless worker.running?
37
36
  running += 1
38
- logger.debug "Worker #{name} is still running (#{worker.pid})"
37
+ Daemonizer.logger.debug "Worker #{name} is still running (#{worker.pid})"
39
38
  end
40
39
  return running
41
40
  end
42
41
 
43
42
  def stop_workers(force)
44
- logger.debug "Stopping #{name} pool workers..."
43
+ Daemonizer.logger.debug "Stopping #{name} pool workers..."
45
44
  @workers.each do |worker|
46
45
  next unless worker.running?
47
46
  worker.stop(force)
metadata CHANGED
@@ -5,9 +5,9 @@ version: !ruby/object:Gem::Version
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 1
9
- - 6
10
- version: 0.1.6
8
+ - 2
9
+ - 0
10
+ version: 0.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Gleb Pomykalov
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-07-13 00:00:00 +04:00
18
+ date: 2010-07-14 00:00:00 +04:00
19
19
  default_executable: daemonizer
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency