Skip to content

Commit 732f223

Browse files
authored
Merge pull request rmosolgo#4443 from rmosolgo/compare-by-identity-for-caches
Use compare_by_identity on some runtime caches
2 parents 054206d + 3d4cb69 commit 732f223

File tree

14 files changed

+62
-28
lines changed

14 files changed

+62
-28
lines changed

lib/graphql/execution/interpreter/arguments_cache.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def initialize(query)
1111
h[ast_node] = Hash.new do |h2, arg_owner|
1212
h2[arg_owner] = Hash.new do |h3, parent_object|
1313
dataload_for(ast_node, arg_owner, parent_object) do |kwarg_arguments|
14-
h3[parent_object] = @query.schema.after_lazy(kwarg_arguments) do |resolved_args|
14+
h3[parent_object] = @query.after_lazy(kwarg_arguments) do |resolved_args|
1515
h3[parent_object] = resolved_args
1616
end
1717
end

lib/graphql/execution/interpreter/runtime.rb

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,11 @@ def initialize(query:, lazies_at_depth:)
223223
# Which assumes that MyObject.get_field("myField") will return the same field
224224
# during the lifetime of a query
225225
@fields_cache = Hash.new { |h, k| h[k] = {} }
226+
# this can by by-identity since owners are the same object, but not the sub-hash, which uses strings.
227+
@fields_cache.compare_by_identity
226228
# { Class => Boolean }
227229
@lazy_cache = {}
230+
@lazy_cache.compare_by_identity
228231
end
229232

230233
def final_result
@@ -472,7 +475,7 @@ def evaluate_selection(result_name, field_ast_nodes_or_ast_node, owner_object, o
472475
end
473476

474477
def evaluate_selection_with_args(arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type, return_type_non_null) # rubocop:disable Metrics/ParameterLists
475-
after_lazy(arguments, owner: owner_type, field: field_defn, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result) do |resolved_arguments|
478+
after_lazy(arguments, field: field_defn, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result) do |resolved_arguments|
476479
if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
477480
continue_value(resolved_arguments, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
478481
next
@@ -562,7 +565,7 @@ def evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_argu
562565
ex_err
563566
end
564567
end
565-
after_lazy(app_result, owner: owner_type, field: field_defn, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result) do |inner_result|
568+
after_lazy(app_result, field: field_defn, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result) do |inner_result|
566569
continue_value = continue_value(inner_result, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
567570
if HALT != continue_value
568571
continue_field(continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result)
@@ -765,7 +768,7 @@ def continue_field(value, owner_type, field, current_type, ast_node, next_select
765768
r
766769
when "UNION", "INTERFACE"
767770
resolved_type_or_lazy = resolve_type(current_type, value)
768-
after_lazy(resolved_type_or_lazy, owner: current_type, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |resolved_type_result|
771+
after_lazy(resolved_type_or_lazy, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |resolved_type_result|
769772
if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2
770773
resolved_type, resolved_value = resolved_type_result
771774
else
@@ -791,7 +794,7 @@ def continue_field(value, owner_type, field, current_type, ast_node, next_select
791794
rescue GraphQL::ExecutionError => err
792795
err
793796
end
794-
after_lazy(object_proxy, owner: current_type, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |inner_object|
797+
after_lazy(object_proxy, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |inner_object|
795798
continue_value = continue_value(inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
796799
if HALT != continue_value
797800
response_hash = GraphQLResultHash.new(result_name, selection_result)
@@ -889,7 +892,7 @@ def resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, fi
889892
st.current_result = response_list
890893
call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
891894
# This will update `response_list` with the lazy
892-
after_lazy(inner_value, owner: inner_type, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list) do |inner_inner_value|
895+
after_lazy(inner_value, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list) do |inner_inner_value|
893896
continue_value = continue_value(inner_inner_value, owner_type, field, inner_type_non_null, ast_node, this_idx, response_list)
894897
if HALT != continue_value
895898
continue_field(continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
@@ -952,12 +955,24 @@ def get_current_runtime_state
952955
current_state[@query] ||= CurrentState.new
953956
end
954957

958+
def minimal_after_lazy(value, &block)
959+
if lazy?(value)
960+
GraphQL::Execution::Lazy.new do
961+
result = @schema.sync_lazy(value)
962+
# The returned result might also be lazy, so check it, too
963+
minimal_after_lazy(result, &block)
964+
end
965+
else
966+
yield(value)
967+
end
968+
end
969+
955970
# @param obj [Object] Some user-returned value that may want to be batched
956971
# @param field [GraphQL::Schema::Field]
957972
# @param eager [Boolean] Set to `true` for mutation root fields only
958973
# @param trace [Boolean] If `false`, don't wrap this with field tracing
959974
# @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
960-
def after_lazy(lazy_obj, owner:, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
975+
def after_lazy(lazy_obj, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
961976
if lazy?(lazy_obj)
962977
orig_result = result
963978
lazy = GraphQL::Execution::Lazy.new(field: field) do
@@ -1048,9 +1063,12 @@ def authorized_new(type, value, context)
10481063
end
10491064

10501065
def lazy?(object)
1051-
@lazy_cache.fetch(object.class) {
1052-
@lazy_cache[object.class] = @schema.lazy?(object)
1053-
}
1066+
obj_class = object.class
1067+
is_lazy = @lazy_cache[obj_class]
1068+
if is_lazy.nil?
1069+
is_lazy = @lazy_cache[obj_class] = @schema.lazy?(object)
1070+
end
1071+
is_lazy
10541072
end
10551073
end
10561074
end

lib/graphql/execution/lookahead.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def arguments
5555
@arguments
5656
else
5757
@arguments = if @field
58-
@query.schema.after_lazy(@query.arguments_for(@ast_nodes.first, @field)) do |args|
58+
@query.after_lazy(@query.arguments_for(@ast_nodes.first, @field)) do |args|
5959
args.is_a?(Execution::Interpreter::Arguments) ? args.keyword_arguments : args
6060
end
6161
else

lib/graphql/query.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,18 @@ def handle_or_reraise(err)
361361
schema.handle_or_reraise(context, err)
362362
end
363363

364+
def after_lazy(value, &block)
365+
if !defined?(@runtime_instance)
366+
@runtime_instance = context.namespace(:interpreter_runtime)[:runtime]
367+
end
368+
369+
if @runtime_instance
370+
@runtime_instance.minimal_after_lazy(value, &block)
371+
else
372+
@schema.after_lazy(value, &block)
373+
end
374+
end
375+
364376
private
365377

366378
def find_operation(operations, operation_name)

lib/graphql/query/null_context.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ def visible_type_membership?(tm, ctx); true; end
1212
end
1313

1414
class NullQuery
15+
def after_lazy(value)
16+
yield(value)
17+
end
1518
end
1619

1720
class NullSchema < GraphQL::Schema

lib/graphql/schema/argument.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ def coerce_into_values(parent_object, values, context, argument_values)
264264

265265
# If this isn't lazy, then the block returns eagerly and assigns the result here
266266
# If it _is_ lazy, then we write the lazy to the hash, then update it later
267-
argument_values[arg_key] = context.schema.after_lazy(coerced_value) do |resolved_coerced_value|
267+
argument_values[arg_key] = context.query.after_lazy(coerced_value) do |resolved_coerced_value|
268268
owner.validate_directive_argument(self, resolved_coerced_value)
269269
prepared_value = begin
270270
prepare_value(parent_object, resolved_coerced_value, context: context)
@@ -281,7 +281,7 @@ def coerce_into_values(parent_object, values, context, argument_values)
281281
end
282282

283283
maybe_loaded_value = loaded_value || prepared_value
284-
context.schema.after_lazy(maybe_loaded_value) do |resolved_loaded_value|
284+
context.query.after_lazy(maybe_loaded_value) do |resolved_loaded_value|
285285
# TODO code smell to access such a deeply-nested constant in a distant module
286286
argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new(
287287
value: resolved_loaded_value,
@@ -303,7 +303,7 @@ def load_and_authorize_value(load_method_owner, coerced_value, context)
303303
else
304304
load_method_owner.public_send(arg_load_method, coerced_value)
305305
end
306-
context.schema.after_lazy(custom_loaded_value) do |custom_value|
306+
context.query.after_lazy(custom_loaded_value) do |custom_value|
307307
if loads
308308
if type.list?
309309
loaded_values = custom_value.each_with_index.map { |custom_val, idx|

lib/graphql/schema/field.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -661,7 +661,7 @@ def resolve(object, args, query_ctx)
661661

662662
Schema::Validator.validate!(validators, application_object, query_ctx, args)
663663

664-
query_ctx.schema.after_lazy(self.authorized?(application_object, args, query_ctx)) do |is_authorized|
664+
query_ctx.query.after_lazy(self.authorized?(application_object, args, query_ctx)) do |is_authorized|
665665
if is_authorized
666666
with_extensions(object, args, query_ctx) do |obj, ruby_kwargs|
667667
method_args = ruby_kwargs
@@ -833,7 +833,7 @@ def with_extensions(obj, args, ctx)
833833
extended_args = extended[:args]
834834
memos = extended[:memos] || EMPTY_HASH
835835

836-
ctx.schema.after_lazy(value) do |resolved_value|
836+
ctx.query.after_lazy(value) do |resolved_value|
837837
idx = 0
838838
@extensions.each do |ext|
839839
memo = memos[idx]

lib/graphql/schema/field/connection_extension.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def after_resolve(value:, object:, arguments:, context:, memo:)
2626
# rename some inputs to avoid conflicts inside the block
2727
maybe_lazy = value
2828
value = nil
29-
context.schema.after_lazy(maybe_lazy) do |resolved_value|
29+
context.query.after_lazy(maybe_lazy) do |resolved_value|
3030
value = resolved_value
3131
if value.is_a? GraphQL::ExecutionError
3232
# This isn't even going to work because context doesn't have ast_node anymore

lib/graphql/schema/input_object.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ def coerce_input(value, ctx)
211211

212212
arguments = coerce_arguments(nil, value, ctx)
213213

214-
ctx.schema.after_lazy(arguments) do |resolved_arguments|
214+
ctx.query.after_lazy(arguments) do |resolved_arguments|
215215
if resolved_arguments.is_a?(GraphQL::Error)
216216
raise resolved_arguments
217217
else

lib/graphql/schema/member/has_arguments.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def argument(*args, **kwargs, &block)
5151
class_eval <<-RUBY, __FILE__, __LINE__ + 1
5252
def #{method_owner}load_#{arg_defn.keyword}(values, context = nil)
5353
argument = get_argument("#{arg_defn.graphql_name}")
54-
(context || self.context).schema.after_lazy(values) do |values2|
54+
(context || self.context).query.after_lazy(values) do |values2|
5555
GraphQL::Execution::Lazy.all(values2.map { |value| load_application_object(argument, value, context || self.context) })
5656
end
5757
end
@@ -363,15 +363,15 @@ def load_and_authorize_application_object(argument, id, context)
363363
end
364364

365365
def authorize_application_object(argument, id, context, loaded_application_object)
366-
context.schema.after_lazy(loaded_application_object) do |application_object|
366+
context.query.after_lazy(loaded_application_object) do |application_object|
367367
if application_object.nil?
368368
err = GraphQL::LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object)
369369
load_application_object_failed(err)
370370
end
371371
# Double-check that the located object is actually of this type
372372
# (Don't want to allow arbitrary access to objects this way)
373373
maybe_lazy_resolve_type = context.schema.resolve_type(argument.loads, application_object, context)
374-
context.schema.after_lazy(maybe_lazy_resolve_type) do |resolve_type_result|
374+
context.query.after_lazy(maybe_lazy_resolve_type) do |resolve_type_result|
375375
if resolve_type_result.is_a?(Array) && resolve_type_result.size == 2
376376
application_object_type, application_object = resolve_type_result
377377
else
@@ -386,7 +386,7 @@ def authorize_application_object(argument, id, context, loaded_application_objec
386386
# This object was loaded successfully
387387
# and resolved to the right type,
388388
# now apply the `.authorized?` class method if there is one
389-
context.schema.after_lazy(application_object_type.authorized?(application_object, context)) do |authed|
389+
context.query.after_lazy(application_object_type.authorized?(application_object, context)) do |authed|
390390
if authed
391391
application_object
392392
else

lib/graphql/schema/object.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def authorized_new(object, context)
6868
maybe_lazy_auth_val
6969
end
7070

71-
context.schema.after_lazy(auth_val) do |is_authorized|
71+
context.query.after_lazy(auth_val) do |is_authorized|
7272
if is_authorized
7373
self.new(object, context)
7474
else

lib/graphql/schema/relay_classic_mutation.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def resolve_with_support(**inputs)
6060
super()
6161
end
6262

63-
context.schema.after_lazy(return_value) do |return_hash|
63+
context.query.after_lazy(return_value) do |return_hash|
6464
# It might be an error
6565
if return_hash.is_a?(Hash)
6666
return_hash[:client_mutation_id] = client_mutation_id

lib/graphql/schema/resolver.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def resolve_with_support(**args)
7070
else
7171
ready?
7272
end
73-
context.schema.after_lazy(ready_val) do |is_ready, ready_early_return|
73+
context.query.after_lazy(ready_val) do |is_ready, ready_early_return|
7474
if ready_early_return
7575
if is_ready != false
7676
raise "Unexpected result from #ready? (expected `true`, `false` or `[false, {...}]`): [#{is_ready.inspect}, #{ready_early_return.inspect}]"
@@ -81,7 +81,7 @@ def resolve_with_support(**args)
8181
# Then call each prepare hook, which may return a different value
8282
# for that argument, or may return a lazy object
8383
load_arguments_val = load_arguments(args)
84-
context.schema.after_lazy(load_arguments_val) do |loaded_args|
84+
context.query.after_lazy(load_arguments_val) do |loaded_args|
8585
@prepared_arguments = loaded_args
8686
Schema::Validator.validate!(self.class.validators, object, context, loaded_args, as: @field)
8787
# Then call `authorized?`, which may raise or may return a lazy object
@@ -90,7 +90,7 @@ def resolve_with_support(**args)
9090
else
9191
authorized?
9292
end
93-
context.schema.after_lazy(authorized_val) do |(authorized_result, early_return)|
93+
context.query.after_lazy(authorized_val) do |(authorized_result, early_return)|
9494
# If the `authorized?` returned two values, `false, early_return`,
9595
# then use the early return value instead of continuing
9696
if early_return
@@ -185,7 +185,7 @@ def load_arguments(args)
185185
if arg_defn
186186
prepped_value = prepared_args[key] = arg_defn.load_and_authorize_value(self, value, context)
187187
if context.schema.lazy?(prepped_value)
188-
prepare_lazies << context.schema.after_lazy(prepped_value) do |finished_prepped_value|
188+
prepare_lazies << context.query.after_lazy(prepped_value) do |finished_prepped_value|
189189
prepared_args[key] = finished_prepped_value
190190
end
191191
end

lib/graphql/schema/warden.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ def initialize(filter, context:, schema:)
9898
@subscription = @schema.subscription
9999
@context = context
100100
@visibility_cache = read_through { |m| filter.call(m, context) }
101+
@visibility_cache.compare_by_identity
101102
# Initialize all ivars to improve object shape consistency:
102103
@types = @visible_types = @reachable_types = @visible_parent_fields =
103104
@visible_possible_types = @visible_fields = @visible_arguments = @visible_enum_arrays =

0 commit comments

Comments
 (0)