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
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,RegexpAnd after 3 calls to the endpoint, I saw the reference counts go from
to