daemonizer 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -0,0 +1,48 @@
1
+ engine :fork
2
+ workers 2
3
+
4
+ pool :daemonizer do
5
+ workers 4
6
+ poll_period 5
7
+ log_file "log/daemonizer.log" #relative to Demfile
8
+
9
+ before_init do |logger, block|
10
+ block.call
11
+ end
12
+
13
+ after_init do |logger, worker_id, workers_count|
14
+ logger.info "Started #{worker_id} from #{workers_count}"
15
+
16
+ exit = false
17
+
18
+ stop = proc {
19
+ exit = true
20
+ }
21
+
22
+ trap('TERM', stop)
23
+ trap('INT', stop)
24
+ trap('EXIT', stop)
25
+
26
+ loop do
27
+ break if exit
28
+ logger.info "Ping #{worker_id}"
29
+ sleep 10
30
+ end
31
+
32
+ true
33
+ end
34
+ end
35
+
36
+ pool :new_daemonizer do
37
+
38
+ before_init do |logger, block|
39
+ logger.info "New: Preparing master process"
40
+ block.call
41
+ end
42
+
43
+ after_init do |logger, worker_id, workers_count|
44
+ logger.info "Started #{worker_id}/#{workers_count}"
45
+ sleep 1
46
+ logger.info "Stopped #{worker_id}/#{workers_count}"
47
+ end
48
+ end
data/Rakefile CHANGED
@@ -1,14 +1,32 @@
1
+ require 'rubygems'
2
+ require 'rubygems/specification'
3
+
1
4
  begin
2
5
  require 'jeweler'
3
6
  Jeweler::Tasks.new do |gemspec|
4
7
  gemspec.name = "daemonizer"
5
8
  gemspec.summary = "Daemonizer allows you to easily create custom daemons on ruby. Supporting preforked and threaded models."
6
- gemspec.description = "Inspired by bundler. Mostly build on top of Alexey Kovyrin's loops code. http://github.com/kovyrin/loops"
9
+ gemspec.description = "Inspired by bundler and rack. Mostly built on top of Alexey Kovyrin's loops code. http://github.com/kovyrin/loops"
7
10
  gemspec.email = "glebpom@gmail.com"
8
11
  gemspec.homepage = "http://github.com/glebpom/daemonizer"
9
12
  gemspec.authors = ["Gleb Pomykalov"]
13
+ gemspec.add_dependency('log4r', '>= 1.1.8')
10
14
  end
11
15
  Jeweler::GemcutterTasks.new
12
16
  rescue LoadError
13
17
  puts "Jeweler not available. Install it with: gem install jeweler"
14
- end
18
+ end
19
+
20
+ begin
21
+ require 'yard'
22
+ YARD::Rake::YardocTask.new(:yard) do |t|
23
+ t.options = ['--title', 'Daemonizer Documentation']
24
+ if ENV['PRIVATE']
25
+ t.options.concat ['--protected', '--private']
26
+ else
27
+ t.options.concat ['--protected', '--no-private']
28
+ end
29
+ end
30
+ rescue LoadError
31
+ puts 'Yard not available. Install it with: sudo gem install yard'
32
+ end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1
1
+ 0.0.2
data/bin/daemonizer ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.dirname(__FILE__) + '/../lib') unless $:.include?(File.dirname(__FILE__) + '/../lib')
3
+
4
+ require File.dirname(__FILE__) + '/../lib/daemonizer'
5
+
6
+ $thor_runner = false
7
+
8
+ Daemonizer::CLI.start
@@ -0,0 +1,57 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{daemonizer}
8
+ s.version = "0.0.2"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Gleb Pomykalov"]
12
+ s.date = %q{2010-07-06}
13
+ s.default_executable = %q{daemonizer}
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
+ s.email = %q{glebpom@gmail.com}
16
+ s.executables = ["daemonizer"]
17
+ s.extra_rdoc_files = [
18
+ "README"
19
+ ]
20
+ s.files = [
21
+ "README",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "bin/daemonizer",
25
+ "daemonizer.gemspec",
26
+ "lib/daemonizer.rb",
27
+ "lib/daemonizer/autoload.rb",
28
+ "lib/daemonizer/cli.rb",
29
+ "lib/daemonizer/config.rb",
30
+ "lib/daemonizer/daemonize.rb",
31
+ "lib/daemonizer/dsl.rb",
32
+ "lib/daemonizer/engine.rb",
33
+ "lib/daemonizer/errors.rb",
34
+ "lib/daemonizer/process_manager.rb",
35
+ "lib/daemonizer/worker.rb",
36
+ "lib/daemonizer/worker_pool.rb"
37
+ ]
38
+ s.homepage = %q{http://github.com/glebpom/daemonizer}
39
+ s.rdoc_options = ["--charset=UTF-8"]
40
+ s.require_paths = ["lib"]
41
+ s.rubygems_version = %q{1.3.7}
42
+ s.summary = %q{Daemonizer allows you to easily create custom daemons on ruby. Supporting preforked and threaded models.}
43
+
44
+ if s.respond_to? :specification_version then
45
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
46
+ s.specification_version = 3
47
+
48
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
49
+ s.add_runtime_dependency(%q<log4r>, [">= 1.1.8"])
50
+ else
51
+ s.add_dependency(%q<log4r>, [">= 1.1.8"])
52
+ end
53
+ else
54
+ s.add_dependency(%q<log4r>, [">= 1.1.8"])
55
+ end
56
+ end
57
+
data/lib/daemonizer.rb ADDED
@@ -0,0 +1,21 @@
1
+ require 'rubygems'
2
+ require 'yaml'
3
+ require 'erb'
4
+ require 'pathname'
5
+ require 'log4r'
6
+
7
+ include Log4r
8
+
9
+ module Daemonizer
10
+
11
+ def self.root=(value)
12
+ @@root = value
13
+ end
14
+
15
+ def self.root
16
+ @@root
17
+ end
18
+
19
+ end
20
+
21
+ require File.dirname(__FILE__) + '/../lib/daemonizer/autoload'
@@ -0,0 +1,16 @@
1
+ module Daemonizer
2
+ # @private
3
+ def self.__p(*path) File.join(File.dirname(File.expand_path(__FILE__)), *path) end
4
+
5
+ autoload :Config, __p('config')
6
+ autoload :Dsl, __p('dsl')
7
+ autoload :Errors, __p('errors')
8
+ autoload :CLI, __p('cli')
9
+ autoload :Daemonize, __p('daemonize')
10
+ autoload :Engine, __p('engine')
11
+ autoload :Worker, __p('worker')
12
+ autoload :WorkerPool, __p('worker_pool')
13
+ autoload :ProcessManager, __p('process_manager')
14
+
15
+ include Errors
16
+ end
@@ -0,0 +1,115 @@
1
+ require 'thor'
2
+ require 'rubygems/config_file'
3
+
4
+ module Daemonizer
5
+ class CLI < Thor
6
+ check_unknown_options!
7
+
8
+ def initialize(*)
9
+ super
10
+ end
11
+
12
+ desc "start", "Start pool"
13
+ method_option :demfile, :type => :string, :aliases => "-D", :banner => "Path to Demfile"
14
+ def start(pool_name = nil)
15
+ control_pools_loop(pool_name, "successfully started", options[:demfile]) do |pool|
16
+ # Pid file check
17
+ if Daemonize.check_pid(pool.pid_file)
18
+ print_pool pool.name, "Can't start, another process exists!"
19
+ exit(1)
20
+ end
21
+
22
+ print_pool pool.name, "Starting pool"
23
+
24
+ app_name = "#{pool.name} monitor\0"
25
+
26
+ Daemonize.daemonize(app_name)
27
+
28
+ Dir.chdir(Daemonizer.root) # Make sure we're in the working directory
29
+
30
+ # Pid file creation
31
+ Daemonize.create_pid(pool.pid_file)
32
+
33
+ # Workers processing
34
+ engine = Engine.new(pool)
35
+ engine.start!
36
+
37
+ # Workers exited, cleaning up
38
+ File.delete(pool.pid_file) rescue nil
39
+ end
40
+ return true
41
+ end
42
+
43
+ desc "stop", "Stop pool"
44
+ method_option :demfile, :type => :string, :aliases => "-D", :banner => "Path to Demfile"
45
+ def stop(pool_name = nil)
46
+ control_pools_loop(pool_name, "successfully stoped", options[:demfile]) do |pool|
47
+ STDOUT.sync = true
48
+ unless Daemonize.check_pid(pool.pid_file)
49
+ print_pool pool.name, "No pid file or a stale pid file!"
50
+ exit 1
51
+ end
52
+
53
+ pid = Daemonize.read_pid(pool.pid_file)
54
+ print_pool pool.name, "Killing the process: #{pid}: "
55
+
56
+ loop do
57
+ Process.kill('SIGTERM', pid)
58
+ sleep(1)
59
+ break unless Daemonize.check_pid(pool.pid_file)
60
+ end
61
+
62
+ print_pool pool.name, " Done!"
63
+ exit(0)
64
+ end
65
+ return true
66
+ end
67
+
68
+ desc "restart", "Restart pool"
69
+ def restart(pool_name = nil)
70
+ invoke :stop, pool_name
71
+ invoke :start, pool_name
72
+ end
73
+
74
+ desc "status", "Print pool status"
75
+ def status(pool_name = nil)
76
+ return true
77
+ end
78
+
79
+ private
80
+ def control_pools_loop(pool_name, message = nil, demfile = nil, &block)
81
+ find_pools(pool_name, demfile).each do |pool|
82
+ Process.fork do
83
+ yield(pool)
84
+ end
85
+ Process.wait
86
+ if $?.exitstatus == 0 and message
87
+ print_pool pool.name, message
88
+ end
89
+ end
90
+ end
91
+
92
+ def find_pools(pool_name, demfile)
93
+ demfile_name = demfile || "Demfile"
94
+
95
+ Daemonizer.root = File.dirname(File.expand_path(demfile_name))
96
+
97
+ pools = Dsl.evaluate(demfile_name)
98
+
99
+ if pool_name
100
+ if pool = pools[pool_name.to_sym]
101
+ [pool]
102
+ else
103
+ print_pool pool_name, "Pool with name `#{pool_name}` is not configured"
104
+ []
105
+ end
106
+ else
107
+ pools.values
108
+ end
109
+ end
110
+
111
+ def print_pool(pool_name, message)
112
+ puts "#{pool_name}: #{message}"
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,65 @@
1
+ module Daemonizer
2
+ class Config
3
+ class ConfigError < StandardError; end
4
+
5
+ attr_reader :pool
6
+
7
+ def initialize(pool, options)
8
+ @pool = pool
9
+ @options = options
10
+ init_defaults
11
+ init_logger
12
+ validate
13
+ end
14
+
15
+ def init_logger
16
+ @logger = Logger.new @pool.to_s
17
+ outputter = FileOutputter.new('log', :filename => self.log_file)
18
+ outputter.formatter = PatternFormatter.new :pattern => "%d - %l %g - %m"
19
+ @logger.outputters = outputter
20
+ @logger.level = INFO
21
+ GDC.set "#{Process.pid}/monitor"
22
+ end
23
+
24
+ def init_defaults
25
+ @options[:before_init] ||= Proc.new
26
+ @options[:engine] ||= :fork
27
+ @options[:workers] ||= 1
28
+ @options[:log_file] ||= "log/#{@pool}.log"
29
+ @options[:poll_period] ||= 5
30
+ @options[:pid_file] ||= "pid/#{@pool}.pid"
31
+ end
32
+
33
+ def validate
34
+ Daemonizer.report_fatal_error "Workers count should be more then zero", @logger if @options[:workers] < 1
35
+ Daemonizer.report_fatal_error "Engine #{@options[:engine]} is not known", @logger unless [:fork, :thread].include?(@options[:engine])
36
+ if @options[:before_init]
37
+ Daemonizer.report_fatal_error "before_init should have block", @logger unless @options[:before_init].is_a?(Proc)
38
+ end
39
+ Daemonizer.report_fatal_error "after_init should be set", @logger if @options[:after_init].nil?
40
+ Daemonizer.report_fatal_error "after_init should have block", @logger unless @options[:after_init].is_a?(Proc)
41
+ Daemonizer.report_fatal_error "Poll period should be more then zero", @logger if @options[:poll_period] < 1
42
+ end
43
+
44
+ [:before_init, :engine, :workers, :after_init, :poll_period, :root].each do |method|
45
+ define_method method do
46
+ @options[method.to_sym]
47
+ end
48
+ end
49
+
50
+ [:log_file, :pid_file].each do |method|
51
+ define_method method do
52
+ File.join(Daemonizer.root, @options[method.to_sym])
53
+ end
54
+ end
55
+
56
+ def name
57
+ @pool
58
+ end
59
+
60
+ def logger
61
+ @logger
62
+ end
63
+ end
64
+
65
+ end
@@ -0,0 +1,67 @@
1
+ module Daemonizer
2
+ module Daemonize
3
+ def self.read_pid(pid_file)
4
+ File.open(pid_file) do |f|
5
+ f.gets.to_i
6
+ end
7
+ rescue Errno::ENOENT
8
+ 0
9
+ end
10
+
11
+ def self.check_pid(pid_file)
12
+ pid = read_pid(pid_file)
13
+ return false if pid.zero?
14
+ if defined?(::JRuby)
15
+ system "kill -0 #{pid} &> /dev/null"
16
+ return $? == 0
17
+ else
18
+ Process.kill(0, pid)
19
+ end
20
+ true
21
+ rescue Errno::ESRCH, Errno::ECHILD, Errno::EPERM
22
+ false
23
+ end
24
+
25
+ def self.create_pid(pid_file)
26
+ if File.exist?(pid_file)
27
+ puts "Pid file #{pid_file} exists! Checking the process..."
28
+ if check_pid(pid_file)
29
+ puts "Can't create new pid file because another process is runnig!"
30
+ return false
31
+ end
32
+ puts "Stale pid file! Removing..."
33
+ File.delete(pid_file)
34
+ end
35
+
36
+ File.open(pid_file, 'w') do |f|
37
+ f.puts(Process.pid)
38
+ end
39
+
40
+ return true
41
+ end
42
+
43
+ def self.daemonize(app_name)
44
+ if defined?(::JRuby)
45
+ puts "WARNING: daemonize method is not implemented for JRuby (yet), please consider using nohup."
46
+ return
47
+ end
48
+
49
+ fork && exit # Fork and exit from the parent
50
+
51
+ # Detach from the controlling terminal
52
+ unless sess_id = Process.setsid
53
+ raise Daemons.RuntimeException.new('cannot detach from controlling terminal')
54
+ end
55
+
56
+ # Prevent the possibility of acquiring a controlling terminal
57
+ trap 'SIGHUP', 'IGNORE'
58
+ exit if pid = fork
59
+
60
+ $0 = app_name if app_name
61
+
62
+ File.umask(0000) # Insure sensible umask
63
+
64
+ return sess_id
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,58 @@
1
+ module Daemonizer
2
+ class DslError < StandardError; end
3
+
4
+ class Dsl
5
+ def self.evaluate(gemfile)
6
+ builder = new
7
+ builder.instance_eval(File.read(gemfile.to_s), gemfile.to_s, 1)
8
+ builder.instance_variable_get("@configs")
9
+ end
10
+
11
+ def initialize
12
+ @source = nil
13
+ @options = {}
14
+ @pool = :default
15
+ @configs = {}
16
+ end
17
+
18
+ def poll_period(seconds)
19
+ @options[:poll_period] = seconds.to_i
20
+ end
21
+
22
+ def log_file(log)
23
+ @options[:log_file] = log
24
+ end
25
+
26
+ def workers(num)
27
+ @options[:workers] = num.to_i
28
+ end
29
+
30
+ def engine(name)
31
+ @options[:engine] = name.to_sym
32
+ end
33
+
34
+ def before_init(&blk)
35
+ @options[:before_init] = blk
36
+ end
37
+
38
+ def after_init(&blk)
39
+ @options[:after_init] = blk
40
+ end
41
+
42
+ def pid_file(pid)
43
+ @options[:pid_file] = pid
44
+ end
45
+
46
+ def pool(name, &blk)
47
+ @pool = name.to_sym
48
+ options = @options.dup
49
+ yield
50
+ @configs[@pool] = Config.new(@pool, @options)
51
+ rescue Config::ConfigError => e
52
+ puts "* Error in pool \"#{@pool}\": #{e.to_s}. Skipping..."
53
+ ensure
54
+ @options = options
55
+ @pool = nil
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,61 @@
1
+ module Daemonizer
2
+ class Engine
3
+ attr_reader :config
4
+
5
+ def initialize(config)
6
+ @config = config
7
+ end
8
+
9
+ def logger
10
+ @config.logger
11
+ end
12
+
13
+ def start!
14
+ @pm = ProcessManager.new(@config)
15
+
16
+ init_block = Proc.new do
17
+ @pm.start_workers do |process_id|
18
+ @config.after_init.call(logger, process_id, @config.workers)
19
+ end
20
+ end
21
+
22
+ @config.before_init.call(@config.logger, init_block)
23
+ # Start monitoring loop
24
+
25
+ setup_signals
26
+ @pm.monitor_workers
27
+ end
28
+
29
+ =begin
30
+ def debug_loop!(loop_name)
31
+ @pm = Daemonizer::ProcessManager.new(global_config, Daemonizer.logger)
32
+ loop_config = loops_config[loop_name] || {}
33
+
34
+ # Adjust loop config values before starting it in debug mode
35
+ loop_config['workers_number'] = 1
36
+ loop_config['debug_loop'] = true
37
+
38
+ # Load loop class
39
+ unless klass = load_loop_class(loop_name, loop_config)
40
+ puts "Can't load loop class!"
41
+ return false
42
+ end
43
+
44
+ # Start the loop
45
+ start_loop(loop_name, klass, loop_config)
46
+ end
47
+ =end
48
+ private
49
+ def setup_signals
50
+ stop = proc {
51
+ @config.logger.info "Received a signal... stopping..."
52
+ @pm.start_shutdown!
53
+ }
54
+
55
+ trap('TERM', stop)
56
+ trap('INT', stop)
57
+ trap('EXIT', stop)
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,6 @@
1
+ module Daemonizer::Errors
2
+ Error = Class.new(RuntimeError)
3
+
4
+ InvalidFrameworkError = Class.new(Error)
5
+ InvalidCommandError = Class.new(Error)
6
+ end
@@ -0,0 +1,94 @@
1
+ module Daemonizer
2
+ class ProcessManager
3
+ def initialize(config)
4
+ @worker_pools = {}
5
+ @shutdown = false
6
+ @config = config
7
+ end
8
+
9
+ def logger
10
+ @config.logger
11
+ end
12
+
13
+ def start_workers(&blk)
14
+ raise ArgumentError, "Need a worker block!" unless block_given?
15
+
16
+ @worker_pools[@config.pool] = WorkerPool.new(@config.pool, self, @config.engine, &blk)
17
+ @worker_pools[@config.pool].start_workers(@config.workers)
18
+ end
19
+
20
+ def monitor_workers
21
+ setup_signals
22
+
23
+ logger.debug 'Starting workers monitoring code...'
24
+ loop do
25
+ logger.debug "Checking workers' health..."
26
+ @worker_pools.each do |name, pool|
27
+ break if shutdown?
28
+ pool.check_workers
29
+ end
30
+
31
+ break if shutdown?
32
+ logger.debug "Sleeping for #{@config.poll_period} seconds..."
33
+ sleep(@config.poll_period)
34
+ end
35
+ ensure
36
+ logger.debug "Workers monitoring loop is finished, starting shutdown..."
37
+ # Send out stop signals
38
+ stop_workers(false)
39
+
40
+ # Wait for all the workers to die
41
+ unless wait_for_workers(10)
42
+ logger.warn "Some workers are still alive after 10 seconds of waiting. Killing them..."
43
+ stop_workers(true)
44
+ wait_for_workers(5)
45
+ end
46
+ end
47
+
48
+ def setup_signals
49
+ # Zombie reapers
50
+ trap('CHLD') {}
51
+ trap('EXIT') {}
52
+ end
53
+
54
+ def wait_for_workers(seconds)
55
+ seconds.times do
56
+ logger.debug "Shutting down... waiting for workers to die (we have #{seconds} seconds)..."
57
+ running_total = 0
58
+
59
+ @worker_pools.each do |name, pool|
60
+ running_total += pool.wait_workers
61
+ end
62
+
63
+ if running_total.zero?
64
+ logger.debug "All workers are dead. Exiting..."
65
+ return true
66
+ end
67
+
68
+ logger.debug "#{running_total} workers are still running! Sleeping for a second..."
69
+ sleep(1)
70
+ end
71
+
72
+ return false
73
+ end
74
+
75
+ def stop_workers(force = false)
76
+ # Set shutdown flag
77
+ logger.debug "Stopping workers#{force ? ' (forced)' : ''}..."
78
+
79
+ # Termination loop
80
+ @worker_pools.each do |name, pool|
81
+ pool.stop_workers(force)
82
+ end
83
+ end
84
+
85
+ def shutdown?
86
+ @shutdown
87
+ end
88
+
89
+ def start_shutdown!
90
+ logger.debug "Starting shutdown (shutdown flag set)..."
91
+ @shutdown = true
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,96 @@
1
+ module Daemonizer
2
+ class Worker
3
+ attr_reader :name
4
+ attr_reader :pid
5
+
6
+ def initialize(name, pm, engine, worker_id, &blk)
7
+ raise ArgumentError, "Need a worker block!" unless block_given?
8
+
9
+ @name = name
10
+ @pm = pm
11
+ @engine = engine.to_s
12
+ @worker_block = blk
13
+ @worker_id = worker_id
14
+ end
15
+
16
+ def logger
17
+ @pm.logger
18
+ end
19
+
20
+ def shutdown?
21
+ @pm.shutdown?
22
+ end
23
+
24
+ def run
25
+ return if shutdown?
26
+ if @engine == 'fork'
27
+ @pid = Kernel.fork do
28
+ @pid = Process.pid
29
+ GDC.set "#{@pid}/#{@worker_id}"
30
+ normal_exit = false
31
+ begin
32
+ $0 = "#{@name} worker: instance #{@worker_id}\0"
33
+ @worker_block.call(@worker_id)
34
+ normal_exit = true
35
+ exit(0)
36
+ rescue Exception => e
37
+ message = SystemExit === e ? "exit(#{e.status})" : e.to_s
38
+ if SystemExit === e and e.success?
39
+ if normal_exit
40
+ logger.info("Worker finished: normal return")
41
+ else
42
+ logger.error("Worker exited: #{message} at #{e.backtrace.first}")
43
+ end
44
+ else
45
+ logger.error("Worker exited with error: #{message}\n #{e.backtrace.join("\n ")}")
46
+ end
47
+ logger.debug("Terminating #{@name} worker: #{@pid}")
48
+ end
49
+ end
50
+ elsif @engine == 'thread'
51
+ @thread = Thread.start do
52
+ @worker_block.call
53
+ end
54
+ else
55
+ raise ArgumentError, "Invalid engine name: #{@engine}"
56
+ end
57
+ rescue Exception => e
58
+ logger.error("Exception from worker: #{e} at #{e.backtrace.first}")
59
+ end
60
+
61
+ def running?(verbose = false)
62
+ if @engine == 'fork'
63
+ return false unless @pid
64
+ begin
65
+ Process.waitpid(@pid, Process::WNOHANG)
66
+ res = Process.kill(0, @pid)
67
+ logger.debug("KILL(#{@pid}) = #{res}") if verbose
68
+ return true
69
+ rescue Errno::ESRCH, Errno::ECHILD, Errno::EPERM => e
70
+ logger.error("Exception from kill: #{e} at #{e.backtrace.first}") if verbose
71
+ return false
72
+ end
73
+ elsif @engine == 'thread'
74
+ @thread && @thread.alive?
75
+ else
76
+ raise ArgumentError, "Invalid engine name: #{@engine}"
77
+ end
78
+ end
79
+
80
+ def stop(force = false)
81
+ if @engine == 'fork'
82
+ begin
83
+ sig = force ? 'SIGKILL' : 'SIGTERM'
84
+ logger.debug("Sending #{sig} to ##{@pid}")
85
+ Process.kill(sig, @pid)
86
+ rescue Errno::ESRCH, Errno::ECHILD, Errno::EPERM=> e
87
+ logger.error("Exception from kill: #{e} at #{e.backtrace.first}")
88
+ end
89
+ elsif @engine == 'thread'
90
+ force && !defined?(::JRuby) ? @thread.kill! : @thread.kill
91
+ else
92
+ raise ArgumentError, "Invalid engine name: #{@engine}"
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,52 @@
1
+ module Daemonizer
2
+ class WorkerPool
3
+ attr_reader :name, :logger
4
+
5
+ def initialize(name, pm, engine, &blk)
6
+ @name = name
7
+ @pm = pm
8
+ @worker_block = blk
9
+ @engine = engine
10
+ @workers = []
11
+ @logger = @pm.logger
12
+ end
13
+
14
+ def shutdown?
15
+ @pm.shutdown?
16
+ end
17
+
18
+ def start_workers(number)
19
+ logger.debug "Creating #{number} workers for #{name} pool..."
20
+ number.times do |i|
21
+ @workers << Worker.new(name, @pm, @engine, i+1, &@worker_block)
22
+ end
23
+ end
24
+
25
+ def check_workers
26
+ logger.debug "Checking loop #{name} workers..."
27
+ @workers.each do |worker|
28
+ next if worker.running? || worker.shutdown?
29
+ logger.warn "Worker #{worker.name} is not running. Restart!"
30
+ worker.run
31
+ end
32
+ end
33
+
34
+ def wait_workers
35
+ running = 0
36
+ @workers.each do |worker|
37
+ next unless worker.running?
38
+ running += 1
39
+ logger.debug "Worker #{name} is still running (#{worker.pid})"
40
+ end
41
+ return running
42
+ end
43
+
44
+ def stop_workers(force)
45
+ logger.debug "Stopping #{name} pool workers..."
46
+ @workers.each do |worker|
47
+ next unless worker.running?
48
+ worker.stop(force)
49
+ end
50
+ end
51
+ end
52
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: daemonizer
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
4
+ hash: 27
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 1
10
- version: 0.0.1
9
+ - 2
10
+ version: 0.0.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Gleb Pomykalov
@@ -15,14 +15,29 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-06-28 00:00:00 +04:00
19
- default_executable:
20
- dependencies: []
21
-
22
- description: Inspired by bundler. Mostly build on top of Alexey Kovyrin's loops code. http://github.com/kovyrin/loops
18
+ date: 2010-07-06 00:00:00 +04:00
19
+ default_executable: daemonizer
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: log4r
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 1
32
+ - 1
33
+ - 8
34
+ version: 1.1.8
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ description: Inspired by bundler and rack. Mostly built on top of Alexey Kovyrin's loops code. http://github.com/kovyrin/loops
23
38
  email: glebpom@gmail.com
24
- executables: []
25
-
39
+ executables:
40
+ - daemonizer
26
41
  extensions: []
27
42
 
28
43
  extra_rdoc_files:
@@ -31,6 +46,19 @@ files:
31
46
  - README
32
47
  - Rakefile
33
48
  - VERSION
49
+ - bin/daemonizer
50
+ - daemonizer.gemspec
51
+ - lib/daemonizer.rb
52
+ - lib/daemonizer/autoload.rb
53
+ - lib/daemonizer/cli.rb
54
+ - lib/daemonizer/config.rb
55
+ - lib/daemonizer/daemonize.rb
56
+ - lib/daemonizer/dsl.rb
57
+ - lib/daemonizer/engine.rb
58
+ - lib/daemonizer/errors.rb
59
+ - lib/daemonizer/process_manager.rb
60
+ - lib/daemonizer/worker.rb
61
+ - lib/daemonizer/worker_pool.rb
34
62
  has_rdoc: true
35
63
  homepage: http://github.com/glebpom/daemonizer
36
64
  licenses: []