Skip to content

GraphQL::Tracing::ActiveSupportNotificationsTrace raises a NoMethodError in v2.5.0 #5334

@yn-misaki

Description

@yn-misaki

When using GraphQL Ruby 2.5.0 with both GraphQL::Tracing::ActiveSupportNotificationsTrace and GraphQL::Dataloader, an intermittent NoMethodError exception occasionally occurs during query execution.

workspace/graphql-ruby/lib/graphql/tracing/notifications_trace.rb:142:in 'GraphQL::Tracing::NotificationsTrace#dataloader_fiber_resume': undefined method 'name' for nil (NoMethodError)

        begin_notifications_event(prev_ev.name, prev_ev.payload)

Versions

graphql version: 2.5.0
rails (or other framework): 8.0.2

GraphQL schema and query

require 'bundler/inline'

gemfile do
  gem 'graphql', path: "./"
  gem 'sqlite3'
  gem 'activerecord', require: 'active_record'
end

ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
ActiveRecord::Schema.define do
  create_table :microposts do |t|
    t.string(:content)
    t.belongs_to :user
  end
  create_table :users do |t|
    t.string(:name)
  end
end

class Micropost < ActiveRecord::Base; end

class User < ActiveRecord::Base
  has_many :microposts
end

user = User.create!(name: 'Sample')
user.microposts.create!(content: 'Hello World!')

class MySchema < GraphQL::Schema
  class Micropost < GraphQL::Schema::Object
    field :content, String
  end

  class User < GraphQL::Schema::Object
    field :name, String
    field :microposts, [Micropost]
  end

  class Query < GraphQL::Schema::Object
    field :microposts, [Micropost] do
      argument :user_id, ID, loads: User
    end

    def microposts(user:)
      user.microposts
    end
  end

  max_complexity 1000
  query(Query)
  trace_with(GraphQL::Tracing::ActiveSupportNotificationsTrace)
  use(GraphQL::Dataloader)

  def self.object_from_id(id, ctx)
    ctx.dataloader.with(GraphQL::Dataloader::ActiveRecordSource, ::User).load(id)
  end

  def self.resolve_type(abs_t, obj, ctx)
    User
  end
end

query_str = <<~GRAPHQL
  query queryOne($userId: ID!) {
    microposts(userId: $userId) {
      content
    }
  }
GRAPHQL

query = GraphQL::Query.new(MySchema, query_str, variables: { userId: '1' })
pp query.result.to_h

Actual behavior

Click to view exception backtrace
workspace/graphql-ruby/lib/graphql/tracing/notifications_trace.rb:142:in 'GraphQL::Tracing::NotificationsTrace#dataloader_fiber_resume': undefined method 'name' for nil (NoMethodError)

        begin_notifications_event(prev_ev.name, prev_ev.payload)
                                         ^^^^^
        from workspace/graphql-ruby/lib/graphql/dataloader.rb:138:in 'GraphQL::Dataloader#yield'
        from workspace/graphql-ruby/lib/graphql/dataloader/source.rb:96:in 'GraphQL::Dataloader::Source#sync'
        from workspace/graphql-ruby/lib/graphql/dataloader/source.rb:57:in 'GraphQL::Dataloader::Source#load'
        from workspace/graphql-ruby/lib/graphql/dataloader/active_record_source.rb:15:in 'GraphQL::Dataloader::ActiveRecordSource#load'
        from dataloder_sample.rb:60:in 'MySchema.object_from_id'
        from workspace/graphql-ruby/lib/graphql/schema/member/has_arguments.rb:335:in 'GraphQL::Schema::Member::HasArguments::ArgumentObjectLoader#object_from_id'
        from workspace/graphql-ruby/lib/graphql/schema/member/has_arguments.rb:343:in 'GraphQL::Schema::Member::HasArguments::ArgumentObjectLoader#load_application_object'
        from workspace/graphql-ruby/lib/graphql/schema/member/has_arguments.rb:347:in 'GraphQL::Schema::Member::HasArguments::ArgumentObjectLoader#load_and_authorize_application_object'
        from workspace/graphql-ruby/lib/graphql/schema/argument.rb:356:in 'GraphQL::Schema::Argument#load_and_authorize_value'
        from workspace/graphql-ruby/lib/graphql/schema/argument.rb:292:in 'block in GraphQL::Schema::Argument#coerce_into_values'
        from workspace/graphql-ruby/lib/graphql/execution/interpreter/runtime.rb:784:in 'GraphQL::Execution::Interpreter::Runtime#minimal_after_lazy'
        from workspace/graphql-ruby/lib/graphql/query.rb:414:in 'GraphQL::Query#after_lazy'
        from workspace/graphql-ruby/lib/graphql/schema/argument.rb:282:in 'GraphQL::Schema::Argument#coerce_into_values'
        from workspace/graphql-ruby/lib/graphql/schema/member/has_arguments.rb:255:in 'block (3 levels) in GraphQL::Schema::Member::HasArguments#coerce_arguments'
        from workspace/graphql-ruby/lib/graphql/dataloader.rb:300:in 'block in GraphQL::Dataloader#spawn_job_fiber'
        from workspace/graphql-ruby/lib/graphql/dataloader.rb:255:in 'block in GraphQL::Dataloader#spawn_fiber'

Additional context

This issue is caused by changes introduced in PR #5298. The error can be fixed by modifying the code similar to how it's implemented in lib/graphql/tracing/monitor_trace.rb, by adding a nil check:

def dataloader_fiber_resume(source)
  prev_ev = Fiber[PREVIOUS_EV_KEY]
  if prev_ev
    begin_notifications_event(prev_ev.name, prev_ev.payload)
  end
  super
end

Since the exact conditions that trigger this error are not clearly understood, it's difficult to write a reliable test case.
I would like your support, if possible.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions