Class: ProcessController

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby-process-controller/process_controller.rb

Defined Under Namespace

Classes: TestDaemon

Constant Summary collapse

COMMANDS =
%w(start stop run)
FAILURE_EXIT_CODES =
{
  :process_controller_init => 1,
  :termination_watchdog => 2,
  :stop_thread => 3,
  :dispatch_termination_signal => 4,
  :stop_has_no_effect => 5
}
DEFAULT_NAME =
"daemon"
LOGGER_CLASS =
defined?(ActiveSupport::BufferedLogger) && ActiveSupport::BufferedLogger || 
defined?(Merb::Logger) && Merb::Logger || 
defined?(Logger) && Logger
LOG_LEVELS =
LOGGER_CLASS::Severity.constants.map{|level| [LOGGER_CLASS::Severity.const_get(level), level]}.sort_by{|level|level.first}.map{|level|level.last}
DEFAULT_PID_DIR =
"tmp/pids"
DEFAULT_LOG_DIR =
"log"
DEFAULT_TERM_TIMEOUT =
15
MINIMUM_TERM_TIMEOUT =
1

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(argv = nil) ⇒ ProcessController

INITIALIZE



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/ruby-process-controller/process_controller.rb', line 157

def initialize(argv = nil)
  initialize_argv_options(argv)
  
  change_process_privileges

  Dir.chdir(@working_dir) if @working_dir
  
  if @command != "stop"
    @logger = LOGGER_CLASS.new(@log_file, @log_level)
    @logger.auto_flushing = true
    
    @stop_mutex = Mutex.new
    @stop_was_called = false
    
    @stop_thread_mutex = Mutex.new
    @stop_thread_was_runned = false
    
    @daemon_mutex = Mutex.new
    @must_terminate = false
    @daemon_started_to_some_extent = false
    @detached = false
    
    if (running_pid = getpid)
      if is_running? running_pid
        raise "#{@name}: already running with pid #{running_pid}"
      else
        log(:warn){"#{self.class}#initialize @name:`#{@name}' -- Found pidfile:`#{@pid_file}' with pid:`#{running_pid}' and is not running, it may be result of an unclean shutdown."}
      end
    end

    @logger.info {"#{self.class}#initialize @name:`#{@name}' -- Creating process..."}
    
    @daemon = yield(self)
    
    @logger.info {"#{self.class}#initialize @name:`#{@name}' -- Process initialized."}
  end
  
  if @argv["help"]
    @argv.show_options
    
  elsif @argv.complete?
    __send__("execute_#{@command}")
    
  else
    @argv.show_errors(@logger) if @logger
    @argv.show_options_and_errors
  end
  
  @logger.flush if @logger && @logger.respond_to?(:flush)
  
rescue Exception => exception
  begin
    log(:fatal){"#{self.class}#initialize @name:`#{@name}' -- #{exception.inspect_with_backtrace}"} if @log_file != STDOUT
    
    unless @detached
      @argv.errors << exception.inspect_with_backtrace
      @argv.show_errors
    end
  rescue Exception => another_exception
    STDERR.puts "\n#{exception.inspect_with_backtrace}"
    STDERR.puts "\nWhile handling previous exception another error was occured:\n#{another_exception.inspect_with_backtrace}"
  ensure
    Process.exit!(FAILURE_EXIT_CODES[:process_controller_init])
  end
end

Instance Attribute Details

#argvObject (readonly)

Returns the value of attribute argv.



82
83
84
# File 'lib/ruby-process-controller/process_controller.rb', line 82

def argv
  @argv
end

#envObject (readonly)

Returns the value of attribute env.



82
83
84
# File 'lib/ruby-process-controller/process_controller.rb', line 82

def env
  @env
end

#loggerObject (readonly)

Returns the value of attribute logger.



82
83
84
# File 'lib/ruby-process-controller/process_controller.rb', line 82

def logger
  @logger
end

#nameObject (readonly)

Returns the value of attribute name.



82
83
84
# File 'lib/ruby-process-controller/process_controller.rb', line 82

def name
  @name
end

Instance Method Details

#apply_argv_optionsObject



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/ruby-process-controller/process_controller.rb', line 116

def apply_argv_options
  @user = @argv["user"]
  @group = @argv["group"]
  
  @working_dir = @argv["working-dir"]
  
  @command = @argv["COMMAND"] || "run"
  
  @env = @argv["environment"] || (@command == "start" ? "production" : "development")
  
  @name = @argv["name"] || DEFAULT_NAME

  @pid_dir = @argv['pid-dir'] || DEFAULT_PID_DIR
  @pid_dir = '.' unless File.directory? @pid_dir

  @pid_file = if @argv['pid-file']
      (@argv['pid-file'] =~ /^\//) ? @argv['pid-file'] : "#{@pid_dir}/#{@argv['pid-file']}"
    else
       "#{@pid_dir}/#{@name}.pid"
    end

  @log_level = LOGGER_CLASS::Severity.const_get((@argv["log-level"] || (@env == "production" ? "warn" : "debug")).upcase.to_sym)
  
  @log_dir = @argv["log-dir"] || DEFAULT_LOG_DIR
  @log_dir = '.' unless File.directory?(@log_dir)
  
  @log_file = if (@command == "run" || @command == "stop")
      STDOUT
    elsif @argv["log-file"]
      (@argv["log-file"] =~ /^\//) ? @argv["log-file"] : "#{@log_dir}/#{@argv["log-file"]}"
    else
      "#{@log_dir}/#{@name}.log"
    end

  @term_timeout = (@argv['term-timeout'] || DEFAULT_TERM_TIMEOUT).to_i
  @term_timeout = MINIMUM_TERM_TIMEOUT if @term_timeout < MINIMUM_TERM_TIMEOUT
end

#define_argv_optionsObject



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/ruby-process-controller/process_controller.rb', line 95

def define_argv_options
  @argv.heading_option "[COMMAND]", "ProcessController command (#{COMMANDS * "/"})"

  @argv.option "-e, --environment NAME", "Run in environment (development/production/testing)"
  @argv.option "-n, --name NAME", "Daemon's name"

  @argv.option "--user USER", "Run as user"
  @argv.option "--group GROUP", "Run as group"
  @argv.option "--working-dir DIRECTORY", "Working directory, defaults to ."

  @argv.option "--pid-dir DIRECTORY", "PID directory, relative to working-dir, defaults to '#{DEFAULT_PID_DIR}', fallbacks to '.', may be absolute path"
  @argv.option "--pid-file FILE", "PID file, defaults to [name].pid, may be absolute path"

  @argv.option "--log-level LEVEL", "Log level (#{LOG_LEVELS * " "})"
  @argv.option "--log-dir DIRECTORY", "Log directory, relative to working-dir, default to '#{DEFAULT_LOG_DIR}', fallbacks to '.', may be absolute path"
  @argv.option "--log-file FILE", "Logfile, default to [name].log, may be absolute path"

  @argv.option "--term-timeout SECONDS", "Termination timeout, default to 30 seconds"
  @argv.option "-h, --help", "Show this help message"
end

#initialize_argv_options(argv) ⇒ Object

ARGV OPTIONS TODO: extract class



88
89
90
91
92
93
# File 'lib/ruby-process-controller/process_controller.rb', line 88

def initialize_argv_options(argv)
  @argv ||= ArgvParser.new(ARGV)
  define_argv_options
  @argv.parse!
  apply_argv_options
end

#stopObject



223
224
225
226
227
228
229
230
# File 'lib/ruby-process-controller/process_controller.rb', line 223

def stop
  @stop_mutex.synchronize do
    unless @stop_was_called
      @stop_was_called = true
      dispatch_termination_signal
    end
  end
end