Skip to content

Commit 6a55247

Browse files
committed
Move instrumentation implementation to trace module
1 parent 899ecb3 commit 6a55247

File tree

4 files changed

+161
-151
lines changed

4 files changed

+161
-151
lines changed

lib/graphql/execution/interpreter.rb

Lines changed: 85 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -37,173 +37,107 @@ def run_all(schema, query_options, context: {}, max_complexity: schema.max_compl
3737
multiplex.current_trace.execute_multiplex(multiplex: multiplex) do
3838
schema = multiplex.schema
3939
queries = multiplex.queries
40-
query_instrumenters = schema.instrumenters[:query]
41-
multiplex_instrumenters = schema.instrumenters[:multiplex]
4240
lazies_at_depth = Hash.new { |h, k| h[k] = [] }
41+
multiplex_analyzers = schema.multiplex_analyzers
42+
if multiplex.max_complexity
43+
multiplex_analyzers += [GraphQL::Analysis::AST::MaxQueryComplexity]
44+
end
4345

44-
# First, run multiplex instrumentation, then query instrumentation for each query
45-
call_hooks(multiplex_instrumenters, multiplex, :before_multiplex, :after_multiplex) do
46-
each_query_call_hooks(query_instrumenters, queries) do
47-
schema = multiplex.schema
48-
multiplex_analyzers = schema.multiplex_analyzers
49-
queries = multiplex.queries
50-
if multiplex.max_complexity
51-
multiplex_analyzers += [GraphQL::Analysis::AST::MaxQueryComplexity]
52-
end
53-
54-
schema.analysis_engine.analyze_multiplex(multiplex, multiplex_analyzers)
55-
begin
56-
# Since this is basically the batching context,
57-
# share it for a whole multiplex
58-
multiplex.context[:interpreter_instance] ||= multiplex.schema.query_execution_strategy.new
59-
# Do as much eager evaluation of the query as possible
60-
results = []
61-
queries.each_with_index do |query, idx|
62-
multiplex.dataloader.append_job {
63-
operation = query.selected_operation
64-
result = if operation.nil? || !query.valid? || query.context.errors.any?
65-
NO_OPERATION
66-
else
67-
begin
68-
# Although queries in a multiplex _share_ an Interpreter instance,
69-
# they also have another item of state, which is private to that query
70-
# in particular, assign it here:
71-
runtime = Runtime.new(query: query, lazies_at_depth: lazies_at_depth)
72-
query.context.namespace(:interpreter_runtime)[:runtime] = runtime
73-
74-
query.current_trace.execute_query(query: query) do
75-
runtime.run_eager
76-
end
77-
rescue GraphQL::ExecutionError => err
78-
query.context.errors << err
79-
NO_OPERATION
80-
end
46+
schema.analysis_engine.analyze_multiplex(multiplex, multiplex_analyzers)
47+
begin
48+
# Since this is basically the batching context,
49+
# share it for a whole multiplex
50+
multiplex.context[:interpreter_instance] ||= multiplex.schema.query_execution_strategy.new
51+
# Do as much eager evaluation of the query as possible
52+
results = []
53+
queries.each_with_index do |query, idx|
54+
multiplex.dataloader.append_job {
55+
operation = query.selected_operation
56+
result = if operation.nil? || !query.valid? || query.context.errors.any?
57+
NO_OPERATION
58+
else
59+
begin
60+
# Although queries in a multiplex _share_ an Interpreter instance,
61+
# they also have another item of state, which is private to that query
62+
# in particular, assign it here:
63+
runtime = Runtime.new(query: query, lazies_at_depth: lazies_at_depth)
64+
query.context.namespace(:interpreter_runtime)[:runtime] = runtime
65+
66+
query.current_trace.execute_query(query: query) do
67+
runtime.run_eager
8168
end
82-
results[idx] = result
83-
}
69+
rescue GraphQL::ExecutionError => err
70+
query.context.errors << err
71+
NO_OPERATION
72+
end
8473
end
74+
results[idx] = result
75+
}
76+
end
8577

86-
multiplex.dataloader.run
78+
multiplex.dataloader.run
8779

88-
# Then, work through lazy results in a breadth-first way
89-
multiplex.dataloader.append_job {
90-
query = multiplex.queries.length == 1 ? multiplex.queries[0] : nil
91-
queries = multiplex ? multiplex.queries : [query]
92-
final_values = queries.map do |query|
93-
runtime = query.context.namespace(:interpreter_runtime)[:runtime]
94-
# it might not be present if the query has an error
95-
runtime ? runtime.final_result : nil
96-
end
97-
final_values.compact!
98-
multiplex.current_trace.execute_query_lazy(multiplex: multiplex, query: query) do
99-
Interpreter::Resolve.resolve_each_depth(lazies_at_depth, multiplex.dataloader)
100-
end
80+
# Then, work through lazy results in a breadth-first way
81+
multiplex.dataloader.append_job {
82+
query = multiplex.queries.length == 1 ? multiplex.queries[0] : nil
83+
queries = multiplex ? multiplex.queries : [query]
84+
final_values = queries.map do |query|
85+
runtime = query.context.namespace(:interpreter_runtime)[:runtime]
86+
# it might not be present if the query has an error
87+
runtime ? runtime.final_result : nil
88+
end
89+
final_values.compact!
90+
multiplex.current_trace.execute_query_lazy(multiplex: multiplex, query: query) do
91+
Interpreter::Resolve.resolve_each_depth(lazies_at_depth, multiplex.dataloader)
92+
end
93+
}
94+
multiplex.dataloader.run
95+
96+
# Then, find all errors and assign the result to the query object
97+
results.each_with_index do |data_result, idx|
98+
query = queries[idx]
99+
# Assign the result so that it can be accessed in instrumentation
100+
query.result_values = if data_result.equal?(NO_OPERATION)
101+
if !query.valid? || query.context.errors.any?
102+
# A bit weird, but `Query#static_errors` _includes_ `query.context.errors`
103+
{ "errors" => query.static_errors.map(&:to_h) }
104+
else
105+
data_result
106+
end
107+
else
108+
result = {
109+
"data" => query.context.namespace(:interpreter_runtime)[:runtime].final_result
101110
}
102-
multiplex.dataloader.run
103-
104-
# Then, find all errors and assign the result to the query object
105-
results.each_with_index do |data_result, idx|
106-
query = queries[idx]
107-
# Assign the result so that it can be accessed in instrumentation
108-
query.result_values = if data_result.equal?(NO_OPERATION)
109-
if !query.valid? || query.context.errors.any?
110-
# A bit weird, but `Query#static_errors` _includes_ `query.context.errors`
111-
{ "errors" => query.static_errors.map(&:to_h) }
112-
else
113-
data_result
114-
end
115-
else
116-
result = {
117-
"data" => query.context.namespace(:interpreter_runtime)[:runtime].final_result
118-
}
119111

120-
if query.context.errors.any?
121-
error_result = query.context.errors.map(&:to_h)
122-
result["errors"] = error_result
123-
end
124-
125-
result
126-
end
127-
if query.context.namespace?(:__query_result_extensions__)
128-
query.result_values["extensions"] = query.context.namespace(:__query_result_extensions__)
129-
end
130-
# Get the Query::Result, not the Hash
131-
results[idx] = query.result
112+
if query.context.errors.any?
113+
error_result = query.context.errors.map(&:to_h)
114+
result["errors"] = error_result
132115
end
133116

134-
results
135-
rescue Exception
136-
# TODO rescue at a higher level so it will catch errors in analysis, too
137-
# Assign values here so that the query's `@executed` becomes true
138-
queries.map { |q| q.result_values ||= {} }
139-
raise
140-
ensure
141-
queries.map { |query|
142-
runtime = query.context.namespace(:interpreter_runtime)[:runtime]
143-
if runtime
144-
runtime.delete_all_interpreter_context
145-
end
146-
}
117+
result
118+
end
119+
if query.context.namespace?(:__query_result_extensions__)
120+
query.result_values["extensions"] = query.context.namespace(:__query_result_extensions__)
147121
end
122+
# Get the Query::Result, not the Hash
123+
results[idx] = query.result
148124
end
149-
end
150-
end
151-
end
152-
153-
private
154125

155-
# Call the before_ hooks of each query,
156-
# Then yield if no errors.
157-
# `call_hooks` takes care of appropriate cleanup.
158-
def each_query_call_hooks(instrumenters, queries, i = 0)
159-
if i >= queries.length
160-
yield
161-
else
162-
query = queries[i]
163-
call_hooks(instrumenters, query, :before_query, :after_query) {
164-
each_query_call_hooks(instrumenters, queries, i + 1) {
165-
yield
126+
results
127+
rescue Exception
128+
# TODO rescue at a higher level so it will catch errors in analysis, too
129+
# Assign values here so that the query's `@executed` becomes true
130+
queries.map { |q| q.result_values ||= {} }
131+
raise
132+
ensure
133+
queries.map { |query|
134+
runtime = query.context.namespace(:interpreter_runtime)[:runtime]
135+
if runtime
136+
runtime.delete_all_interpreter_context
137+
end
166138
}
167-
}
168-
end
169-
end
170-
171-
# Call each before hook, and if they all succeed, yield.
172-
# If they don't all succeed, call after_ for each one that succeeded.
173-
def call_hooks(instrumenters, object, before_hook_name, after_hook_name)
174-
begin
175-
successful = []
176-
instrumenters.each do |instrumenter|
177-
instrumenter.public_send(before_hook_name, object)
178-
successful << instrumenter
179-
end
180-
181-
# if any before hooks raise an exception, quit calling before hooks,
182-
# but call the after hooks on anything that succeeded but also
183-
# raise the exception that came from the before hook.
184-
rescue GraphQL::ExecutionError => err
185-
object.context.errors << err
186-
rescue => e
187-
raise call_after_hooks(successful, object, after_hook_name, e)
188-
end
189-
190-
begin
191-
yield # Call the user code
192-
ensure
193-
ex = call_after_hooks(successful, object, after_hook_name, nil)
194-
raise ex if ex
195-
end
196-
end
197-
198-
def call_after_hooks(instrumenters, object, after_hook_name, ex)
199-
instrumenters.reverse_each do |instrumenter|
200-
begin
201-
instrumenter.public_send(after_hook_name, object)
202-
rescue => e
203-
ex = e
204139
end
205140
end
206-
ex
207141
end
208142
end
209143

lib/graphql/schema.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,6 +1044,7 @@ def lazy_resolve(lazy_class, value_method)
10441044
end
10451045

10461046
def instrument(instrument_step, instrumenter, options = {})
1047+
trace_with(Tracing::LegacyHooksTrace)
10471048
own_instrumenters[instrument_step] << instrumenter
10481049
end
10491050

lib/graphql/tracing.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# frozen_string_literal: true
22
require "graphql/tracing/trace"
33
require "graphql/tracing/legacy_trace"
4+
require "graphql/tracing/legacy_hooks_trace"
45

56
# Legacy tracing:
67
require "graphql/tracing/active_support_notifications_tracing"
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# frozen_string_literal: true
2+
module GraphQL
3+
module Tracing
4+
module LegacyHooksTrace
5+
def execute_multiplex(multiplex:)
6+
multiplex_instrumenters = multiplex.schema.instrumenters[:multiplex]
7+
query_instrumenters = multiplex.schema.instrumenters[:query]
8+
# First, run multiplex instrumentation, then query instrumentation for each query
9+
RunHooks.call_hooks(multiplex_instrumenters, multiplex, :before_multiplex, :after_multiplex) do
10+
RunHooks.each_query_call_hooks(query_instrumenters, multiplex.queries) do
11+
super
12+
end
13+
end
14+
end
15+
16+
module RunHooks
17+
module_function
18+
# Call the before_ hooks of each query,
19+
# Then yield if no errors.
20+
# `call_hooks` takes care of appropriate cleanup.
21+
def each_query_call_hooks(instrumenters, queries, i = 0)
22+
if i >= queries.length
23+
yield
24+
else
25+
query = queries[i]
26+
call_hooks(instrumenters, query, :before_query, :after_query) {
27+
each_query_call_hooks(instrumenters, queries, i + 1) {
28+
yield
29+
}
30+
}
31+
end
32+
end
33+
34+
# Call each before hook, and if they all succeed, yield.
35+
# If they don't all succeed, call after_ for each one that succeeded.
36+
def call_hooks(instrumenters, object, before_hook_name, after_hook_name)
37+
begin
38+
successful = []
39+
instrumenters.each do |instrumenter|
40+
instrumenter.public_send(before_hook_name, object)
41+
successful << instrumenter
42+
end
43+
44+
# if any before hooks raise an exception, quit calling before hooks,
45+
# but call the after hooks on anything that succeeded but also
46+
# raise the exception that came from the before hook.
47+
rescue GraphQL::ExecutionError => err
48+
object.context.errors << err
49+
rescue => e
50+
raise call_after_hooks(successful, object, after_hook_name, e)
51+
end
52+
53+
begin
54+
yield # Call the user code
55+
ensure
56+
ex = call_after_hooks(successful, object, after_hook_name, nil)
57+
raise ex if ex
58+
end
59+
end
60+
61+
def call_after_hooks(instrumenters, object, after_hook_name, ex)
62+
instrumenters.reverse_each do |instrumenter|
63+
begin
64+
instrumenter.public_send(after_hook_name, object)
65+
rescue => e
66+
ex = e
67+
end
68+
end
69+
ex
70+
end
71+
end
72+
end
73+
end
74+
end

0 commit comments

Comments
 (0)