Skip to content

Commit 2816885

Browse files
authored
Merge pull request #5240 from rmosolgo/better-nr-trace
Rewrite NewRelicTrace to support fiber stops/starts
2 parents a5451ab + 5451eba commit 2816885

File tree

13 files changed

+446
-134
lines changed

13 files changed

+446
-134
lines changed

.rubocop.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ require:
44
- ./lib/graphql/rubocop/graphql/default_null_true
55
- ./lib/graphql/rubocop/graphql/default_required_true
66
- ./cop/development/context_is_passed_cop
7-
- ./cop/development/trace_calls_super_cop.rb
7+
- ./cop/development/trace_methods_cop.rb
88

99
AllCops:
1010
DisabledByDefault: true
@@ -45,9 +45,10 @@ Development/NoFocusCop:
4545
Include:
4646
- "spec/**/*"
4747

48-
Development/TraceCallsSuperCop:
48+
Development/TraceMethodsCop:
4949
Include:
50-
- "lib/graphql/tracing/*_trace.rb"
50+
- "lib/graphql/tracing/perfetto_trace.rb"
51+
- "lib/graphql/tracing/new_relic_trace.rb"
5152

5253
# def ...
5354
# end

cop/development/trace_calls_super_cop.rb

Lines changed: 0 additions & 61 deletions
This file was deleted.
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# frozen_string_literal: true
2+
require 'rubocop'
3+
4+
module Cop
5+
module Development
6+
class TraceMethodsCop < RuboCop::Cop::Base
7+
extend RuboCop::Cop::AutoCorrector
8+
9+
TRACE_HOOKS = [
10+
:analyze_multiplex,
11+
:analyze_query,
12+
:authorized,
13+
:authorized_lazy,
14+
:begin_analyze_multiplex,
15+
:begin_authorized,
16+
:begin_dataloader,
17+
:begin_dataloader_source,
18+
:begin_execute_field,
19+
:begin_execute_multiplex,
20+
:begin_parse,
21+
:begin_resolve_type,
22+
:begin_validate,
23+
:dataloader_fiber_exit,
24+
:dataloader_fiber_resume,
25+
:dataloader_fiber_yield,
26+
:dataloader_spawn_execution_fiber,
27+
:dataloader_spawn_source_fiber,
28+
:end_analyze_multiplex,
29+
:end_authorized,
30+
:end_dataloader,
31+
:end_dataloader_source,
32+
:end_execute_field,
33+
:end_execute_multiplex,
34+
:end_parse,
35+
:end_resolve_type,
36+
:end_validate,
37+
:execute_field,
38+
:execute_field_lazy,
39+
:execute_multiplex,
40+
:execute_query,
41+
:execute_query_lazy,
42+
:lex,
43+
:parse,
44+
:resolve_type,
45+
:resolve_type_lazy,
46+
:validate,
47+
]
48+
49+
MSG = "Trace methods should call `super` to pass control to other traces"
50+
51+
def on_def(node)
52+
if TRACE_HOOKS.include?(node.method_name) && !node.each_descendant(:super, :zsuper).any?
53+
add_offense(node) do |corrector|
54+
if node.body
55+
offset = node.loc.column + 2
56+
corrector.insert_after(node.body.loc.expression, "\n#{' ' * offset}super")
57+
end
58+
end
59+
end
60+
end
61+
62+
def on_module(node)
63+
if node.defined_module_name.to_s.end_with?("Trace")
64+
all_defs = []
65+
node.body.each_child_node do |body_node|
66+
if body_node.def_type?
67+
all_defs << body_node.method_name
68+
end
69+
end
70+
71+
missing_defs = TRACE_HOOKS - all_defs
72+
redundant_defs = [
73+
# Not really necessary for making a good trace:
74+
:lex, :analyze_query, :execute_query, :execute_query_lazy,
75+
# Only useful for isolated event tracking:
76+
:dataloader_fiber_exit, :dataloader_spawn_execution_fiber, :dataloader_spawn_source_fiber
77+
]
78+
missing_defs.each do |missing_def|
79+
if all_defs.include?(:"begin_#{missing_def}") && all_defs.include?(:"end_#{missing_def}")
80+
redundant_defs << missing_def
81+
redundant_defs << :"#{missing_def}_lazy"
82+
end
83+
end
84+
85+
missing_defs -= redundant_defs
86+
if missing_defs.any?
87+
add_offense(node, message: "Missing some trace hook methods:\n\n- #{missing_defs.join("\n- ")}")
88+
end
89+
end
90+
end
91+
end
92+
end
93+
end

lib/graphql/execution/interpreter.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def run_all(schema, query_options, context: {}, max_complexity: schema.max_compl
3737
multiplex = Execution::Multiplex.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity)
3838
Fiber[:__graphql_current_multiplex] = multiplex
3939
trace = multiplex.current_trace
40-
trace.begin_multiplex(multiplex)
40+
trace.begin_execute_multiplex(multiplex)
4141
trace.execute_multiplex(multiplex: multiplex) do
4242
schema = multiplex.schema
4343
queries = multiplex.queries
@@ -155,7 +155,7 @@ def run_all(schema, query_options, context: {}, max_complexity: schema.max_compl
155155
end
156156
end
157157
ensure
158-
trace&.end_multiplex(multiplex)
158+
trace&.end_execute_multiplex(multiplex)
159159
end
160160
end
161161

lib/graphql/execution/interpreter/runtime.rb

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -789,8 +789,10 @@ def after_lazy(lazy_obj, field:, owner_object:, arguments:, ast_node:, result:,
789789
runtime_state.was_authorized_by_scope_items = was_authorized_by_scope_items
790790
# Wrap the execution of _this_ method with tracing,
791791
# but don't wrap the continuation below
792+
result = nil
792793
inner_obj = begin
793-
if trace
794+
result = if trace
795+
@current_trace.begin_execute_field(field, owner_object, arguments, query)
794796
@current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do
795797
schema.sync_lazy(lazy_obj)
796798
end
@@ -805,6 +807,10 @@ def after_lazy(lazy_obj, field:, owner_object:, arguments:, ast_node:, result:,
805807
rescue GraphQL::ExecutionError => ex_err
806808
ex_err
807809
end
810+
ensure
811+
if trace
812+
@current_trace.end_execute_field(field, owner_object, arguments, query, result)
813+
end
808814
end
809815
yield(inner_obj, runtime_state)
810816
end
@@ -852,17 +858,18 @@ def resolve_type(type, value)
852858
resolved_type, resolved_value = @current_trace.resolve_type(query: query, type: type, object: value) do
853859
query.resolve_type(type, value)
854860
end
861+
@current_trace.end_resolve_type(type, value, context, resolved_type)
855862

856863
if lazy?(resolved_type)
857864
GraphQL::Execution::Lazy.new do
865+
@current_trace.begin_resolve_type(type, value, context)
858866
@current_trace.resolve_type_lazy(query: query, type: type, object: value) do
859867
rt = schema.sync_lazy(resolved_type)
860868
@current_trace.end_resolve_type(type, value, context, rt)
861869
rt
862870
end
863871
end
864872
else
865-
@current_trace.end_resolve_type(type, value, context, resolved_type)
866873
[resolved_type, resolved_value]
867874
end
868875
end

lib/graphql/schema/object.rb

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,29 +66,35 @@ def wrap(object, context)
6666
# @return [GraphQL::Schema::Object, GraphQL::Execution::Lazy]
6767
# @raise [GraphQL::UnauthorizedError] if the user-provided hook returns `false`
6868
def authorized_new(object, context)
69-
maybe_lazy_auth_val = context.query.current_trace.authorized(query: context.query, type: self, object: object) do
70-
begin
71-
context.query.current_trace.begin_authorized(self, object, context)
72-
authorized?(object, context)
73-
rescue GraphQL::UnauthorizedError => err
74-
context.schema.unauthorized_object(err)
75-
rescue StandardError => err
76-
context.query.handle_or_reraise(err)
69+
context.query.current_trace.begin_authorized(self, object, context)
70+
begin
71+
maybe_lazy_auth_val = context.query.current_trace.authorized(query: context.query, type: self, object: object) do
72+
begin
73+
authorized?(object, context)
74+
rescue GraphQL::UnauthorizedError => err
75+
context.schema.unauthorized_object(err)
76+
rescue StandardError => err
77+
context.query.handle_or_reraise(err)
78+
end
7779
end
80+
ensure
81+
context.query.current_trace.end_authorized(self, object, context, maybe_lazy_auth_val)
7882
end
7983

8084
auth_val = if context.schema.lazy?(maybe_lazy_auth_val)
8185
GraphQL::Execution::Lazy.new do
86+
context.query.current_trace.begin_authorized(self, object, context)
8287
context.query.current_trace.authorized_lazy(query: context.query, type: self, object: object) do
83-
context.schema.sync_lazy(maybe_lazy_auth_val)
88+
res = context.schema.sync_lazy(maybe_lazy_auth_val)
89+
context.query.current_trace.end_authorized(self, object, context, res)
90+
res
8491
end
8592
end
8693
else
8794
maybe_lazy_auth_val
8895
end
8996

9097
context.query.after_lazy(auth_val) do |is_authorized|
91-
context.query.current_trace.end_authorized(self, object, context, is_authorized)
9298
if is_authorized
9399
self.new(object, context)
94100
else

lib/graphql/static_validation/validator.rb

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ def initialize(schema:, rules: GraphQL::StaticValidation::ALL_RULES)
2727
# @param max_errors [Integer] Maximum number of errors before aborting validation. Any positive number will limit the number of errors. Defaults to nil for no limit.
2828
# @return [Array<Hash>]
2929
def validate(query, validate: true, timeout: nil, max_errors: nil)
30+
errors = nil
3031
query.current_trace.begin_validate(query, validate)
31-
is_valid = false
3232
query.current_trace.validate(validate: validate, query: query) do
3333
begin_t = Time.now
3434
errors = if validate == false
@@ -53,21 +53,20 @@ def validate(query, validate: true, timeout: nil, max_errors: nil)
5353

5454
context.errors
5555
end
56-
is_valid = errors.size == 0
5756

5857
{
5958
remaining_timeout: timeout ? (timeout - (Time.now - begin_t)) : nil,
6059
errors: errors,
6160
}
6261
end
6362
rescue GraphQL::ExecutionError => e
64-
is_valid = false
63+
errors = [e]
6564
{
6665
remaining_timeout: nil,
67-
errors: [e],
66+
errors: errors,
6867
}
6968
ensure
70-
query.current_trace.end_validate(query, validate, is_valid)
69+
query.current_trace.end_validate(query, validate, errors)
7170
end
7271

7372
# Invoked when static validation times out.

0 commit comments

Comments
 (0)