-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix retaining setup blocks when remounting APIs #2102
Conversation
I'm having hard time demonstrating the leak myself, with or without remounting. Help me modify /~https://github.com/dblock/grape/blob/leak-2102/benchmark/remounting.rb please? |
Working on it! We definitely saw memory usage normalize when I deployed this patch to production but it will be good to demonstrate in a test like what you are doing. |
I changed the script to this: $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
require 'grape'
require 'benchmark/memory'
class VotingApi < Grape::API
logger Logger.new(STDOUT)
helpers do
def logger
VotingApi.logger
end
end
namespace 'votes' do
get do
logger
end
end
end
class PostApi < Grape::API
mount VotingApi
end
class CommentAPI < Grape::API
mount VotingApi
end
env = Rack::MockRequest.env_for('/votes', method: 'GET')
PostApi.call(env)
CommentAPI.call(env)
setup_ar = VotingApi.instance_variable_get(:@setup).to_a
setup_set = VotingApi.instance_variable_get(:@setup).to_set
Benchmark.memory do |api|
calls = 1000
api.report('using Array') do
VotingApi.instance_variable_set(:@setup, setup_ar)
calls.times { PostApi.call(env) }
end
api.report('using Set') do
VotingApi.instance_variable_set(:@setup, setup_set)
calls.times { PostApi.call(env) }
end
api.compare!
end And get the following output:
I think the key thing to pay attention to is the retained objects for each run. We are still creating a hash on every call to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm good with merging this after:
- Squash your changes, see my nitpicks below.
- Merge /~https://github.com/dblock/grape/tree/leak-2102 on top into this PR, let's keep the benchmark.
Benchmark retaining setup objects when remounting, fixed in ruby-grape#2102.
dc8815a
to
416a7e1
Compare
@dblock Thanks for those suggestions. I've made them and squashed the commits. Let me know if you need anything else 👍 |
We've had some breakage due to this change (i.e. everything works as expected in d40d697) We use a simple API builder component to setup our mundane CRUD end-points. Code example that breaks after this merge: module PortalMounts
class MyAPIBuilder < Grape::API
def self.build_api!
api_self = self
get 'works' do
'this also works!'
end
resource "things" do
get 'also_works' do
'this also works!'
end
api_self.add_endpoint
end
end
private
def self.add_endpoint
get 'not_work' do
'broken endpoint!'
end
end
end
class MyAPI < MyAPIBuilder
build_api!
end
end
Workaround is found by passing the 'resource self' explicitly, like this: class MyAPIBuilder < Grape::API
def self.build_api!
api_self = self
get 'works' do
'this also works!'
end
resource "things" do
get 'also_works' do
'this also works!'
end
api_self.add_endpoint self
end
end
private
def self.add_endpoint resource_self
resource_self.get 'not_work' do
'broken endpoint!'
end
end
end and calling grape methods on I can't say if this is behavior is desired or a regression? |
i don't think anything that used to work should be desired ;) so let's call it a regression PR a spec and let's see how bad the fix could be? |
This is a simple fix for #2071.
Grape::API
class methods that are not referenced in theNON_OVERRIDABLE
constant are overridden to calladd_setup
when invoked. Unfortunately there is a memory leak withadd_setup
where a hash describing the class method call is added to@setup
on every call (not just the first). This PR changes the data structure of@setup
from anArray
to aSet
therefore only allowing a single hash per combination of method name and arguments.This bug is causing pain for us as we call
Grape::API.logger
in every API request 📈 In our caseGrape::API.logger
with no arguments is sorted by moving to aSet
, but I am not too familiar with the inner workings of Grape so I will ask for some guidance of whether this doesn't meet a particular edge case or breaks some other functionality. Also the spec I have added was for our specific issue, so I ask for guidance if you guys would prefer a more generic spec somewhere else.