Skip to content

Commit cb8a6f9

Browse files
authored
Merge pull request #4399 from rmosolgo/simplify-runtime-state
Use a dedicated object for runtime state
2 parents a04428f + 9fea915 commit cb8a6f9

File tree

4 files changed

+87
-73
lines changed

4 files changed

+87
-73
lines changed

lib/graphql/execution/interpreter/runtime.rb

Lines changed: 58 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ class Interpreter
88
#
99
# @api private
1010
class Runtime
11+
class CurrentState
12+
def initialize
13+
@current_object = nil
14+
@current_field = nil
15+
@current_arguments = nil
16+
@current_result_name = nil
17+
@current_result = nil
18+
end
19+
20+
attr_accessor :current_result, :current_result_name,
21+
:current_arguments, :current_field, :current_object
22+
end
1123

1224
module GraphQLResult
1325
def initialize(result_name, parent_result)
@@ -157,15 +169,6 @@ class GraphQLSelectionSet < Hash
157169
# @return [GraphQL::Query::Context]
158170
attr_reader :context
159171

160-
def thread_info
161-
info = Thread.current[:__graphql_runtime_info]
162-
if !info
163-
new_ti = {}
164-
info = Thread.current[:__graphql_runtime_info] = new_ti
165-
end
166-
info
167-
end
168-
169172
def initialize(query:, lazies_at_depth:)
170173
@query = query
171174
@dataloader = query.multiplex.dataloader
@@ -218,7 +221,9 @@ def run_eager
218221
root_operation = query.selected_operation
219222
root_op_type = root_operation.operation_type || "query"
220223
root_type = schema.root_type_for_operation(root_op_type)
221-
set_all_interpreter_context(query.root_value, nil, nil, nil, @response)
224+
st = get_current_runtime_state
225+
st.current_object = query.root_value
226+
st.current_result = @response
222227
object_proxy = authorized_new(root_type, query.root_value, context)
223228
object_proxy = schema.sync_lazy(object_proxy)
224229

@@ -245,7 +250,10 @@ def run_eager
245250
end
246251

247252
@dataloader.append_job {
248-
set_all_interpreter_context(query.root_value, nil, nil, nil, selection_response)
253+
st = get_current_runtime_state
254+
st.current_object = query.root_value
255+
st.current_result = selection_response
256+
249257
call_method_on_directives(:resolve, object_proxy, selections.graphql_directives) do
250258
evaluate_selections(
251259
object_proxy,
@@ -364,7 +372,10 @@ def gather_selections(owner_object, owner_type, selections, selections_to_run =
364372

365373
# @return [void]
366374
def evaluate_selections(owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object) # rubocop:disable Metrics/ParameterLists
367-
set_all_interpreter_context(owner_object, nil, nil, nil, selections_result)
375+
st = get_current_runtime_state
376+
st.current_object = owner_object
377+
st.current_result_name = nil
378+
st.current_result = selections_result
368379

369380
finished_jobs = 0
370381
enqueued_jobs = gathered_selections.size
@@ -422,7 +433,11 @@ def evaluate_selection(result_name, field_ast_nodes_or_ast_node, owner_object, o
422433
(selections_result.graphql_non_null_field_names ||= []).push(result_name)
423434
end
424435
# Set this before calling `run_with_directives`, so that the directive can have the latest path
425-
set_all_interpreter_context(nil, field_defn, nil, result_name, selections_result)
436+
st = get_current_runtime_state
437+
st.current_field = field_defn
438+
st.current_result = selections_result
439+
st.current_result_name = result_name
440+
426441
object = owner_object
427442

428443
if is_introspection
@@ -489,8 +504,12 @@ def evaluate_selection_with_args(arguments, field_defn, ast_node, field_ast_node
489504
resolved_arguments.keyword_arguments
490505
end
491506

492-
set_all_interpreter_context(nil, nil, resolved_arguments, result_name, selection_result)
493-
507+
st = get_current_runtime_state
508+
st.current_field = field_defn
509+
st.current_object = object
510+
st.current_arguments = resolved_arguments
511+
st.current_result_name = result_name
512+
st.current_result = selection_result
494513
# Optimize for the case that field is selected only once
495514
if field_ast_nodes.nil? || field_ast_nodes.size == 1
496515
next_selections = ast_node.selections
@@ -588,11 +607,10 @@ def set_graphql_dead(selection_result)
588607
end
589608

590609
def current_path
591-
ti = thread_info
592-
path = ti &&
593-
(result = ti[:current_result]) &&
594-
(result.path)
595-
if path && (rn = ti[:current_result_name])
610+
st = get_current_runtime_state
611+
result = st.current_result
612+
path = result && result.path
613+
if path && (rn = st.current_result_name)
596614
path = path.dup
597615
path.push(rn)
598616
end
@@ -769,8 +787,13 @@ def continue_field(value, owner_type, field, current_type, ast_node, next_select
769787
this_result = response_hash
770788
final_result = nil
771789
end
772-
# Don't pass `result_name` here because it's already included in the new response hash
773-
set_all_interpreter_context(continue_value, nil, nil, nil, this_result) # reset this mutable state
790+
# reset this mutable state
791+
# Unset `result_name` here because it's already included in the new response hash
792+
st = get_current_runtime_state
793+
st.current_object = continue_value
794+
st.current_result_name = nil
795+
st.current_result = this_result
796+
774797
call_method_on_directives(:resolve, continue_value, selections.graphql_directives) do
775798
evaluate_selections(
776799
continue_value,
@@ -835,7 +858,9 @@ def continue_field(value, owner_type, field, current_type, ast_node, next_select
835858
end
836859

837860
def resolve_list_item(inner_value, inner_type, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type) # rubocop:disable Metrics/ParameterLists
838-
set_all_interpreter_context(nil, nil, nil, this_idx, response_list)
861+
st = get_current_runtime_state
862+
st.current_result_name = this_idx
863+
st.current_result = response_list
839864
call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
840865
# This will update `response_list` with the lazy
841866
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|
@@ -891,21 +916,8 @@ def directives_include?(node, graphql_object, parent_type)
891916
true
892917
end
893918

894-
def set_all_interpreter_context(object, field, arguments, result_name, result)
895-
ti = thread_info
896-
if object
897-
ti[:current_object] = object
898-
end
899-
if field
900-
ti[:current_field] = field
901-
end
902-
if arguments
903-
ti[:current_arguments] = arguments
904-
end
905-
ti[:current_result_name] = result_name
906-
if result
907-
ti[:current_result] = result
908-
end
919+
def get_current_runtime_state
920+
Thread.current[:__graphql_runtime_info] ||= CurrentState.new
909921
end
910922

911923
# @param obj [Object] Some user-returned value that may want to be batched
@@ -917,7 +929,12 @@ def after_lazy(lazy_obj, owner:, field:, owner_object:, arguments:, ast_node:, r
917929
if lazy?(lazy_obj)
918930
orig_result = result
919931
lazy = GraphQL::Execution::Lazy.new(field: field) do
920-
set_all_interpreter_context(owner_object, field, arguments, result_name, orig_result)
932+
st = get_current_runtime_state
933+
st.current_object = owner_object
934+
st.current_field = field
935+
st.current_arguments = arguments
936+
st.current_result_name = result_name
937+
st.current_result = orig_result
921938
# Wrap the execution of _this_ method with tracing,
922939
# but don't wrap the continuation below
923940
inner_obj = begin
@@ -953,7 +970,7 @@ def after_lazy(lazy_obj, owner:, field:, owner_object:, arguments:, ast_node:, r
953970
lazy
954971
end
955972
else
956-
set_all_interpreter_context(owner_object, field, arguments, result_name, result)
973+
# Don't need to reset state here because it _wasn't_ lazy.
957974
yield(lazy_obj)
958975
end
959976
end
@@ -968,13 +985,7 @@ def arguments(graphql_object, arg_owner, ast_node)
968985
end
969986

970987
def delete_all_interpreter_context
971-
if (ti = thread_info)
972-
ti.delete(:current_result)
973-
ti.delete(:current_result_name)
974-
ti.delete(:current_field)
975-
ti.delete(:current_object)
976-
ti.delete(:current_arguments)
977-
end
988+
Thread.current[:__graphql_runtime_info] = nil
978989
end
979990

980991
def resolve_type(type, value)

lib/graphql/query/context.rb

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -226,8 +226,8 @@ def [](key)
226226
if key == :current_path
227227
current_path
228228
else
229-
thread_info = Thread.current[:__graphql_runtime_info]
230-
thread_info && thread_info[key]
229+
(current_runtime_state = Thread.current[:__graphql_runtime_info]) &&
230+
(current_runtime_state.public_send(key))
231231
end
232232
else
233233
# not found
@@ -236,11 +236,11 @@ def [](key)
236236
end
237237

238238
def current_path
239-
thread_info = Thread.current[:__graphql_runtime_info]
240-
path = thread_info &&
241-
(result = thread_info[:current_result]) &&
239+
current_runtime_state = Thread.current[:__graphql_runtime_info]
240+
path = current_runtime_state &&
241+
(result = current_runtime_state.current_result) &&
242242
(result.path)
243-
if path && (rn = thread_info[:current_result_name])
243+
if path && (rn = current_runtime_state.current_result_name)
244244
path = path.dup
245245
path.push(rn)
246246
end
@@ -259,8 +259,8 @@ def delete(key)
259259

260260
def fetch(key, default = UNSPECIFIED_FETCH_DEFAULT)
261261
if RUNTIME_METADATA_KEYS.include?(key)
262-
(thread_info = Thread.current[:__graphql_runtime_info]) &&
263-
thread_info[key]
262+
(runtime = Thread.current[:__graphql_runtime_info]) &&
263+
(runtime.public_send(key))
264264
elsif @scoped_context.key?(key)
265265
scoped_context[key]
266266
elsif @provided_values.key?(key)
@@ -276,8 +276,9 @@ def fetch(key, default = UNSPECIFIED_FETCH_DEFAULT)
276276

277277
def dig(key, *other_keys)
278278
if RUNTIME_METADATA_KEYS.include?(key)
279-
(thread_info = Thread.current[:__graphql_runtime_info]).key?(key) &&
280-
thread_info.dig(key, *other_keys)
279+
(current_runtime_state = Thread.current[:__graphql_runtime_info]) &&
280+
(obj = current_runtime_state.public_send(key)) &&
281+
obj.dig(*other_keys)
281282
elsif @scoped_context.key?(key)
282283
@scoped_context.dig(key, *other_keys)
283284
else

lib/graphql/schema/field.rb

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -608,15 +608,16 @@ def authorized?(object, args, context)
608608
# The resolver _instance_ will check itself during `resolve()`
609609
@resolver_class.authorized?(object, context)
610610
else
611-
if (arg_values = context[:current_arguments])
612-
# ^^ that's provided by the interpreter at runtime, and includes info about whether the default value was used or not.
613-
using_arg_values = true
614-
arg_values = arg_values.argument_values
615-
else
616-
arg_values = args
617-
using_arg_values = false
618-
end
619611
if args.size > 0
612+
if (arg_values = context[:current_arguments])
613+
# ^^ that's provided by the interpreter at runtime, and includes info about whether the default value was used or not.
614+
using_arg_values = true
615+
arg_values = arg_values.argument_values
616+
else
617+
arg_values = args
618+
using_arg_values = false
619+
end
620+
620621
args = context.warden.arguments(self)
621622
args.each do |arg|
622623
arg_key = arg.keyword

spec/graphql/query/context_spec.rb

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def push_query_error
9494

9595
it "allows you to read values of contexts using dig" do
9696
assert_equal(1, context.dig(:a, :b))
97-
Thread.current[:__graphql_runtime_info][:current_arguments] = { c: 1 }
97+
Thread.current[:__graphql_runtime_info] = OpenStruct.new(current_arguments: {c: 1})
9898
assert_equal 1, context.dig(:current_arguments, :c)
9999
end
100100
end
@@ -111,7 +111,7 @@ def push_query_error
111111

112112
it "can override values set by runtime" do
113113
context = GraphQL::Query::Context.new(query: OpenStruct.new(schema: schema), values: {a: {b: 1}}, object: nil)
114-
Thread.current[:__graphql_runtime_info] = { current_object: :runtime_value }
114+
Thread.current[:__graphql_runtime_info] = OpenStruct.new({ current_object: :runtime_value })
115115
assert_equal :runtime_value, context[:current_object]
116116
context[:current_object] = :override_value
117117
assert_equal :override_value, context[:current_object]
@@ -383,8 +383,9 @@ class ContextSchema < GraphQL::Schema
383383

384384
it "always retrieves a scoped context value if set" do
385385
context = GraphQL::Query::Context.new(query: OpenStruct.new(schema: schema), values: nil, object: nil)
386-
runtime_info = Thread.current[:__graphql_runtime_info] ||= {}
387-
runtime_info[:current_result] = OpenStruct.new(path: ["somewhere"])
386+
dummy_runtime = OpenStruct.new(current_result: nil)
387+
Thread.current[:__graphql_runtime_info] = dummy_runtime
388+
dummy_runtime.current_result = OpenStruct.new(path: ["somewhere"])
388389
expected_key = :a
389390
expected_value = :test
390391

@@ -404,7 +405,7 @@ class ContextSchema < GraphQL::Schema
404405
assert_nil(context.fetch(expected_key))
405406
assert_nil(context.dig(expected_key)) if RUBY_VERSION >= '2.3.0'
406407

407-
runtime_info[:current_result] = OpenStruct.new(path: ["something", "new"])
408+
dummy_runtime.current_result = OpenStruct.new(path: ["something", "new"])
408409

409410
assert_equal(expected_value, context[expected_key])
410411
assert_equal({ expected_key => expected_value}, context.to_h)
@@ -413,15 +414,15 @@ class ContextSchema < GraphQL::Schema
413414
assert_equal(expected_value, context.dig(expected_key)) if RUBY_VERSION >= '2.3.0'
414415

415416
# Enter a child field:
416-
runtime_info[:current_result] = OpenStruct.new(path: ["somewhere", "child"])
417+
dummy_runtime.current_result = OpenStruct.new(path: ["somewhere", "child"])
417418
assert_nil(context[expected_key])
418419
assert_equal({ expected_key => nil }, context.to_h)
419420
assert(context.key?(expected_key))
420421
assert_nil(context.fetch(expected_key))
421422
assert_nil(context.dig(expected_key)) if RUBY_VERSION >= '2.3.0'
422423

423424
# And a grandchild field
424-
runtime_info[:current_result] = OpenStruct.new(path: ["somewhere", "child", "grandchild"])
425+
dummy_runtime.current_result = OpenStruct.new(path: ["somewhere", "child", "grandchild"])
425426
context.scoped_set!(expected_key, :something_else)
426427
context.scoped_set!(:another_key, :another_value)
427428
assert_equal(:something_else, context[expected_key])
@@ -445,7 +446,7 @@ class ContextSchema < GraphQL::Schema
445446
it "has a #current_path method" do
446447
context = GraphQL::Query::Context.new(query: OpenStruct.new(schema: schema), values: nil, object: nil)
447448
current_result = OpenStruct.new(path: ["somewhere", "child", "grandchild"])
448-
Thread.current[:__graphql_runtime_info][:current_result] = current_result
449+
Thread.current[:__graphql_runtime_info] = OpenStruct.new(current_result: current_result)
449450
assert_equal ["somewhere", "child", "grandchild"], context.scoped_context.current_path
450451
end
451452
end

0 commit comments

Comments
 (0)