Class: Forever::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/forever/base.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}, &block) ⇒ Base


8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/forever/base.rb', line 8

def initialize(options={}, &block)
  @options = options
  forking = options.delete(:fork)

  # Run others methods
  options.each { |k,v| send(k, v) if respond_to?(k) }

  instance_eval(&block)

  # Setup directories
  Dir.chdir(dir)
  Dir.mkdir(tmp) unless File.exist?(tmp)
  Dir.mkdir(File.dirname(log)) if log && !File.exist?(File.dirname(log))

  write_config!

  case ARGV[0]
    when 'config'
      print config.to_yaml
      exit
    when 'start', 'restart', 'up', nil
      stop
    when 'run', 'live'
      detach = false
      stop
    when 'stop'
      stop
      exit
    when 'kill'
      stop!
      exit
    when 'update'
      print "[\e[90m%s\e[0m] Config written in \e[1m%s\e[0m\n" % [name, FOREVER_PATH]
      exit
    when 'remove'
      stop
      remove
      exit
    else
      print <<-RUBY.gsub(/ {10}/,'') % name
        Usage: \e[1m./%s\e[0m [start|stop|kill|restart|config|update]

        Commands:

          start      stop (if present) the daemon and perform a start
          live       run in no-deamon mode
          stop       stop the daemon if a during when it is idle
          restart    same as start
          kill       force stop by sending a KILL signal to the process
          config     show the current daemons config
          update     update the daemon config
          remove     removes the daemon config

      RUBY
      exit
  end

  clean_tmp!

  # Enable REE - http://www.rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
  GC.copy_on_write_friendly = true if GC.respond_to?(:copy_on_write_friendly=)

  maybe_fork(detach) do
    Process.setsid if detach != false

    $0 = "Forever: #{$0}" unless ENV['DONT_TOUCH_PS']
    print "[\e[90m%s\e[0m] Process %s with pid \e[1m%d\e[0m with \e[1m%s\e[0m and Forever v.%s\n" %
      [name, detach != false ? :daemonized : :running, Process.pid, forking ? :fork : :thread, Forever::VERSION]

    %w(INT TERM).each { |signal| trap(signal)  { stop! } }
    trap(:HUP) do
      IO.open(1, 'w'){ |s| s.puts config }
    end

    File.open(pid, "w") { |f| f.write(Process.pid.to_s) } if pid

    stream      = log ? File.new(log, @options[:append_log] ? 'a' : 'w') : File.open('/dev/null', 'w')
    stream.sync = true

    STDOUT.reopen(stream)
    STDERR.reopen(STDOUT)

    @started_at = Time.now

    # Invoke our before :all filters
    filters[:before][:all].each { |block| safe_call(block) }

    # Store pids of childs
    pids = []

    # Start deamons
    until stopping?
      current_queue = 1

      jobs.each do |job|
        next unless job.time?(Time.now)
        if queue && current_queue > queue
          puts "\n\nThe queue limit of #{queue} has been exceeded.\n\n"
          on_limit_exceeded ? on_limit_exceeded.call : sleep(60)
          break
        end
        if forking
          begin
            GC.start
            pids << Process.detach(fork { job_call(job) })
          rescue Errno::EAGAIN
            puts "\n\nWait all processes since os cannot create a new one\n\n"
            Process.waitall
          end
        else
          Thread.new { job_call(job) }
        end
        current_queue += 1
      end

      # Detach zombies, our ps will be happier
      pids.delete_if { |p| p.stop? }

      sleep 0.5
    end


    # Invoke our after :all filters
    filters[:after][:all].each { |block| safe_call(block) }

    # If we are here it means we are exiting so we can remove the pid and pending stop.txt
    clean_tmp!
  end

  self
end

Instance Attribute Details

#started_atObject (readonly)

Returns the value of attribute started_at


6
7
8
# File 'lib/forever/base.rb', line 6

def started_at
  @started_at
end

Instance Method Details

#after(filter, &block) ⇒ Object

After :all or :each jobs hook


316
317
318
319
# File 'lib/forever/base.rb', line 316

def after(filter, &block)
  raise "Filter #{filter.inspect} not supported, available options are: :each, :all" unless [:each, :all].include?(filter)
  filters[:after][filter] << block
end

#before(filter, &block) ⇒ Object

Before :all or :each jobs hook


308
309
310
311
# File 'lib/forever/base.rb', line 308

def before(filter, &block)
  raise "Filter #{filter.inspect} not supported, available options are: :each, :all" unless [:each, :all].include?(filter)
  filters[:before][filter] << block
end

#configObject

Return config of current worker in a hash


324
325
326
# File 'lib/forever/base.rb', line 324

def config
  { :dir => dir, :file => file, :log => log, :pid => pid }
end

#dir(value = nil) ⇒ Object Also known as: workspace

Base working Directory


183
184
185
# File 'lib/forever/base.rb', line 183

def dir(value=nil)
  value ? @_dir = value : @_dir
end

#every(period, options = {}, &block) ⇒ Object

Define a new job task

Example:

every 1.second, :at => '12:00' do
  my_long_task
end

148
149
150
# File 'lib/forever/base.rb', line 148

def every(period, options={}, &block)
  jobs << Forever::Job.new(period, options.merge!(:dir => dir), &block)
end

#file(value = nil) ⇒ Object

Caller file


162
163
164
# File 'lib/forever/base.rb', line 162

def file(value=nil)
  value ? @_file = value : @_file
end

#jobsObject

Our job list


155
156
157
# File 'lib/forever/base.rb', line 155

def jobs
  @_jobs ||= []
end

#log(value = nil) ⇒ Object

File were we redirect STOUT and STDERR, can be false.

Default: dir + 'log/.log'


200
201
202
203
# File 'lib/forever/base.rb', line 200

def log(value=nil)
  @_log ||= File.join(dir, "log/#{name}.log") if exists?(dir, file)
  value.nil? ? @_log : @_log = value
end

#nameObject

Daemon name


169
170
171
# File 'lib/forever/base.rb', line 169

def name
  File.basename(file, '.*')
end

#on_error(&block) ⇒ Object

Callback raised when an error occour


259
260
261
# File 'lib/forever/base.rb', line 259

def on_error(&block)
  block_given? ? @_on_error = block : @_on_error
end

#on_exit(&block) ⇒ Object

Callback raised when at exit


273
274
275
# File 'lib/forever/base.rb', line 273

def on_exit(&block)
  after(:all, &block)
end

#on_limit_exceeded(&block) ⇒ Object

Callback raised when queue limit was exceeded


266
267
268
# File 'lib/forever/base.rb', line 266

def on_limit_exceeded(&block)
  block_given? ? @_on_limit_exceeded = block : @_on_limit_exceeded
end

#on_ready(&block) ⇒ Object

Callback to fire when the daemon start (blocking, not in thread)


280
281
282
# File 'lib/forever/base.rb', line 280

def on_ready(&block)
  before(:all, &block)
end

#pid(value = nil) ⇒ Object

File were we store pid

Default: dir + 'tmp/.pid'


210
211
212
213
# File 'lib/forever/base.rb', line 210

def pid(value=nil)
  @_pid ||= File.join(tmp, "#{name}.pid") if exists?(dir, file)
  value.nil? ? @_pid : @_pid = value
end

#queue(value = nil) ⇒ Object

Queue size


176
177
178
# File 'lib/forever/base.rb', line 176

def queue(value=nil)
  value ? @_queue = value : @_queue
end

#removeObject

Remove the daemon from the config file


249
250
251
252
253
254
# File 'lib/forever/base.rb', line 249

def remove
  print "[\e[90m%s\e[0m] Removed the daemon from the config " % name
  config_was = File.exist?(FOREVER_PATH) ? YAML.load_file(FOREVER_PATH) : []
  config_was.delete_if { |conf| conf[:file] == file }
  File.open(FOREVER_PATH, "w") { |f| f.write config_was.to_yaml }
end

#running?(silent = false) ⇒ Boolean

Returns true if the pid exist and the process is running


287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/forever/base.rb', line 287

def running?(silent=false)
  if exists?(pid)
    current = File.read(pid).to_i
    print "[\e[90m%s\e[0m] Found pid \e[1m%d\e[0m...\n" % [name, current] unless silent
  else
    print "[\e[90m%s\e[0m] Pid \e[1mnot found\e[0m, process seems doesn't exist!\n" % name unless silent
    return false
  end

  is_running = begin
    Process.kill(0, current)
  rescue Errno::ESRCH
    false
  end

  is_running
end

#stopObject

Perform a soft stop


234
235
236
237
238
239
240
241
242
243
244
# File 'lib/forever/base.rb', line 234

def stop
  if running?
    print "[\e[90m%s\e[0m] Waiting the daemon\'s death " % name
    FileUtils.touch(stop_txt)
    while running?(true)
      print '.'; $stdout.flush
      sleep 1
    end
    print " \e[1mDONE\e[0m\n"
  end
end

#stop!Object

Search if there is a running process and stop it


218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/forever/base.rb', line 218

def stop!
  FileUtils.rm_f(stop_txt)
  if running?
    pid_was = File.read(pid).to_i
    print "[\e[90m%s\e[0m] Killing process \e[1m%d\e[0m...\n" % [name, pid_was]
    filters[:after][:all].each { |block| safe_call(block) }
    clean_tmp!
    Process.kill(:KILL, pid_was)
  else
    print "[\e[90m%s\e[0m] Process with \e[1mnot found\e[0m" % name
  end
end

#tmpObject

Temp directory, used to store pids and jobs status


191
192
193
# File 'lib/forever/base.rb', line 191

def tmp
  File.join(dir, 'tmp')
end

#to_sObject Also known as: inspect

Convert forever object in a readable string showing current config


331
332
333
# File 'lib/forever/base.rb', line 331

def to_s
  "#<Forever dir:#{dir}, file:#{file}, log:#{log}, pid:#{pid} jobs:#{jobs.size}>"
end