Class: RuboCop::Cop::ThreadSafety::RackMiddlewareInstanceVariable

Inherits:
Base
  • Object
show all
Includes:
AllowedIdentifiers, OperationWithThreadsafeResult
Defined in:
lib/rubocop/cop/thread_safety/rack_middleware_instance_variable.rb

Overview

Avoid instance variables in rack middleware.

Middlewares are initialized once, meaning any instance variables are shared between executor threads. To avoid potential race conditions, it’s recommended to design middlewares to be stateless or to implement proper synchronization mechanisms.

Examples:

# bad
class CounterMiddleware
  def initialize(app)
    @app = app
    @counter = 0
  end

  def call(env)
    app.call(env)
  ensure
    @counter += 1
  end
end

# good
class CounterMiddleware
  def initialize(app)
    @app = app
    @counter = Concurrent::AtomicReference.new(0)
  end

  def call(env)
    app.call(env)
  ensure
    @counter.update { |ref| ref + 1 }
  end
end

class IdentityMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    app.call(env)
  end
end

Constant Summary collapse

MSG =
'Avoid instance variables in Rack middleware.'
RESTRICT_ON_SEND =
%i[instance_variable_get instance_variable_set].freeze

Instance Method Summary collapse

Methods included from OperationWithThreadsafeResult

#operation_produces_threadsafe_object?

Instance Method Details

#app_variable(node) ⇒ Object



64
65
66
# File 'lib/rubocop/cop/thread_safety/rack_middleware_instance_variable.rb', line 64

def_node_search :app_variable, <<~MATCHER
  (def :initialize (args (arg $_) ...) `(ivasgn $_ (lvar $_)))
MATCHER

#on_class(node) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/rubocop/cop/thread_safety/rack_middleware_instance_variable.rb', line 68

def on_class(node)
  return unless rack_middleware_like_class?(node)

  constructor_method = find_constructor_method(node)
  return unless (application_variable = extract_application_variable_from_contructor_method(constructor_method))

  safe_variables = extract_safe_variables_from_constructor_method(constructor_method)

  node.each_node(:def) do |def_node|
    def_node.each_node(:ivasgn, :ivar) do |ivar_node|
      variable, = ivar_node.to_a
      if variable == application_variable || safe_variables.include?(variable) || allowed_identifier?(variable)
        next
      end

      add_offense ivar_node
    end
  end
end

#on_send(node) ⇒ Object



88
89
90
91
92
93
94
95
# File 'lib/rubocop/cop/thread_safety/rack_middleware_instance_variable.rb', line 88

def on_send(node)
  argument = node.first_argument

  return unless argument&.sym_type? || argument&.str_type?
  return if allowed_identifier?(argument.value)

  add_offense node
end

#rack_middleware_like_class?(node) ⇒ Object



59
60
61
# File 'lib/rubocop/cop/thread_safety/rack_middleware_instance_variable.rb', line 59

def_node_matcher :rack_middleware_like_class?, <<~MATCHER
  (class (const nil? _) nil? (begin <(def :initialize (args (arg _)+) ...) (def :call (args (arg _)) ...) ...>))
MATCHER