Skip to content

Memory leak under Rack::Test #665

Description

@mattsnyder

We've been writing our API using Grape and the DSL is great! However as we write more endpoints and test them with RSpec we've seen memory utilization skyrocket! Upwards of 8+G to test ~7 endpoints!

I wrote a spec file to try and isolate the problem and noticed three classes that with every call to an endpoint leave behind numerous objects that cannot be garbage collected.

Here is the spec I wrote that shows the increase in references for these classes:
Grape::Route, Grape::Endpoint,Grape::Util::HashStack,Regexp

And after 3 calls to the endpoint, I saw the reference counts go from

"Grape::Route 25 references"
"Grape::Endpoint 36 references"
"Grape::Util::HashStack 50 references"
"Regexp 4386 references"

to

"Grape::Route 47 references"
"Grape::Endpoint 47 references"
"Grape::Util::HashStack 60 references"
"Regexp 4687 references"
require 'spec_helper'

class GcApi < Grape::API
  prefix 'gcapi'
  format :json

  resources :testing do
    get do
      true
    end

    put do
      false
    end
  end
end

UserTesting::Orders::Application.routes.draw do
  mount GcApi => '/'
end


describe GcApi do
 include Rack::Test::Methods
 def app
   subject
 end

  def log(object, message)
    header = "*"*20 + message + "*"*20
    p header
    p object
    p "*"*header.length
  end

  def dumpit(file)
    counts = Hash.new{ 0 }
    ObjectSpace.each_object do |o|
      counts[o.class] += 1
    end
    file = File.open(file, 'w')
    counts.each do |key, val|
      file.puts(key.to_s + "=" + val.to_s)
    end
    file.close
  end

  def show_references(klass)
    count = 0
    ObjectSpace.each_object(klass) do |o|
      count += 1
    end
    p klass.to_s + " #{count} references"
  end

  def accept_header
    { 'HTTP_ACCEPT' => "application/json" }
  end

  before(:all) do
    GC.start
    log ObjectSpace.count_objects, "BEFOREALL"
    # dumpit("before.txt")
    show_references(Grape::Route)
    show_references(Grape::Endpoint)
    show_references(Grape::Util::HashStack)
    show_references(Regexp)
  end

  after(:all) do
    show_references(Grape::Route)
    show_references(Grape::Endpoint)
    show_references(Grape::Util::HashStack)
    show_references(Regexp)
    GC.start
    log ObjectSpace.count_objects, "AFTERALL"
    # dumpit("after.txt")
  end

  it "should run quickly" do #1635
    get "/gcapi/testing.json", nil, {}
    last_response.body.should == "true"
  end

  it "should run longer" do #1635
    get "/gcapi/testing.json", nil, {}
    last_response.should be_empty # false assumption
  end

  it "should run quickly or maybe not" do #409
    put "/gcapi/testing.json", nil, {}
    last_response.body.should == "false"
  end

  # it "should be able to GC" do
  #   GC.start
  #   sleep 5
  #   log ObjectSpace.count_objects, "BEFORESIMPLETEST"
  #   (1+1).should eql(2)
  #   GC.start
  #   sleep 5
  #   log ObjectSpace.count_objects, "AFTERSIMPLETEST"
  # end
end

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions