Class: GraphQL::Schema::Timeout

Inherits:
Object
  • Object
show all
Defined in:
lib/graphql/schema/timeout.rb

Overview

This plugin will stop resolving new fields after max_seconds have elapsed. After the time has passed, any remaining fields will be nil, with errors added to the errors key. Any already-resolved fields will be in the data key, so you'll get a partial response.

You can subclass GraphQL::Schema::Timeout and override the handle_timeout method to provide custom logic when a timeout error occurs.

Note that this will stop a query in between field resolutions, but it doesn't interrupt long-running resolve functions. Be sure to use timeout options for external connections. For more info, see www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/

Examples:

Stop resolving fields after 2 seconds

class MySchema < GraphQL::Schema
  use GraphQL::Schema::Timeout, max_seconds: 2
end

Notifying Bugsnag and logging a timeout

class MyTimeout < GraphQL::Schema::Timeout
  def handle_timeout(error, query)
     Rails.logger.warn("GraphQL Timeout: #{error.message}: #{query.query_string}")
     Bugsnag.notify(error, {query_string: query.query_string})
  end
end

class MySchema < GraphQL::Schema
  use MyTimeout, max_seconds: 2
end

Defined Under Namespace

Classes: TimeoutError

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(max_seconds:) ⇒ Timeout

Returns a new instance of Timeout.

Parameters:

  • max_seconds (Numeric)

    how many seconds the query should be allowed to resolve new fields


44
45
46
# File 'lib/graphql/schema/timeout.rb', line 44

def initialize(max_seconds:)
  @max_seconds = max_seconds
end

Instance Attribute Details

#max_secondsObject (readonly)

Returns the value of attribute max_seconds


36
37
38
# File 'lib/graphql/schema/timeout.rb', line 36

def max_seconds
  @max_seconds
end

Class Method Details

.use(schema, **options) ⇒ Object


38
39
40
41
# File 'lib/graphql/schema/timeout.rb', line 38

def self.use(schema, **options)
  tracer = new(**options)
  schema.tracer(tracer)
end

Instance Method Details

#handle_timeout(error, query) ⇒ Object

Invoked when a query times out.


91
92
93
# File 'lib/graphql/schema/timeout.rb', line 91

def handle_timeout(error, query)
  # override to do something interesting
end

#trace(key, data) ⇒ Object


48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/graphql/schema/timeout.rb', line 48

def trace(key, data)
  case key
  when 'execute_multiplex'
    timeout_state = {
      timeout_at: Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) + max_seconds * 1000,
      timed_out: false
    }

    data.fetch(:multiplex).queries.each do |query|
      query.context.namespace(self.class)[:state] = timeout_state
    end

    yield
  when 'execute_field', 'execute_field_lazy'
    query = data[:context] ? data.fetch(:context).query : data.fetch(:query)
    timeout_state = query.context.namespace(self.class).fetch(:state)
    if Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) > timeout_state.fetch(:timeout_at)
      error = if data[:context]
        context = data.fetch(:context)
        GraphQL::Schema::Timeout::TimeoutError.new(context.parent_type, context.field)
      else
        field = data.fetch(:field)
        GraphQL::Schema::Timeout::TimeoutError.new(field.owner, field)
      end

      # Only invoke the timeout callback for the first timeout
      unless timeout_state[:timed_out]
        timeout_state[:timed_out] = true
        handle_timeout(error, query)
      end

      error
    else
      yield
    end
  else
    yield
  end
end