Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix current_depth method #4386

Merged
merged 3 commits into from
Mar 17, 2023
Merged

Fix current_depth method #4386

merged 3 commits into from
Mar 17, 2023

Conversation

psyho
Copy link
Contributor

@psyho psyho commented Mar 15, 2023

Under some circumstances, the thread_info object is empty, and there is no result. In such a case, the method should return depth 1 instead of failing.

We ran into this problem after updating to graphql version 2.0.19 (although it also happens in 2.0.18).

Under some circumstances, the `thread_info` object is empty, and there
is no result. In such case, the method should return depth 1 instead of
failing.
@rmosolgo
Copy link
Owner

Hey, thanks for submitting this fix! I'm interested in including a test for this case to make sure I don't break it again in the future. Could you add a test case to this PR for it, or else share the stack trace of the error you encountered? (The stack trace would likely include enough info to replicate the bug.)

@psyho
Copy link
Contributor Author

psyho commented Mar 16, 2023

@rmosolgo I would absolutely add a failing spec, but I honestly don't know what causes it. We use graphql-batch with custom loaders across many types, and the tests only failed for one particular query, which didn't do anything especially unique with the loaders...

FWIW, here is a stacktrace from the spec:

NoMethodError:
  undefined method `graphql_parent' for nil:NilClass
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:606:in `current_depth'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:956:in `after_lazy'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:522:in `block (2 levels) in evaluate_selection_with_args'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:861:in `call_method_on_directives'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:507:in `block in evaluate_selection_with_args'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:961:in `after_lazy'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:445:in `evaluate_selection_with_args'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:435:in `evaluate_selection'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:373:in `block (2 levels) in evaluate_selections'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/dataloader/null_dataloader.rb:19:in `append_job'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:372:in `block in evaluate_selections'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:371:in `each'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:371:in `evaluate_selections'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:861:in `call_method_on_directives'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:784:in `block (2 levels) in continue_field'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:210:in `tap_or_each'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:774:in `block in continue_field'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:961:in `after_lazy'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:760:in `continue_field'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:854:in `block (2 levels) in resolve_list_item'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:961:in `after_lazy'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:851:in `block in resolve_list_item'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:861:in `call_method_on_directives'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:849:in `resolve_list_item'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:814:in `block (2 levels) in continue_field'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/dataloader/null_dataloader.rb:19:in `append_job'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:813:in `block in continue_field'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:808:in `each'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:808:in `continue_field'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:525:in `block (3 levels) in evaluate_selection_with_args'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:961:in `after_lazy'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:522:in `block (2 levels) in evaluate_selection_with_args'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:861:in `call_method_on_directives'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:507:in `block in evaluate_selection_with_args'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:961:in `after_lazy'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:445:in `evaluate_selection_with_args'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:439:in `block in evaluate_selection'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/schema/member/has_arguments.rb:288:in `block (3 levels) in coerce_arguments'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/dataloader/null_dataloader.rb:19:in `append_job'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/schema/member/has_arguments.rb:269:in `block (2 levels) in coerce_arguments'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/schema/member/has_arguments.rb:268:in `each'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/schema/member/has_arguments.rb:268:in `block in coerce_arguments'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/schema/member/has_arguments.rb:297:in `coerce_arguments'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/arguments_cache.rb:52:in `dataload_for'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:438:in `evaluate_selection'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:373:in `block (2 levels) in evaluate_selections'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/dataloader/null_dataloader.rb:19:in `append_job'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:372:in `block in evaluate_selections'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:371:in `each'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:371:in `evaluate_selections'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:250:in `block (4 levels) in run_eager'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:861:in `call_method_on_directives'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:249:in `block (3 levels) in run_eager'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/dataloader/null_dataloader.rb:19:in `append_job'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:247:in `block (2 levels) in run_eager'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:210:in `tap_or_each'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:238:in `block in run_eager'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:861:in `call_method_on_directives'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter/runtime.rb:229:in `run_eager'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter.rb:75:in `block (6 levels) in run_all'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/tracing.rb:62:in `execute_query'
    # /usr/local/bundle/ruby/3.2.0/gems/renometry-1.14.1/lib/renometry/graphql/tracer.rb:38:in `block in execute_query'
    # /usr/local/bundle/ruby/3.2.0/gems/renometry-1.14.1/lib/renometry/span_manager.rb:21:in `span'
    # /usr/local/bundle/ruby/3.2.0/gems/renometry-1.14.1/lib/renometry.rb:29:in `span'
    # /usr/local/bundle/ruby/3.2.0/gems/renometry-1.14.1/lib/renometry/graphql/tracer.rb:38:in `execute_query'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/tracing/new_relic_trace.rb:22:in `block in execute_query'
    # /usr/local/bundle/ruby/3.2.0/gems/newrelic_rpm-8.9.0/lib/new_relic/agent/method_tracer_helpers.rb:41:in `block in trace_execution_scoped'
    # /usr/local/bundle/ruby/3.2.0/gems/newrelic_rpm-8.9.0/lib/new_relic/agent/tracer.rb:351:in `capture_segment_error'
    # /usr/local/bundle/ruby/3.2.0/gems/newrelic_rpm-8.9.0/lib/new_relic/agent/method_tracer_helpers.rb:41:in `trace_execution_scoped'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/tracing/new_relic_trace.rb:21:in `execute_query'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter.rb:74:in `block (5 levels) in run_all'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/dataloader/null_dataloader.rb:19:in `append_job'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter.rb:62:in `block (4 levels) in run_all'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter.rb:61:in `each'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter.rb:61:in `each_with_index'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter.rb:61:in `block (3 levels) in run_all'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter.rb:172:in `block (2 levels) in each_query_call_hooks'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter.rb:167:in `each_query_call_hooks'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter.rb:171:in `block in each_query_call_hooks'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter.rb:198:in `call_hooks'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter.rb:170:in `each_query_call_hooks'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter.rb:46:in `block (2 levels) in run_all'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter.rb:198:in `call_hooks'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter.rb:45:in `block in run_all'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/tracing.rb:58:in `execute_multiplex'
    # /usr/local/bundle/ruby/3.2.0/gems/renometry-1.14.1/lib/renometry/graphql/tracer.rb:30:in `block in execute_multiplex'
    # /usr/local/bundle/ruby/3.2.0/gems/renometry-1.14.1/lib/renometry/span_manager.rb:21:in `span'
    # /usr/local/bundle/ruby/3.2.0/gems/renometry-1.14.1/lib/renometry.rb:29:in `span'
    # /usr/local/bundle/ruby/3.2.0/gems/renometry-1.14.1/lib/renometry/graphql/tracer.rb:29:in `execute_multiplex'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/tracing/new_relic_trace.rb:37:in `block in execute_multiplex'
    # /usr/local/bundle/ruby/3.2.0/gems/newrelic_rpm-8.9.0/lib/new_relic/agent/method_tracer_helpers.rb:41:in `block in trace_execution_scoped'
    # /usr/local/bundle/ruby/3.2.0/gems/newrelic_rpm-8.9.0/lib/new_relic/agent/tracer.rb:351:in `capture_segment_error'
    # /usr/local/bundle/ruby/3.2.0/gems/newrelic_rpm-8.9.0/lib/new_relic/agent/method_tracer_helpers.rb:41:in `trace_execution_scoped'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/tracing/new_relic_trace.rb:36:in `execute_multiplex'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/execution/interpreter.rb:37:in `run_all'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/schema.rb:1044:in `multiplex'
    # /usr/local/bundle/ruby/3.2.0/gems/graphql-2.0.19/lib/graphql/schema.rb:1020:in `execute'
    # ./spec/support/graphql_spec_helpers.rb:14:in `block in graphql_result'
    # /usr/local/bundle/ruby/3.2.0/gems/bullet-7.0.7/lib/bullet.rb:230:in `profile'
    # ./spec/support/graphql_spec_helpers.rb:13:in `graphql_result'
    # ./spec/graphql/queries/projects_by_ids_spec.rb:3:in `block (2 levels) in <top (required)>'
    # ./spec/graphql/queries/projects_by_ids_spec.rb:364:in `block (4 levels) in <top (required)>'
    # ./spec/spec_helper.rb:100:in `block (3 levels) in <top (required)>'
    # /usr/local/bundle/ruby/3.2.0/gems/database_cleaner-core-2.0.1/lib/database_cleaner/strategy.rb:30:in `cleaning'
    # /usr/local/bundle/ruby/3.2.0/gems/database_cleaner-core-2.0.1/lib/database_cleaner/cleaners.rb:34:in `block (2 levels) in cleaning'
    # /usr/local/bundle/ruby/3.2.0/gems/database_cleaner-core-2.0.1/lib/database_cleaner/cleaners.rb:35:in `cleaning'
    # ./spec/spec_helper.rb:99:in `block (2 levels) in <top (required)>'
    # /usr/local/bundle/ruby/3.2.0/gems/webmock-3.18.1/lib/webmock/rspec.rb:37:in `block (2 levels) in <top (required)>'
    # /usr/local/bundle/ruby/3.2.0/gems/rspec-retry-0.6.2/lib/rspec/retry.rb:124:in `block in run'
    # /usr/local/bundle/ruby/3.2.0/gems/rspec-retry-0.6.2/lib/rspec/retry.rb:110:in `loop'
    # /usr/local/bundle/ruby/3.2.0/gems/rspec-retry-0.6.2/lib/rspec/retry.rb:110:in `run'
    # /usr/local/bundle/ruby/3.2.0/gems/rspec-retry-0.6.2/lib/rspec_ext/rspec_ext.rb:12:in `run_with_retry'
    # /usr/local/bundle/ruby/3.2.0/gems/rspec-retry-0.6.2/lib/rspec/retry.rb:37:in `block (2 levels) in setup'

@rmosolgo
Copy link
Owner

Ok, thanks, this helps -- it looks like it was resolving a list at the time. As for returning 1, did you know that it was working on a first-level field, or did you suggest 1 as a better fallback value than raising an error? Also, if you wouldn't mind sharing the query that raised this error, I'd appreciate it. (I'm trying to figure out whether the fix for this bug should actually be to fix how GraphQL-Ruby manages its runtime context, or whether there's a legitimate case where this is nil.)

@psyho
Copy link
Contributor Author

psyho commented Mar 16, 2023

This is the minimum query that causes the failure in our spec:

{
      result: projectById(id: "#{project_id}") {
        feasibilityReviews {
          id
        }
      }
}

I tried reproducing it by creating a stripped-down schema that uses the same loader and graphql-batch, but it worked, so I don't know what conditions are needed to reproduce this issue.

The loader used for this field looks like this:

      class RelationLoader < ::GraphQL::Batch::Loader
        def initialize(relation:, column:)
          super()
          @relation = relation
          @column = column
        end

        def perform(ids)
          @relation.where(@column => ids).group_by(&@column).each do |id, records|
            fulfill(id, records)
          end

          ids.each { |id| fulfill(id, []) unless fulfilled?(*id) }
        end
      end

and it is used more or less like this:

 class ProjectType < GraphQL::Schema::Object
    field :feasibility_reviews, [FeasibilityReviewType], null: false
    
    def feasibility_reviews
      RelationLoader.for(relation: Models::FeasibilityReview.where(reviewer_id: current_user.id), column: :project_id)
    end
  end

@rmosolgo
Copy link
Owner

Thanks for those details -- that's really helpful. I'll try to come up with a replication.

Also, does projectById use a batch loader, too, or does it fetch a project right away?

@psyho
Copy link
Contributor Author

psyho commented Mar 16, 2023

It uses a regular ActiveRecord Models::Project.find, nothing async.

@rmosolgo
Copy link
Owner

Cool, thanks for confirming. I can't figure out exactly what's wrong 😖 But I just pushed an alternative solution which simplifies the class a bit (by removing a method). Could you give that solution a try and see if it still fixes it for you, or if you still encounter an error (please share the message and backtrace if so)?

@psyho
Copy link
Contributor Author

psyho commented Mar 16, 2023

Thank you @rmosolgo! I ran the specs and the fix works.

@rmosolgo
Copy link
Owner

Ok, thanks for checking into it. I'm afraid there's some other bug lurking here where some of the runtime context isn't set correctly 😖 But this will have to do for now.

@rmosolgo rmosolgo merged commit 3f106ee into rmosolgo:master Mar 17, 2023
@rmosolgo rmosolgo added this to the 2.0.20 milestone Mar 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants