class ASF::AutoGC

‘use’ the following class in config.ru to automatically run Garbage Collection every ‘n’ requests, or ‘m’ minutes.

This tries to run garbage collection “out of band” (i.e., between requests), and when other requests are active (which can happen with threaded servers like Puma).

In addition to keeping memory usage bounded, this keeps the LDAP cache from going stale.

Public Class Methods

new(app, frequency=100, minutes=15) click to toggle source

Define the frequency with which GC should be run (as in every ‘n’ requests), and the maximum number of idle minutes between GC runs. This class also will make use of PhusionPassenger’s out of band GC, if available.

# File lib/whimsy/asf/rack.rb, line 106
def initialize(app, frequency=100, minutes=15)
  @app = app
  @frequency = frequency
  @request_count = 0
  @queue = Queue.new
  @mutex = Mutex.new

  if defined?(PhusionPassenger)
    # https://github.com/suyccom/yousell/blob/master/config.ru
    # https://www.phusionpassenger.com/library/indepth/ruby/out_of_band_work.html
    if PhusionPassenger.respond_to?(:require_passenger_lib)
      PhusionPassenger.require_passenger_lib 'rack/out_of_band_gc'
    else
      # Phusion Passenger < 4.0.33
      require 'phusion_passenger/rack/out_of_band_gc'
    end

    @passenger = PhusionPassenger::Rack::OutOfBandGc.new(app, frequency)
  end

  Thread.kill(@@background) if @@background

  if minutes
    # divide minutes by frequency and use the result to determine the
    # time between simulated requests
    @@background = Thread.new do
      seconds = minutes * 60.0 / frequency
      loop do
        sleep seconds
        maybe_perform_gc
      end
    end
  end
end

Public Instance Methods

call(env) click to toggle source

Rack middleware used to push an object onto the queue prior to the request (this stops AutoGC from running during the request), and popping it afterward the request completes. Also will spin off a thread to run GC after the reply completes (using rack.after_reply if available), otherwise using a standard Thread.

# File lib/whimsy/asf/rack.rb, line 146
def call(env)
  @queue.push 1

  if @passenger
    @passenger.call(env)
  else
    # https://github.com/puma/puma/issues/450
    status, header, body = @app.call(env)

    if (ary = env['rack.after_reply']) # this is intended to be assignment, see #108
      ary << lambda {maybe_perform_gc}
    else
      Thread.new {sleep 0.1; maybe_perform_gc}
    end

    [status, header, body]
  end
ensure
  @queue.pop
end
maybe_perform_gc() click to toggle source

Run GC when no requests are active and after every @frequency events.

# File lib/whimsy/asf/rack.rb, line 169
def maybe_perform_gc
  @mutex.synchronize do
    @request_count += 1
    if @queue.empty? and @request_count >= @frequency
      @request_count = 0
      disabled = GC.enable
      GC.start
      GC.disable if disabled
    end
  end
end