daemonizer 0.0.1 → 0.0.2

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.
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: []