class GarbageMan::Collector

Constants

WRITE_MOVE_OPTIONS

Attributes

after_gc_callbacks[R]
before_gc_callbacks[R]
fiber_poll[R]
last_gc_finished_at[R]
request_count[RW]
selected_to_collect_at[R]
show_gc_times[RW]
will_collect[RW]
will_select_next_server[RW]

Public Class Methods

new() click to toggle source
# File lib/garbageman/collector.rb, line 31
def initialize
  @show_gc_times = true
  @show_memory_released = false
  @before_gc_callbacks = []
  @after_gc_callbacks = []
  reset
end

Public Instance Methods

collect() click to toggle source
# File lib/garbageman/collector.rb, line 70
def collect
  # if we are starting to queue requests turn on gc, we could be in trouble
  if Config.check_request_queue? && queuing?
    warn QUEUING_REQUESTS
    GC.enable
  elsif waited_too_long_to_gc?
    warn (WAITED_TOO_LONG % (Time.now - @last_gc_finished_at))
    GC.enable
  end

  unless can_collect?
    @can_collect_at ||= Time.now
    return unless (Time.now - @can_collect_at) >= Config.time_to_wait_before_collecting
    return unless will_collect # return unless been selected to gc
    if waited_too_long_for_connections_to_drain?
      warn CONNECTIONS_DID_NOT_DRAIN_IN_TIME
    else
      return
    end
  end

  File.open(Config.gc_last_collected_file, 'w') { |f| f.write Time.now.to_i.to_s }

  before_gc_callbacks.each(&:call)

  write_gc_yaml server_index, STARTING
  debug "starting gc"
  memory_used = @show_memory_released ? process_resident_memory_size_in_kb : 0
  starts = Time.now
  GC.enable
  Config.gc_starts.times do
    GC.start
    sleep(Config.gc_sleep) if Config.gc_sleep > 0
  end
  @last_gc_finished_at = Time.now
  diff = (@last_gc_finished_at - starts) * 1000
  info "GC took #{'%.2f' % diff}ms for #{@request_count} requests" if @show_gc_times
  info "GC freed #{memory_used - process_resident_memory_size_in_kb}kb of memory" if @show_memory_released
  warn "was running GC and received #{Thin::Backends::Base.num_connections} connections " if Thin::Backends::Base.num_connections > 0
  write_gc_yaml server_index, NEXT_SERVER

  after_gc_callbacks.each(&:call)

  reset

  if can_disable?
    debug DISABLE_GC
    GC.disable
  else
    warn CANT_TURN_OFF
    GC.enable
  end
end
create_gc_yaml() click to toggle source

creates the gc yaml file or resets the status back to selected

# File lib/garbageman/collector.rb, line 137
def create_gc_yaml
  return unless server_index
  if File.exists?(Config.gc_yaml_file)
    return unless current_server?
  else
    return unless server_index == 0
  end
  write_gc_yaml server_index, SELECTED
end
debug(msg) click to toggle source
# File lib/garbageman/collector.rb, line 149
def debug(msg)
  logger.debug msg
end
healthy?() click to toggle source
# File lib/garbageman/collector.rb, line 44
def healthy?
  unless can_disable?
    warn CAN_NOT_DISABLE_GC
    GC.enable
    return true
  end

  if select_next_server?
    if @will_select_next_server
      select_next_server
    else
      # wait until we receive another request before selecting the next server
      @will_select_next_server = true
    end
    return true
  end

  if should_collect?
    @selected_to_collect_at ||= Time.now
    write_gc_yaml server_index, WILL_COLLECT
    false
  else
    true
  end
end
info(msg) click to toggle source
# File lib/garbageman/collector.rb, line 153
def info(msg)
  logger.info msg
end
logger() click to toggle source
# File lib/garbageman/collector.rb, line 147
def logger; GarbageMan.logger; end
process_resident_memory_size_in_kb() click to toggle source
# File lib/garbageman/collector.rb, line 124
def process_resident_memory_size_in_kb
  if File.exists?('/proc/self/statm')
    File.read('/proc/self/statm').split(' ')[1]
  else
    headers, stats = `ps v #{Process.pid}`.split "\n"
    return 0 unless headers && stats
    headers = headers.strip.gsub(/ +/, ' ').split(' ')
    stats = stats.strip.gsub(/ +/, ' ').split(' ')
    Hash[headers.zip(stats)]['RSS'].to_i
  end
end
register_fiber_pool(pool) click to toggle source

for use with rack-fiber_pool

# File lib/garbageman/collector.rb, line 40
def register_fiber_pool(pool)
  @fiber_poll = pool
end
select_next_server() click to toggle source
# File lib/garbageman/collector.rb, line 161
def select_next_server
  return unless @will_select_next_server
  return unless @request_count >= Config.num_request_before_selecting_next_server
  @will_select_next_server = false

  Config.thin_config['servers'].times do |i|
    next_server_index = (server_index + i + 1) % num_servers
    file = socket_file next_server_index
    next unless File.exists?(file)
    debug "selected #{next_server_index}"
    write_gc_yaml next_server_index, SELECTED
    return true
  end
  false
end
warn(msg) click to toggle source
# File lib/garbageman/collector.rb, line 157
def warn(msg)
  logger.warn msg
end

Private Instance Methods

busy?() click to toggle source
# File lib/garbageman/collector.rb, line 211
def busy?
  fiber_poll && fiber_poll.busy_fibers.size > 0
end
can_collect?() click to toggle source

no traffic and we've been selected by health check

# File lib/garbageman/collector.rb, line 224
def can_collect?
  @will_collect && ! busy? && Thin::Backends::Base.num_connections == 0
end
can_disable?() click to toggle source
# File lib/garbageman/collector.rb, line 255
def can_disable?
  uses_sockets? &&
    not_queuing? &&
    not_forcing_gc? &&
    enough_running_servers?
end
current_server?() click to toggle source
# File lib/garbageman/collector.rb, line 233
def current_server?
  config = Config.gc_config
  config && config['gc'] && config['gc']['server'] && config['gc']['server'] == server_index
end
enough_running_servers?() click to toggle source

make sure there are 3 or more servers running before disabling gc

# File lib/garbageman/collector.rb, line 271
def enough_running_servers?
  num_servers >= Config.min_servers_to_disable_gc && num_running_servers >= Config.min_servers_to_disable_gc
end
forcing_gc?() click to toggle source
# File lib/garbageman/collector.rb, line 243
def forcing_gc?
  File.exists?(Config.enable_gc_file)
end
not_forcing_gc?() click to toggle source
# File lib/garbageman/collector.rb, line 247
def not_forcing_gc?
  ! forcing_gc?
end
not_queuing?() click to toggle source
# File lib/garbageman/collector.rb, line 219
def not_queuing?
  ! queuing?
end
num_running_servers() click to toggle source
# File lib/garbageman/collector.rb, line 262
def num_running_servers
  count = 0
  Config.thin_config['servers'].times do |i|
    count += 1 if i == server_index || File.exists?(socket_file(i))
  end
  count
end
num_servers() click to toggle source
# File lib/garbageman/collector.rb, line 183
def num_servers
  Config.thin_config['servers']
end
queuing?() click to toggle source
# File lib/garbageman/collector.rb, line 215
def queuing?
  fiber_poll && fiber_poll.queue.size > 0
end
reset() click to toggle source
# File lib/garbageman/collector.rb, line 203
def reset
  @request_count = 0
  @will_collect = false
  @will_select_next_server = false
  @selected_to_collect_at = nil
  @can_collect_at = nil
end
select_next_server?() click to toggle source
# File lib/garbageman/collector.rb, line 238
def select_next_server?
  config = Config.gc_config
  config && config['gc'] && config['gc']['server'] && config['gc']['server'] == server_index && config['gc']['status'] == 'next_server'
end
server_index() click to toggle source
# File lib/garbageman/collector.rb, line 179
def server_index
  Thin::Backends::Base.server_index
end
should_collect?() click to toggle source

if the request count is high enough and it is our turn

# File lib/garbageman/collector.rb, line 229
def should_collect?
  @will_collect = (@request_count >= Config.num_request_before_collecting && current_server?)
end
socket_file(index) click to toggle source
# File lib/garbageman/collector.rb, line 275
def socket_file(index)
  Config.thin_config['socket'].sub '.sock', ".#{index}.sock"
end
uses_sockets?() click to toggle source
# File lib/garbageman/collector.rb, line 251
def uses_sockets?
  Config.thin_config.has_key?('socket')
end
waited_too_long_for_connections_to_drain?() click to toggle source
# File lib/garbageman/collector.rb, line 284
def waited_too_long_for_connections_to_drain?
  @selected_to_collect_at && (Time.now - @selected_to_collect_at) >= Config.max_connection_drain_time
end
waited_too_long_to_gc?() click to toggle source
# File lib/garbageman/collector.rb, line 279
def waited_too_long_to_gc?
  return false unless @last_gc_finished_at
  (Time.now - @last_gc_finished_at) >= Config.max_time_without_gc
end
write_gc_yaml(index, status) click to toggle source
# File lib/garbageman/collector.rb, line 188
def write_gc_yaml(index, status)
  config = {'gc' => {
              'server' => index,
              'status' => status,
              'request_count' => @request_count,
              'will_collect' => @will_collect,
              'will_select_next_server' => @will_select_next_server,
              'selected_to_collect_at' => @selected_to_collect_at.to_s
            }
           }
  File.open(Config.gc_yaml_tmp_file, 'w') { |f| f.write config.to_yaml }
  # atomic write
  FileUtils.mv Config.gc_yaml_tmp_file, Config.gc_yaml_file, WRITE_MOVE_OPTIONS
end