ThreadSafety

ThreadSafety/ActiveSupportCallbacks

Enabled by default Safe Supports autocorrection Version Added Version Changed

Pending

Yes

No

0.8

-

Avoid mutating ActiveSupport callback chains at runtime.

Calls such as User.skip_callback and User.set_callback mutate callback chains at process scope.

Examples

# bad
Site.skip_callback(:commit, :after, :after_owner_change)

# bad
Site.set_callback(
  :commit, :after, :after_owner_change,
  if: :saved_change_to_owner?
)

# good
class User < ApplicationRecord
  skip_callback :commit, :after, :after_owner_change
end

ThreadSafety/ClassAndModuleAttributes

Enabled by default Safe Supports autocorrection Version Added Version Changed

Enabled

Yes

No

-

-

Avoid mutating class and module attributes.

They are implemented by class variables, which are not thread-safe.

Examples

# bad
class User
  cattr_accessor :current_user
end

Configurable attributes

Name Default value Configurable values

ActiveSupportClassAttributeAllowed

false

Boolean

ThreadSafety/ClassInstanceVariable

Enabled by default Safe Supports autocorrection Version Added Version Changed

Enabled

Yes

No

-

-

Avoid class instance variables.

Examples

# bad
class User
  def self.notify(info)
    @info = validate(info)
    Notifier.new(@info).deliver
  end
end

class Model
  class << self
    def table_name(name)
      @table_name = name
    end
  end
end

class Host
  %i[uri port].each do |key|
    define_singleton_method("#{key}=") do |value|
      instance_variable_set("@#{key}", value)
    end
  end
end

module Example
  module ClassMethods
    def test(params)
      @params = params
    end
  end
end

module Example
  class_methods do
    def test(params)
      @params = params
    end
  end
end

module Example
  module_function

  def test(params)
    @params = params
  end
end

module Example
  def test(params)
    @params = params
  end

  module_function :test
end

ThreadSafety/DirChdir

Enabled by default Safe Supports autocorrection Version Added Version Changed

Enabled

Yes

No

-

-

Avoid using Dir.chdir due to its process-wide effect. If AllowCallWithBlock (disabled by default) option is enabled, calling Dir.chdir with block will be allowed.

Examples

# bad
Dir.chdir("/var/run")

# bad
FileUtils.chdir("/var/run")

AllowCallWithBlock: false (default)

# bad
Dir.chdir("/var/run") do
  puts Dir.pwd
end

AllowCallWithBlock: true

# good
Dir.chdir("/var/run") do
  puts Dir.pwd
end

Configurable attributes

Name Default value Configurable values

AllowCallWithBlock

false

Boolean

ThreadSafety/EnvMutation

Enabled by default Safe Supports autocorrection Version Added Version Changed

Disabled

Yes

No

0.8

-

Avoid mutating ENV.

Environment variables are process-wide. Mutating them can affect other threads, requests, jobs, or subprocesses running in the same Ruby process.

Examples

# bad
ENV['TZ'] = 'UTC'

# bad
ENV.update('FOO' => 'bar')

# good
system({ 'TZ' => 'UTC' }, 'date')

# good
ENV.fetch('TZ', 'UTC')

ThreadSafety/LazySynchronizationPrimitive

Enabled by default Safe Supports autocorrection Version Added Version Changed

Pending

Yes

No

0.8

-

Avoid lazily initializing synchronization primitives with ||=.

The check-then-set performed by ||= is not atomic, so concurrent threads can observe an uninitialized primitive or create more than one instance. Eagerly assign the primitive (for example in initialize) or use a constant.

Examples

# bad
def mutex
  @mutex ||= Mutex.new
end

# bad
def mutex = @mutex ||= Mutex.new

# good
def initialize
  @mutex = Mutex.new
end

ThreadSafety/MethodRedefinition

Enabled by default Safe Supports autocorrection Version Added Version Changed

Pending

Yes

No

0.8

-

Avoid the thread-unsafe combination of remove_method followed by defining a method with the same name. This can lead to a race condition, as these two actions are not atomic. As a safer alternative, consider aliasing the method to itself instead.

Examples

# bad
remove_method :foo
def foo; end

# good
alias_method :foo, :foo
def foo; end

# good
alias foo foo
def foo; end

ThreadSafety/MutableClassInstanceVariable

Enabled by default Safe Supports autocorrection Version Added Version Changed

Enabled

Yes

Always (Unsafe)

-

-

Checks whether some class instance variable isn’t a mutable literal (e.g. array or hash).

Class instance variables are a risk to threaded code as they are shared between threads. A mutable object such as an array or hash may be updated via an attr_reader so would not be detected by the ThreadSafety/ClassAndModuleAttributes cop.

Strict mode can be used to freeze all class instance variables, rather than just literals. Strict mode is considered an experimental feature. It has not been updated with an exhaustive list of all methods that will produce frozen objects so there is a decent chance of getting some false positives. Luckily, there is no harm in freezing an already frozen object.

Examples

EnforcedStyle: literals (default)

# bad
class Model
  @list = [1, 2, 3]
end

# good
class Model
  @list = [1, 2, 3].freeze
end

# good
class Model
  @var = <<~TESTING.freeze
    This is a heredoc
  TESTING
end

# good
class Model
  @var = Something.new
end

EnforcedStyle: strict

# bad
class Model
  @var = Something.new
end

# bad
class Model
  @var = Struct.new do
    def foo
      puts 1
    end
  end
end

# good
class Model
  @var = Something.new.freeze
end

# good
class Model
  @var = Struct.new do
    def foo
      puts 1
    end
  end.freeze
end

Configurable attributes

Name Default value Configurable values

EnforcedStyle

literals

literals, strict

ThreadSafety/NewThread

Enabled by default Safe Supports autocorrection Version Added Version Changed

Enabled

Yes

No

-

-

Avoid starting new threads.

Let a framework like Sidekiq handle the threads.

Examples

# bad
Thread.new { do_work }

ThreadSafety/RackMiddlewareInstanceVariable

Enabled by default Safe Supports autocorrection Version Added Version Changed

Enabled

Yes

No

-

-

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

Configurable attributes

Name Default value Configurable values

Include

app/middleware/**/*.rb, lib/middleware/**/*.rb, app/middlewares/**/*.rb, lib/middlewares/**/*.rb

Array

AllowedIdentifiers

[]

Array