-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Description
Describe the bug
Authorization failures on types defined by anonymous classes in large applications can be very slow.
Versions
graphql version: 1.8.7
ruby version: < 2.7
Steps to reproduce
- Be running a ruby version prior to 2.7
- Be running a large application with lots of constants in the constant namespace of the VM.
- Have a schema that is using an anonymous class (e.g from
Class.new(GraphQL::Object)) as a type and has not explicitly defined a name method. - Make a request where
unauthorized?for the type will returnfalse.
Expected behavior
Unauthorized requests should return quickly.
Actual behavior
In our (large) application, we were consistently seeing authentication failures taking 100-150 seconds.
Additional context
Prior to a patch in Ruby 2.7, Module#name has poor performance on anonymous classes, due to searching the entire constant namespace of the VM. [0][1][2]. This comes to play if you have a large application that is calling #name on an anonymous class or module.
In GraphQL::UnauthorizedError, if no message is specified, a default message is created [3]. In this default message creation, we call type.name. If the type is an anonymous class which has not explicitly defined a name method, the performance issue will be triggered.
A GraphQL::UnauthorizedError is instantiated without a message when an object fails its authorization [4].
I believe if we change the default message creation in GraphQL::UnauthorizedError#initialize to use type.graphql_name instead of type.name, we would get both a better error message AND would solve the performance regression.
In our application, we monkeypatched the GraphQL::UnauthorizedError#initialize method to not call type.name and saw our unauthorized requests go from taking 100 - 150 seconds to a few milliseconds.
Footnotes:
[0] https://bugs.ruby-lang.org/issues/11119
[1] https://bugs.ruby-lang.org/issues/15625
[2] https://bugs.ruby-lang.org/issues/15765 - Patch for Ruby 2.7
[3]
| message ||= "An instance of #{object.class} failed #{type.name}'s authorization check" |
[4]
graphql-ruby/lib/graphql/schema/object.rb
Line 53 in 5cb105f
| err = GraphQL::UnauthorizedError.new(object: object, type: self, context: context) |