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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6186a05451917191f601a118b61cfd9e14fe59a4a187daa09af557450fee9935
4
- data.tar.gz: 2a161a1fabc1fd5cdee9d13b3cd7729e497a4e8c2862427d002baaa1943b0bf0
3
+ metadata.gz: fa732d8bbefbcfc25dfd2b8adbfcc84788d77d8a21992b152975d9ee92e2f105
4
+ data.tar.gz: f2d29b69ad8e94c9c4b5de205b7301d45ffb3fb3a1c69429af27a5000e7350c6
5
5
  SHA512:
6
- metadata.gz: aaa04443787ec115fa6dc9e8aa3b069fe2bf7967e38c9eec9ea5c00c9c3d32208dc161a5cb082888279dd183f25acf935a3ccd3aac854e78b1b727ccb3c9121e
7
- data.tar.gz: 7957032ddf36e744c0b1dd640bfcf1f3f9e1f899cb73bb1566c8e88ce858e008637a8e3502baaa266617100c89e62be8a852cea471a0c358de1e7c62b7fada86
6
+ metadata.gz: ec8b96d08f872297a64bbf558f93a27e09bbe61d2a06b7248fdb2a3896a7367c520eceb0ca7f1e88fdb26fc8a03e2eab1772794353b5e12a3724a3371360d689
7
+ data.tar.gz: 7479c57f327e71e7fbc7b263ea13af5e4e908d7a29849946329bae072ba454636dca72465b1c7dc64d9150064d5e61ca68a81eb45a4ea247a5af69027afd6fb7
@@ -1,26 +1,55 @@
1
1
  name: CI
2
2
 
3
- on: [push, pull_request]
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ pull_request:
4
8
 
5
9
  jobs:
6
- test:
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.5", "2.6", "2.7", "3.0", "3.1", "3.2", ruby-head, jruby-9.2, jruby-9.3]
14
- rubocop_version: ["0.90", "1.20"]
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
- - uses: actions/checkout@v3
19
- - name: Set up Ruby
20
- uses: ruby/setup-ruby@v1
21
- with:
22
- bundler-cache: true # 'bundle install' and cache gems
23
- ruby-version: ${{ matrix.ruby }}
24
- bundler: 2.3.26
25
- - name: Run tests
26
- run: bundle exec rspec
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
@@ -1,6 +1,10 @@
1
- name: Lint
1
+ name: Lint
2
2
 
3
- on: [push, pull_request]
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
- - uses: actions/checkout@v3
13
- - name: Set up Ruby
14
- uses: ruby/setup-ruby@v1
15
- with:
16
- bundler-cache: true # 'bundle install' and cache gems
17
- ruby-version: "2.7"
18
- - name: Run Rubocop
19
- run: bundle exec rubocop
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
@@ -1,2 +1,3 @@
1
1
  --color
2
2
  --require spec_helper
3
+ --warnings
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.5
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
- appraise 'rubocop-0.90' do
4
- gem 'rubocop', '~> 0.90.0'
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.20' do
8
- gem 'rubocop', '~> 1.20.0'
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-2023 CoverMyMeds
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/bbatsov/rubocop).
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,2 @@
1
+ renamed:
2
+ ThreadSafety/InstanceVariableInClassMethod: ThreadSafety/ClassInstanceVariable
@@ -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
+ |===