rubocop-thread_safety 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +43 -14
- data/.github/workflows/lint.yml +14 -10
- data/.rspec +1 -0
- data/.rubocop.yml +13 -11
- data/Appraisals +16 -4
- data/CHANGELOG.md +14 -0
- data/Gemfile +12 -1
- data/LICENSE.txt +1 -1
- data/README.md +3 -1
- data/Rakefile +14 -0
- data/config/default.yml +15 -0
- data/config/obsoletion.yml +2 -0
- data/docs/modules/ROOT/pages/cops.adoc +8 -0
- data/docs/modules/ROOT/pages/cops_threadsafety.adoc +361 -0
- data/gemfiles/rubocop_1.48.gemfile +20 -0
- data/gemfiles/rubocop_1.66.gemfile +19 -0
- data/lib/rubocop/cop/mixin/operation_with_threadsafe_result.rb +44 -0
- data/lib/rubocop/cop/thread_safety/class_and_module_attributes.rb +5 -2
- data/lib/rubocop/cop/thread_safety/{instance_variable_in_class_method.rb → class_instance_variable.rb} +67 -13
- data/lib/rubocop/cop/thread_safety/dir_chdir.rb +37 -0
- data/lib/rubocop/cop/thread_safety/mutable_class_instance_variable.rb +8 -41
- data/lib/rubocop/cop/thread_safety/new_thread.rb +3 -5
- data/lib/rubocop/cop/thread_safety/rack_middleware_instance_variable.rb +120 -0
- data/lib/rubocop/thread_safety/inject.rb +2 -2
- data/lib/rubocop/thread_safety/version.rb +1 -1
- data/lib/rubocop/thread_safety.rb +2 -0
- data/lib/rubocop-thread_safety.rb +5 -1
- data/rubocop-thread_safety.gemspec +9 -8
- data/tasks/cops_documentation.rake +46 -0
- metadata +22 -87
- data/.rubocop_todo.yml +0 -12
- data/gemfiles/rubocop_0.90.gemfile +0 -7
- data/gemfiles/rubocop_1.20.gemfile +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fa732d8bbefbcfc25dfd2b8adbfcc84788d77d8a21992b152975d9ee92e2f105
|
4
|
+
data.tar.gz: f2d29b69ad8e94c9c4b5de205b7301d45ffb3fb3a1c69429af27a5000e7350c6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ec8b96d08f872297a64bbf558f93a27e09bbe61d2a06b7248fdb2a3896a7367c520eceb0ca7f1e88fdb26fc8a03e2eab1772794353b5e12a3724a3371360d689
|
7
|
+
data.tar.gz: 7479c57f327e71e7fbc7b263ea13af5e4e908d7a29849946329bae072ba454636dca72465b1c7dc64d9150064d5e61ca68a81eb45a4ea247a5af69027afd6fb7
|
data/.github/workflows/ci.yml
CHANGED
@@ -1,26 +1,55 @@
|
|
1
1
|
name: CI
|
2
2
|
|
3
|
-
on:
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- master
|
7
|
+
pull_request:
|
4
8
|
|
5
9
|
jobs:
|
6
|
-
|
7
|
-
|
10
|
+
confirm_documentation:
|
8
11
|
runs-on: ubuntu-latest
|
12
|
+
name: Confirm documentation
|
13
|
+
steps:
|
14
|
+
- uses: actions/checkout@v4
|
15
|
+
- uses: ruby/setup-ruby@v1
|
16
|
+
with:
|
17
|
+
ruby-version: "3.2"
|
18
|
+
bundler-cache: true
|
19
|
+
- run: bundle exec rake documentation_syntax_check confirm_documentation
|
9
20
|
|
21
|
+
test:
|
22
|
+
runs-on: ubuntu-latest
|
10
23
|
strategy:
|
11
24
|
fail-fast: false
|
12
25
|
matrix:
|
13
|
-
ruby: ["2.
|
14
|
-
rubocop_version: ["
|
26
|
+
ruby: ["2.7", "3.0", "3.1", "3.2", "3.3", ruby-head, jruby-9.4]
|
27
|
+
rubocop_version: ["1.48", "1.66"]
|
15
28
|
env:
|
16
29
|
BUNDLE_GEMFILE: "gemfiles/rubocop_${{ matrix.rubocop_version }}.gemfile"
|
17
30
|
steps:
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
31
|
+
- uses: actions/checkout@v4
|
32
|
+
- name: Set up Ruby
|
33
|
+
uses: ruby/setup-ruby@v1
|
34
|
+
with:
|
35
|
+
bundler-cache: true # 'bundle install' and cache gems
|
36
|
+
ruby-version: ${{ matrix.ruby }}
|
37
|
+
bundler: 2.3.26
|
38
|
+
- name: Run tests
|
39
|
+
run: bundle exec rspec
|
40
|
+
|
41
|
+
test-prism:
|
42
|
+
runs-on: ubuntu-latest
|
43
|
+
env:
|
44
|
+
BUNDLE_GEMFILE: "gemfiles/rubocop_1.66.gemfile"
|
45
|
+
PARSER_ENGINE: parser_prism
|
46
|
+
steps:
|
47
|
+
- uses: actions/checkout@v4
|
48
|
+
- name: Set up Ruby
|
49
|
+
uses: ruby/setup-ruby@v1
|
50
|
+
with:
|
51
|
+
bundler-cache: true # 'bundle install' and cache gems
|
52
|
+
ruby-version: 3.3
|
53
|
+
bundler: 2.3.26
|
54
|
+
- name: Run tests
|
55
|
+
run: bundle exec rspec
|
data/.github/workflows/lint.yml
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
-
name: Lint
|
1
|
+
name: Lint
|
2
2
|
|
3
|
-
on:
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- master
|
7
|
+
pull_request:
|
4
8
|
|
5
9
|
jobs:
|
6
10
|
lint:
|
@@ -9,11 +13,11 @@ jobs:
|
|
9
13
|
name: Rubocop
|
10
14
|
|
11
15
|
steps:
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
- uses: actions/checkout@v4
|
17
|
+
- name: Set up Ruby
|
18
|
+
uses: ruby/setup-ruby@v1
|
19
|
+
with:
|
20
|
+
bundler-cache: true # 'bundle install' and cache gems
|
21
|
+
ruby-version: "2.7"
|
22
|
+
- name: Run Rubocop
|
23
|
+
run: bundle exec rubocop
|
data/.rspec
CHANGED
data/.rubocop.yml
CHANGED
@@ -1,11 +1,12 @@
|
|
1
|
-
inherit_from: .rubocop_todo.yml
|
2
|
-
|
3
1
|
require:
|
4
2
|
- rubocop/cop/internal_affairs
|
3
|
+
- rubocop-rake
|
4
|
+
- rubocop-rspec
|
5
5
|
|
6
6
|
AllCops:
|
7
7
|
DisplayCopNames: true
|
8
|
-
TargetRubyVersion: 2.
|
8
|
+
TargetRubyVersion: 2.7
|
9
|
+
NewCops: enable
|
9
10
|
|
10
11
|
Lint/RaiseException:
|
11
12
|
Enabled: true
|
@@ -40,10 +41,6 @@ Style/FormatStringToken:
|
|
40
41
|
Exclude:
|
41
42
|
- spec/**/*
|
42
43
|
|
43
|
-
Style/FrozenStringLiteralComment:
|
44
|
-
Exclude:
|
45
|
-
- "gemfiles/*.gemfile"
|
46
|
-
|
47
44
|
Style/HashEachMethods:
|
48
45
|
Enabled: true
|
49
46
|
|
@@ -68,12 +65,17 @@ Style/OptionHash:
|
|
68
65
|
Style/Send:
|
69
66
|
Enabled: true
|
70
67
|
|
71
|
-
Style/StringLiterals:
|
72
|
-
Exclude:
|
73
|
-
- "gemfiles/*.gemfile"
|
74
|
-
|
75
68
|
Style/StringMethods:
|
76
69
|
Enabled: true
|
77
70
|
|
78
71
|
Style/SymbolArray:
|
79
72
|
Enabled: true
|
73
|
+
|
74
|
+
RSpec/ExampleLength:
|
75
|
+
Max: 11
|
76
|
+
Exclude:
|
77
|
+
- spec/rubocop/cop/thread_safety/rack_middleware_instance_variable_spec.rb
|
78
|
+
|
79
|
+
RSpec/ContextWording:
|
80
|
+
Exclude:
|
81
|
+
- spec/shared_contexts.rb
|
data/Appraisals
CHANGED
@@ -1,9 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
customize_gemfiles do
|
4
|
+
{
|
5
|
+
single_quotes: true,
|
6
|
+
heading: <<~HEADING.strip
|
7
|
+
frozen_string_literal: true
|
8
|
+
|
9
|
+
This file was generated by Appraisal
|
10
|
+
HEADING
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
appraise 'rubocop-1.48' do
|
15
|
+
gem 'base64', '~> 0.1.1'
|
16
|
+
gem 'rubocop', '~> 1.48.0'
|
5
17
|
end
|
6
18
|
|
7
|
-
appraise 'rubocop-1.
|
8
|
-
gem 'rubocop', '~> 1.
|
19
|
+
appraise 'rubocop-1.66' do
|
20
|
+
gem 'rubocop', '~> 1.66.0'
|
9
21
|
end
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# Change log
|
2
|
+
|
3
|
+
## 0.6.0
|
4
|
+
|
5
|
+
* [#59](https://github.com/rubocop/rubocop-thread_safety/pull/59): Rename `ThreadSafety::InstanceVariableInClassMethod` cop to `ThreadSafety::ClassInstanceVariable` to better reflect its purpose. ([@viralpraxis](https://github.com/viralpraxis))
|
6
|
+
* [#55](https://github.com/rubocop/rubocop-thread_safety/pull/55): Enhance `ThreadSafety::InstanceVariableInClassMethod` cop to detect offenses within `class_eval/exec` blocks. ([@viralpraxis](https://github.com/viralpraxis))
|
7
|
+
* [#54](https://github.com/rubocop/rubocop-thread_safety/pull/54): Drop support for RuboCop older than 1.48. ([@viralpraxis](https://github.com/viralpraxis))
|
8
|
+
* [#52](https://github.com/rubocop/rubocop-thread_safety/pull/52): Add new `RackMiddlewareInstanceVariable` cop to detect instance variables in Rack middleware. ([@viralpraxis](https://github.com/viralpraxis))
|
9
|
+
* [#48](https://github.com/rubocop/rubocop-thread_safety/pull/48): Do not report instance variables in `ActionDispatch` callbacks in singleton methods. ([@viralpraxis](https://github.com/viralpraxis))
|
10
|
+
* [#43](https://github.com/rubocop/rubocop-thread_safety/pull/43): Make detection of ActiveSupport's `class_attribute` configurable. ([@viralpraxis](https://github.com/viralpraxis))
|
11
|
+
* [#42](https://github.com/rubocop/rubocop-thread_safety/pull/42): Fix some `InstanceVariableInClassMethod` cop false positive offenses. ([@viralpraxis](https://github.com/viralpraxis))
|
12
|
+
* [#41](https://github.com/rubocop/rubocop-thread_safety/pull/41): Drop support for MRI older than 2.7. ([@viralpraxis](https://github.com/viralpraxis))
|
13
|
+
* [#38](https://github.com/rubocop/rubocop-thread_safety/pull/38): Fix `NewThread` cop detection is case of `Thread.start`, `Thread.fork`, or `Thread.new` with arguments. ([@viralpraxis](https://github.com/viralpraxis))
|
14
|
+
* [#36](https://github.com/rubocop/rubocop-thread_safety/pull/36): Add new `DirChdir` cop to detect `Dir.chdir` calls. ([@viralpraxis](https://github.com/viralpraxis))
|
data/Gemfile
CHANGED
@@ -2,5 +2,16 @@
|
|
2
2
|
|
3
3
|
source 'https://rubygems.org'
|
4
4
|
|
5
|
-
# Specify your gem's dependencies in rubocop-thread_safety.gemspec
|
6
5
|
gemspec
|
6
|
+
|
7
|
+
gem 'appraisal'
|
8
|
+
gem 'bundler', '>= 1.10', '< 3'
|
9
|
+
gem 'prism', '~> 1.2.0'
|
10
|
+
gem 'pry' unless ENV['CI']
|
11
|
+
gem 'rake', '>= 10.0'
|
12
|
+
gem 'rspec', '~> 3.0'
|
13
|
+
gem 'rubocop', github: 'rubocop/rubocop'
|
14
|
+
gem 'rubocop-rake', '~> 0.6.0'
|
15
|
+
gem 'rubocop-rspec'
|
16
|
+
gem 'simplecov'
|
17
|
+
gem 'yard'
|
data/LICENSE.txt
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
Portions Copyright 2016-2023 Michael Gee and contributors
|
2
|
-
Portions Copyright 2016-
|
2
|
+
Portions Copyright 2016-2022 CoverMyMeds
|
3
3
|
|
4
4
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
5
5
|
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# RuboCop::ThreadSafety
|
2
2
|
|
3
3
|
Thread-safety analysis for your projects, as an extension to
|
4
|
-
[RuboCop](https://github.com/
|
4
|
+
[RuboCop](https://github.com/rubocop/rubocop).
|
5
5
|
|
6
6
|
## Installation and Usage
|
7
7
|
|
@@ -60,6 +60,8 @@ Improvements that would make shared state thread-safe include:
|
|
60
60
|
* Use [`RequestStore`](https://github.com/steveklabnik/request_store)
|
61
61
|
* Use `Thread.current[:name]`
|
62
62
|
|
63
|
+
Certain system calls, such as `chdir`, affect the entire process. To avoid potential thread-safety issues, it's preferable to use (if possible) the `chdir` option in methods like `Kernel.system` and `IO.popen` rather than relying on `Dir.chdir`.
|
64
|
+
|
63
65
|
## Development
|
64
66
|
|
65
67
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/Rakefile
CHANGED
@@ -1,8 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'bundler/gem_tasks'
|
4
|
+
require 'open3'
|
4
5
|
require 'rspec/core/rake_task'
|
5
6
|
|
7
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
8
|
+
|
6
9
|
RSpec::Core::RakeTask.new(:spec)
|
7
10
|
|
11
|
+
desc 'Confirm documentation is up to date'
|
12
|
+
task confirm_documentation: :generate_cops_documentation do
|
13
|
+
_, _, _, process =
|
14
|
+
Open3.popen3('git diff --exit-code docs/')
|
15
|
+
|
16
|
+
unless process.value.success?
|
17
|
+
abort 'Please run `rake generate_cops_documentation` ' \
|
18
|
+
'and add docs/ to the commit.'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
8
22
|
task default: :spec
|
data/config/default.yml
CHANGED
@@ -5,6 +5,7 @@
|
|
5
5
|
ThreadSafety/ClassAndModuleAttributes:
|
6
6
|
Description: 'Avoid mutating class and module attributes.'
|
7
7
|
Enabled: true
|
8
|
+
ActiveSupportClassAttributeAllowed: false
|
8
9
|
|
9
10
|
ThreadSafety/InstanceVariableInClassMethod:
|
10
11
|
Description: 'Avoid using instance variables in class methods.'
|
@@ -30,3 +31,17 @@ ThreadSafety/NewThread:
|
|
30
31
|
Avoid starting new threads.
|
31
32
|
Let a framework like Sidekiq handle the threads.
|
32
33
|
Enabled: true
|
34
|
+
|
35
|
+
ThreadSafety/DirChdir:
|
36
|
+
Description: Avoid using `Dir.chdir` due to its process-wide effect.
|
37
|
+
Enabled: true
|
38
|
+
|
39
|
+
ThreadSafety/RackMiddlewareInstanceVariable:
|
40
|
+
Description: Avoid instance variables in Rack middleware.
|
41
|
+
Enabled: true
|
42
|
+
Include:
|
43
|
+
- 'app/middleware/**/*.rb'
|
44
|
+
- 'lib/middleware/**/*.rb'
|
45
|
+
- 'app/middlewares/**/*.rb'
|
46
|
+
- 'lib/middlewares/**/*.rb'
|
47
|
+
AllowedIdentifiers: []
|
@@ -0,0 +1,8 @@
|
|
1
|
+
=== Department xref:cops_threadsafety.adoc[ThreadSafety]
|
2
|
+
|
3
|
+
* xref:cops_threadsafety.adoc#threadsafetyclassandmoduleattributes[ThreadSafety/ClassAndModuleAttributes]
|
4
|
+
* xref:cops_threadsafety.adoc#threadsafetyclassinstancevariable[ThreadSafety/ClassInstanceVariable]
|
5
|
+
* xref:cops_threadsafety.adoc#threadsafetymutableclassinstancevariable[ThreadSafety/MutableClassInstanceVariable]
|
6
|
+
* xref:cops_threadsafety.adoc#threadsafetynewthread[ThreadSafety/NewThread]
|
7
|
+
* xref:cops_threadsafety.adoc#threadsafetydirchdir[ThreadSafety/DirChdir]
|
8
|
+
* xref:cops_threadsafety.adoc#threadsafetyrackmiddlewareinstancevariable[ThreadSafety/RackMiddlewareInstanceVariable]
|
@@ -0,0 +1,361 @@
|
|
1
|
+
////
|
2
|
+
Do NOT edit this file by hand directly, as it is automatically generated.
|
3
|
+
|
4
|
+
Please make any necessary changes to the cop documentation within the source files themselves.
|
5
|
+
////
|
6
|
+
|
7
|
+
= ThreadSafety
|
8
|
+
|
9
|
+
[#threadsafetyclassandmoduleattributes]
|
10
|
+
== ThreadSafety/ClassAndModuleAttributes
|
11
|
+
|
12
|
+
|===
|
13
|
+
| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed
|
14
|
+
|
15
|
+
| Enabled
|
16
|
+
| Yes
|
17
|
+
| No
|
18
|
+
| -
|
19
|
+
| -
|
20
|
+
|===
|
21
|
+
|
22
|
+
Avoid mutating class and module attributes.
|
23
|
+
|
24
|
+
They are implemented by class variables, which are not thread-safe.
|
25
|
+
|
26
|
+
[#examples-threadsafetyclassandmoduleattributes]
|
27
|
+
=== Examples
|
28
|
+
|
29
|
+
[source,ruby]
|
30
|
+
----
|
31
|
+
# bad
|
32
|
+
class User
|
33
|
+
cattr_accessor :current_user
|
34
|
+
end
|
35
|
+
----
|
36
|
+
|
37
|
+
[#configurable-attributes-threadsafetyclassandmoduleattributes]
|
38
|
+
=== Configurable attributes
|
39
|
+
|
40
|
+
|===
|
41
|
+
| Name | Default value | Configurable values
|
42
|
+
|
43
|
+
| ActiveSupportClassAttributeAllowed
|
44
|
+
| `false`
|
45
|
+
| Boolean
|
46
|
+
|===
|
47
|
+
|
48
|
+
[#threadsafetyclassinstancevariable]
|
49
|
+
== ThreadSafety/ClassInstanceVariable
|
50
|
+
|
51
|
+
|===
|
52
|
+
| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed
|
53
|
+
|
54
|
+
| Enabled
|
55
|
+
| Yes
|
56
|
+
| No
|
57
|
+
| -
|
58
|
+
| -
|
59
|
+
|===
|
60
|
+
|
61
|
+
Avoid class instance variables.
|
62
|
+
|
63
|
+
[#examples-threadsafetyclassinstancevariable]
|
64
|
+
=== Examples
|
65
|
+
|
66
|
+
[source,ruby]
|
67
|
+
----
|
68
|
+
# bad
|
69
|
+
class User
|
70
|
+
def self.notify(info)
|
71
|
+
@info = validate(info)
|
72
|
+
Notifier.new(@info).deliver
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class Model
|
77
|
+
class << self
|
78
|
+
def table_name(name)
|
79
|
+
@table_name = name
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class Host
|
85
|
+
%i[uri port].each do |key|
|
86
|
+
define_singleton_method("#{key}=") do |value|
|
87
|
+
instance_variable_set("@#{key}", value)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
module Example
|
93
|
+
module ClassMethods
|
94
|
+
def test(params)
|
95
|
+
@params = params
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
module Example
|
101
|
+
class_methods do
|
102
|
+
def test(params)
|
103
|
+
@params = params
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
module Example
|
109
|
+
module_function
|
110
|
+
|
111
|
+
def test(params)
|
112
|
+
@params = params
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
module Example
|
117
|
+
def test(params)
|
118
|
+
@params = params
|
119
|
+
end
|
120
|
+
|
121
|
+
module_function :test
|
122
|
+
end
|
123
|
+
----
|
124
|
+
|
125
|
+
[#threadsafetydirchdir]
|
126
|
+
== ThreadSafety/DirChdir
|
127
|
+
|
128
|
+
|===
|
129
|
+
| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed
|
130
|
+
|
131
|
+
| Enabled
|
132
|
+
| Yes
|
133
|
+
| No
|
134
|
+
| -
|
135
|
+
| -
|
136
|
+
|===
|
137
|
+
|
138
|
+
Avoid using `Dir.chdir` due to its process-wide effect.
|
139
|
+
|
140
|
+
[#examples-threadsafetydirchdir]
|
141
|
+
=== Examples
|
142
|
+
|
143
|
+
[source,ruby]
|
144
|
+
----
|
145
|
+
# bad
|
146
|
+
Dir.chdir("/var/run")
|
147
|
+
|
148
|
+
# bad
|
149
|
+
FileUtils.chdir("/var/run")
|
150
|
+
----
|
151
|
+
|
152
|
+
[#threadsafetymutableclassinstancevariable]
|
153
|
+
== ThreadSafety/MutableClassInstanceVariable
|
154
|
+
|
155
|
+
|===
|
156
|
+
| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed
|
157
|
+
|
158
|
+
| Enabled
|
159
|
+
| Yes
|
160
|
+
| Always (Unsafe)
|
161
|
+
| -
|
162
|
+
| -
|
163
|
+
|===
|
164
|
+
|
165
|
+
Checks whether some class instance variable isn't a
|
166
|
+
mutable literal (e.g. array or hash).
|
167
|
+
|
168
|
+
It is based on Style/MutableConstant from RuboCop.
|
169
|
+
See https://github.com/rubocop/rubocop/blob/master/lib/rubocop/cop/style/mutable_constant.rb
|
170
|
+
|
171
|
+
Class instance variables are a risk to threaded code as they are shared
|
172
|
+
between threads. A mutable object such as an array or hash may be
|
173
|
+
updated via an attr_reader so would not be detected by the
|
174
|
+
ThreadSafety/ClassAndModuleAttributes cop.
|
175
|
+
|
176
|
+
Strict mode can be used to freeze all class instance variables, rather
|
177
|
+
than just literals.
|
178
|
+
Strict mode is considered an experimental feature. It has not been
|
179
|
+
updated with an exhaustive list of all methods that will produce frozen
|
180
|
+
objects so there is a decent chance of getting some false positives.
|
181
|
+
Luckily, there is no harm in freezing an already frozen object.
|
182
|
+
|
183
|
+
[#examples-threadsafetymutableclassinstancevariable]
|
184
|
+
=== Examples
|
185
|
+
|
186
|
+
[#enforcedstyle_-literals-_default_-threadsafetymutableclassinstancevariable]
|
187
|
+
==== EnforcedStyle: literals (default)
|
188
|
+
|
189
|
+
[source,ruby]
|
190
|
+
----
|
191
|
+
# bad
|
192
|
+
class Model
|
193
|
+
@list = [1, 2, 3]
|
194
|
+
end
|
195
|
+
|
196
|
+
# good
|
197
|
+
class Model
|
198
|
+
@list = [1, 2, 3].freeze
|
199
|
+
end
|
200
|
+
|
201
|
+
# good
|
202
|
+
class Model
|
203
|
+
@var = <<~TESTING.freeze
|
204
|
+
This is a heredoc
|
205
|
+
TESTING
|
206
|
+
end
|
207
|
+
|
208
|
+
# good
|
209
|
+
class Model
|
210
|
+
@var = Something.new
|
211
|
+
end
|
212
|
+
----
|
213
|
+
|
214
|
+
[#enforcedstyle_-strict-threadsafetymutableclassinstancevariable]
|
215
|
+
==== EnforcedStyle: strict
|
216
|
+
|
217
|
+
[source,ruby]
|
218
|
+
----
|
219
|
+
# bad
|
220
|
+
class Model
|
221
|
+
@var = Something.new
|
222
|
+
end
|
223
|
+
|
224
|
+
# bad
|
225
|
+
class Model
|
226
|
+
@var = Struct.new do
|
227
|
+
def foo
|
228
|
+
puts 1
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# good
|
234
|
+
class Model
|
235
|
+
@var = Something.new.freeze
|
236
|
+
end
|
237
|
+
|
238
|
+
# good
|
239
|
+
class Model
|
240
|
+
@var = Struct.new do
|
241
|
+
def foo
|
242
|
+
puts 1
|
243
|
+
end
|
244
|
+
end.freeze
|
245
|
+
end
|
246
|
+
----
|
247
|
+
|
248
|
+
[#configurable-attributes-threadsafetymutableclassinstancevariable]
|
249
|
+
=== Configurable attributes
|
250
|
+
|
251
|
+
|===
|
252
|
+
| Name | Default value | Configurable values
|
253
|
+
|
254
|
+
| EnforcedStyle
|
255
|
+
| `literals`
|
256
|
+
| `literals`, `strict`
|
257
|
+
|===
|
258
|
+
|
259
|
+
[#threadsafetynewthread]
|
260
|
+
== ThreadSafety/NewThread
|
261
|
+
|
262
|
+
|===
|
263
|
+
| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed
|
264
|
+
|
265
|
+
| Enabled
|
266
|
+
| Yes
|
267
|
+
| No
|
268
|
+
| -
|
269
|
+
| -
|
270
|
+
|===
|
271
|
+
|
272
|
+
Avoid starting new threads.
|
273
|
+
|
274
|
+
Let a framework like Sidekiq handle the threads.
|
275
|
+
|
276
|
+
[#examples-threadsafetynewthread]
|
277
|
+
=== Examples
|
278
|
+
|
279
|
+
[source,ruby]
|
280
|
+
----
|
281
|
+
# bad
|
282
|
+
Thread.new { do_work }
|
283
|
+
----
|
284
|
+
|
285
|
+
[#threadsafetyrackmiddlewareinstancevariable]
|
286
|
+
== ThreadSafety/RackMiddlewareInstanceVariable
|
287
|
+
|
288
|
+
|===
|
289
|
+
| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed
|
290
|
+
|
291
|
+
| Enabled
|
292
|
+
| Yes
|
293
|
+
| No
|
294
|
+
| -
|
295
|
+
| -
|
296
|
+
|===
|
297
|
+
|
298
|
+
Avoid instance variables in rack middleware.
|
299
|
+
|
300
|
+
Middlewares are initialized once, meaning any instance variables are shared between executor threads.
|
301
|
+
To avoid potential race conditions, it's recommended to design middlewares to be stateless
|
302
|
+
or to implement proper synchronization mechanisms.
|
303
|
+
|
304
|
+
[#examples-threadsafetyrackmiddlewareinstancevariable]
|
305
|
+
=== Examples
|
306
|
+
|
307
|
+
[source,ruby]
|
308
|
+
----
|
309
|
+
# bad
|
310
|
+
class CounterMiddleware
|
311
|
+
def initialize(app)
|
312
|
+
@app = app
|
313
|
+
@counter = 0
|
314
|
+
end
|
315
|
+
|
316
|
+
def call(env)
|
317
|
+
app.call(env)
|
318
|
+
ensure
|
319
|
+
@counter += 1
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
# good
|
324
|
+
class CounterMiddleware
|
325
|
+
def initialize(app)
|
326
|
+
@app = app
|
327
|
+
@counter = Concurrent::AtomicReference.new(0)
|
328
|
+
end
|
329
|
+
|
330
|
+
def call(env)
|
331
|
+
app.call(env)
|
332
|
+
ensure
|
333
|
+
@counter.update { |ref| ref + 1 }
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
class IdentityMiddleware
|
338
|
+
def initialize(app)
|
339
|
+
@app = app
|
340
|
+
end
|
341
|
+
|
342
|
+
def call(env)
|
343
|
+
app.call(env)
|
344
|
+
end
|
345
|
+
end
|
346
|
+
----
|
347
|
+
|
348
|
+
[#configurable-attributes-threadsafetyrackmiddlewareinstancevariable]
|
349
|
+
=== Configurable attributes
|
350
|
+
|
351
|
+
|===
|
352
|
+
| Name | Default value | Configurable values
|
353
|
+
|
354
|
+
| Include
|
355
|
+
| `+app/middleware/**/*.rb+`, `+lib/middleware/**/*.rb+`, `+app/middlewares/**/*.rb+`, `+lib/middlewares/**/*.rb+`
|
356
|
+
| Array
|
357
|
+
|
358
|
+
| AllowedIdentifiers
|
359
|
+
| `[]`
|
360
|
+
| Array
|
361
|
+
|===
|