Skip to content

Commit 47ed266

Browse files
committed
Lazy initialization of Endpoint and trick with helpers.
Now I'm working on reducing calls to `extend` in `Grape` because they invalidate Ruby's method cache slowing everything down. I applied a *clever* trick with subclassing `Endpoint` and including helpers into this subclass. It also forced me to fix a thread safety issue reported by @etehtsea here: #1201 (comment) because calling `include` twice would be really inconsistent. Also I'm sorry, 06930da does not actually fix anything, I overlooked it :)
1 parent 06930da commit 47ed266

File tree

5 files changed

+54
-28
lines changed

5 files changed

+54
-28
lines changed

.rubocop_todo.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Metrics/BlockNesting:
2222
# Offense count: 5
2323
# Configuration parameters: CountComments.
2424
Metrics/ClassLength:
25-
Max: 257
25+
Max: 300
2626

2727
# Offense count: 23
2828
Metrics/CyclomaticComplexity:

lib/grape/dsl/inside_route.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class MethodNotYetAvailable < StandardError; end
1818
# filter +type+.
1919
def self.post_filter_methods(type)
2020
@post_filter_modules ||= { before: PostBeforeFilter }
21-
@post_filter_modules[type] || Module.new
21+
@post_filter_modules[type]
2222
end
2323

2424
# Methods which should not be available in filters until the before filter

lib/grape/endpoint.rb

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ class Endpoint
1212
include Grape::DSL::InsideRoute
1313

1414
class << self
15+
def new(*args, &block)
16+
if self == Endpoint
17+
Class.new(Endpoint).new(*args, &block)
18+
else
19+
super
20+
end
21+
end
22+
1523
def before_each(new_setup = false, &block)
1624
@before_each ||= []
1725
if new_setup == false
@@ -25,6 +33,13 @@ def before_each(new_setup = false, &block)
2533
end
2634
end
2735

36+
def run_before_each(endpoint)
37+
superclass.run_before_each(endpoint) unless self == Endpoint
38+
before_each.each do |blk|
39+
blk.call(endpoint) if blk.respond_to? :call
40+
end
41+
end
42+
2843
# @api private
2944
#
3045
# Create an UnboundMethod that is appropriate for executing an endpoint
@@ -75,6 +90,8 @@ def initialize(new_settings, options = {}, &block)
7590
@options[:method] = Array(options[:method])
7691
@options[:route_options] ||= {}
7792

93+
@lazy_initialize_lock = Mutex.new
94+
7895
return unless block_given?
7996

8097
@source = block
@@ -188,14 +205,11 @@ def compile_path(prepared_path, anchor = true, requirements = {})
188205
end
189206

190207
def call(env)
191-
build_app
192-
build_helpers
208+
lazy_initialize!
193209
dup.call!(env)
194210
end
195211

196212
def call!(env)
197-
extend helpers
198-
199213
env[Grape::Env::API_ENDPOINT] = self
200214
@env = env
201215
@app.call(env)
@@ -223,9 +237,7 @@ def run
223237

224238
cookies.read(@request)
225239

226-
self.class.before_each.each do |blk|
227-
blk.call(self) if blk.respond_to?(:call)
228-
end if self.class.before_each.any?
240+
self.class.run_before_each(self)
229241

230242
run_filters befores, :before
231243

@@ -258,10 +270,6 @@ def run
258270
end
259271
end
260272

261-
def build_app
262-
@app ||= options[:app] || build_stack
263-
end
264-
265273
def build_stack
266274
b = Rack::Builder.new
267275

@@ -317,8 +325,25 @@ def build_helpers
317325
end
318326
end
319327

328+
private :build_stack, :build_helpers
329+
320330
def helpers
321-
@helpers ||= build_helpers
331+
lazy_initialize! && @helpers
332+
end
333+
334+
def lazy_initialize!
335+
return true if @lazy_initialized
336+
337+
@lazy_initialize_lock.synchronize do
338+
return true if @lazy_initialized
339+
340+
@app = options[:app] || build_stack
341+
@helpers = build_helpers.tap do |mod|
342+
self.class.send(:include, mod)
343+
end
344+
345+
@lazy_initialized = true
346+
end
322347
end
323348

324349
def run_filters(filters, type = :other)
@@ -327,7 +352,8 @@ def run_filters(filters, type = :other)
327352
instance_eval(&filter)
328353
end
329354
end
330-
extend DSL::InsideRoute.post_filter_methods(type)
355+
post_extension = DSL::InsideRoute.post_filter_methods(type)
356+
extend post_extension if post_extension
331357
end
332358

333359
def befores

spec/grape/api_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1088,7 +1088,7 @@ def before
10881088

10891089
subject.get(:hello) { 'Hello, world.' }
10901090
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
1091-
expect(basic_auth_context).to be_an_instance_of(Grape::Endpoint)
1091+
expect(basic_auth_context).to be_a_kind_of(Grape::Endpoint)
10921092
end
10931093

10941094
it 'has access to helper methods' do

spec/grape/endpoint_spec.rb

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,38 +1028,38 @@ def memoized
10281028

10291029
# In order that the events finalized (time each block ended)
10301030
expect(@events).to contain_exactly(
1031-
have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: an_instance_of(Grape::Endpoint),
1031+
have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(Grape::Endpoint),
10321032
filters: a_collection_containing_exactly(an_instance_of(Proc)),
10331033
type: :before }),
1034-
have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: an_instance_of(Grape::Endpoint),
1034+
have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(Grape::Endpoint),
10351035
filters: [],
10361036
type: :before_validation }),
1037-
have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: an_instance_of(Grape::Endpoint),
1037+
have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(Grape::Endpoint),
10381038
filters: [],
10391039
type: :after_validation }),
1040-
have_attributes(name: 'endpoint_render.grape', payload: { endpoint: an_instance_of(Grape::Endpoint) }),
1041-
have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: an_instance_of(Grape::Endpoint),
1040+
have_attributes(name: 'endpoint_render.grape', payload: { endpoint: a_kind_of(Grape::Endpoint) }),
1041+
have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(Grape::Endpoint),
10421042
filters: [],
10431043
type: :after }),
1044-
have_attributes(name: 'endpoint_run.grape', payload: { endpoint: an_instance_of(Grape::Endpoint),
1044+
have_attributes(name: 'endpoint_run.grape', payload: { endpoint: a_kind_of(Grape::Endpoint),
10451045
env: an_instance_of(Hash) })
10461046
)
10471047

10481048
# In order that events were initialized
10491049
expect(@events.sort_by(&:time)).to contain_exactly(
1050-
have_attributes(name: 'endpoint_run.grape', payload: { endpoint: an_instance_of(Grape::Endpoint),
1050+
have_attributes(name: 'endpoint_run.grape', payload: { endpoint: a_kind_of(Grape::Endpoint),
10511051
env: an_instance_of(Hash) }),
1052-
have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: an_instance_of(Grape::Endpoint),
1052+
have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(Grape::Endpoint),
10531053
filters: a_collection_containing_exactly(an_instance_of(Proc)),
10541054
type: :before }),
1055-
have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: an_instance_of(Grape::Endpoint),
1055+
have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(Grape::Endpoint),
10561056
filters: [],
10571057
type: :before_validation }),
1058-
have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: an_instance_of(Grape::Endpoint),
1058+
have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(Grape::Endpoint),
10591059
filters: [],
10601060
type: :after_validation }),
1061-
have_attributes(name: 'endpoint_render.grape', payload: { endpoint: an_instance_of(Grape::Endpoint) }),
1062-
have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: an_instance_of(Grape::Endpoint),
1061+
have_attributes(name: 'endpoint_render.grape', payload: { endpoint: a_kind_of(Grape::Endpoint) }),
1062+
have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(Grape::Endpoint),
10631063
filters: [],
10641064
type: :after })
10651065
)

0 commit comments

Comments
 (0)