thread_local_var_accessors 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +90 -0
- data/LICENSE +21 -0
- data/LICENSE.md +21 -0
- data/README.md +211 -0
- data/Rakefile +38 -0
- data/lib/thread_local_var_accessors/version.rb +3 -0
- data/lib/thread_local_var_accessors.rb +188 -0
- data/thread_local_var_accessors.gemspec +34 -0
- metadata +234 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1b6e9d88de11a15e80e03d531bbb506da4b962e057ee1e88f0eb56f79c0eca74
|
4
|
+
data.tar.gz: 919b3b1100b7d8683317869cda3dbd87672ba6e30a0a86c565fffd0cefadcc99
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2c1c294e53542491de381e69ff49b9b68942f6d5dc06ef8256f954dbec8af9c23396a4fd995d10701017b790c51880c6928c451c4e521c0b7cef80233af50aba
|
7
|
+
data.tar.gz: 740127f6247210b11f2ad478005eb459780afa6f50e451416b74a6ac35081d1ab818151b85bab786dd60031f71ee67cc01fc7b45423c454976f66834e000d0e0
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
thread_local_var_accessors (0.1.1)
|
5
|
+
concurrent-ruby
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
ast (2.4.2)
|
11
|
+
builder (3.2.4)
|
12
|
+
concurrent-ruby (1.2.2)
|
13
|
+
diff-lcs (1.5.0)
|
14
|
+
docile (1.4.0)
|
15
|
+
fuubar (2.5.1)
|
16
|
+
rspec-core (~> 3.0)
|
17
|
+
ruby-progressbar (~> 1.4)
|
18
|
+
json (2.6.3)
|
19
|
+
parallel (1.22.1)
|
20
|
+
parser (3.2.1.1)
|
21
|
+
ast (~> 2.4.1)
|
22
|
+
rainbow (3.1.1)
|
23
|
+
rake (13.0.6)
|
24
|
+
redcarpet (3.6.0)
|
25
|
+
regexp_parser (2.7.0)
|
26
|
+
rexml (3.2.5)
|
27
|
+
rspec (3.12.0)
|
28
|
+
rspec-core (~> 3.12.0)
|
29
|
+
rspec-expectations (~> 3.12.0)
|
30
|
+
rspec-mocks (~> 3.12.0)
|
31
|
+
rspec-core (3.12.1)
|
32
|
+
rspec-support (~> 3.12.0)
|
33
|
+
rspec-expectations (3.12.2)
|
34
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
35
|
+
rspec-support (~> 3.12.0)
|
36
|
+
rspec-mocks (3.12.4)
|
37
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
38
|
+
rspec-support (~> 3.12.0)
|
39
|
+
rspec-support (3.12.0)
|
40
|
+
rspec_junit (4.0.4)
|
41
|
+
builder (>= 3.2.2)
|
42
|
+
rspec (>= 3.3.0)
|
43
|
+
rspec_junit_formatter (0.6.0)
|
44
|
+
rspec-core (>= 2, < 4, != 2.12.0)
|
45
|
+
rubocop (1.48.1)
|
46
|
+
json (~> 2.3)
|
47
|
+
parallel (~> 1.10)
|
48
|
+
parser (>= 3.2.0.0)
|
49
|
+
rainbow (>= 2.2.2, < 4.0)
|
50
|
+
regexp_parser (>= 1.8, < 3.0)
|
51
|
+
rexml (>= 3.2.5, < 4.0)
|
52
|
+
rubocop-ast (>= 1.26.0, < 2.0)
|
53
|
+
ruby-progressbar (~> 1.7)
|
54
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
55
|
+
rubocop-ast (1.28.0)
|
56
|
+
parser (>= 3.2.1.0)
|
57
|
+
ruby-progressbar (1.13.0)
|
58
|
+
simplecov (0.22.0)
|
59
|
+
docile (~> 1.1)
|
60
|
+
simplecov-html (~> 0.11)
|
61
|
+
simplecov_json_formatter (~> 0.1)
|
62
|
+
simplecov-html (0.12.3)
|
63
|
+
simplecov_json_formatter (0.1.4)
|
64
|
+
spring (4.1.1)
|
65
|
+
terminal-notifier-guard (1.7.0)
|
66
|
+
unicode-display_width (2.4.2)
|
67
|
+
webrick (1.7.0)
|
68
|
+
yard (0.9.28)
|
69
|
+
webrick (~> 1.7.0)
|
70
|
+
|
71
|
+
PLATFORMS
|
72
|
+
x86_64-darwin-21
|
73
|
+
|
74
|
+
DEPENDENCIES
|
75
|
+
bundler
|
76
|
+
fuubar
|
77
|
+
rake
|
78
|
+
redcarpet
|
79
|
+
rspec
|
80
|
+
rspec_junit
|
81
|
+
rspec_junit_formatter
|
82
|
+
rubocop
|
83
|
+
simplecov
|
84
|
+
spring
|
85
|
+
terminal-notifier-guard
|
86
|
+
thread_local_var_accessors!
|
87
|
+
yard
|
88
|
+
|
89
|
+
BUNDLED WITH
|
90
|
+
2.4.6
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2023 Alan Stebbens
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/LICENSE.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# MIT LICENSE
|
2
|
+
|
3
|
+
Copyright (c) 2023 Alan Stebbens <aks@stebbens.org>
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,211 @@
|
|
1
|
+
# thread_local_var_accessors
|
2
|
+
|
3
|
+
Ruby gem to make `ThreadLocalVars` easy to use, with either "accessor" methods,
|
4
|
+
or instance methods.
|
5
|
+
|
6
|
+
Build Status: [](https://dl.circleci.com/status-badge/redirect/gh/aks/thread_local_var_accessors/tree/main)
|
7
|
+
|
8
|
+
## Description
|
9
|
+
|
10
|
+
This module has methods making it easy to use the `Concurrent::ThreadLocalVar`
|
11
|
+
as instance variables. This makes instance variables using this code as
|
12
|
+
actually thread-local, without also leaking memory over time.
|
13
|
+
|
14
|
+
See [Why Concurrent::ThreadLocalVar](https://github.com/ruby-concurrency/concurrent-ruby/blob/master/lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb#L10-L17) to understand why we use TLVs instead of `Thread.current.thread_variable_(set|get)`
|
15
|
+
|
16
|
+
### Class Methods
|
17
|
+
|
18
|
+
Just as Rails provides _attribute_ accessor methods, eg: `attr_accessor`,
|
19
|
+
`attr_reader`, and `attr_writer`, this module provides the following
|
20
|
+
class methods for declaring getter and setter methods based on instance
|
21
|
+
variables that use `ThreadLocalVar` (TLV) objects.
|
22
|
+
|
23
|
+
tlv_reader :var1, :var2, ...
|
24
|
+
tlv_writer :var3, :var4, ...
|
25
|
+
tlv_accessor :var5, :var6, ...
|
26
|
+
|
27
|
+
- `tlv_reader` creates an instance method with the name `name`, that references
|
28
|
+
the instance variable names '@name', which is expected to be either nil,
|
29
|
+
or already have a `Concurrent::ThreadLocalVar` instance.
|
30
|
+
|
31
|
+
- `tlv_writer` creates an instance method with the name `name=`, which accepts a single
|
32
|
+
argument that is the new value. This method checks for an existing value
|
33
|
+
on the instance variable named `@name`, which should be a
|
34
|
+
`Concurrent::ThreadLocalVar` instance. If `@name` value is nil, then a new
|
35
|
+
`Concurrent::ThreadLocalVar` instance is assigned to it. In either case, the
|
36
|
+
instance variable's TLV object is assigned the new value, which is returned.
|
37
|
+
|
38
|
+
- `tlv_accessor` - creates both a `tlv_reader` and a `tlv_writer`.
|
39
|
+
|
40
|
+
For reference, see [ThreadLocalVars](https://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/ThreadLocalVar.html).
|
41
|
+
|
42
|
+
### Instance Methods
|
43
|
+
|
44
|
+
With the accessor methods, obtaining values and setting values
|
45
|
+
becomes very simple:
|
46
|
+
|
47
|
+
tlv_accessor :timeout
|
48
|
+
...
|
49
|
+
timeout # fetches the current TLV value, unique to each thread
|
50
|
+
...
|
51
|
+
self.timeout = 0.5 # stores the TLV value just for this thread
|
52
|
+
|
53
|
+
Alternative ways to initialize:
|
54
|
+
|
55
|
+
tlv_set(:timeout, 0)
|
56
|
+
|
57
|
+
tlv_set(:timeout) # ensure that @timeout is initialized to an [TLV](TLV)
|
58
|
+
@timeout.value = 0
|
59
|
+
|
60
|
+
The following methods are used within the above reader, writer, accessor
|
61
|
+
methods:
|
62
|
+
|
63
|
+
tlv_get(name) - fetches the value of TLV `name`
|
64
|
+
tlv_set(name, value) - stores the value into the TLV `name`
|
65
|
+
|
66
|
+
There is a block form to `tls_set`:
|
67
|
+
|
68
|
+
tlv_set(name) { |old_val| new_val }
|
69
|
+
|
70
|
+
In addition, there is also a `tlv_set_once` method that can be used to set
|
71
|
+
a TLV value only if has not currently be set already.
|
72
|
+
|
73
|
+
tlv_set_once(name, value)
|
74
|
+
|
75
|
+
tlv_set_once(name) { |old_val| new_value }
|
76
|
+
|
77
|
+
For `tlv_accessor` instance variables, it's possible to use the assign operators, eg: `+=`, or `||=`.
|
78
|
+
For example:
|
79
|
+
|
80
|
+
tlv_accessor :timeout, :count
|
81
|
+
|
82
|
+
self.timeout ||= DEFAULT_TIMEOUT
|
83
|
+
|
84
|
+
self.count += 1
|
85
|
+
|
86
|
+
### TLV Name Arguments
|
87
|
+
|
88
|
+
The `name` argument to `tlv_get` and `tlv_set` is the same as given on
|
89
|
+
the accessor, reader, and writer methods: either a string or symbol name,
|
90
|
+
automatically converted as needed to instance-variable syntax (eg: :@name),
|
91
|
+
and setter-method name syntax (eg :name=).
|
92
|
+
|
93
|
+
|
94
|
+
## Installation
|
95
|
+
|
96
|
+
gem install thread_local_var_accessors
|
97
|
+
|
98
|
+
Within another app, within its `Gemfile` or `*.gemspec` file:
|
99
|
+
|
100
|
+
gem 'thread_local_var_accessors'
|
101
|
+
|
102
|
+
Then:
|
103
|
+
|
104
|
+
bundle install
|
105
|
+
|
106
|
+
## Usage
|
107
|
+
|
108
|
+
Use the class methods to declare instance getter and setter methods:
|
109
|
+
|
110
|
+
tlv_reader :name1
|
111
|
+
tlv_writer :name2
|
112
|
+
tlv_accessor :name3, :name4
|
113
|
+
|
114
|
+
The above invocations:
|
115
|
+
- create reader methods for `name1`, `name3`, and `name4`.
|
116
|
+
- create writer methods for `name2`, `name3`, and `name4`.
|
117
|
+
|
118
|
+
The writer methods accept a value as the second argument, or from the result of an associated block.
|
119
|
+
|
120
|
+
Note: to use the read-and-operate operators, eg: `+=`, `-=`, `||=`, etc., the object must have both a reader and writer method. In other words, it needs to have been created as an `tlv_accessor`.
|
121
|
+
|
122
|
+
When adapting legacy code to become thread-safe, it's sometimes necessary to use the underlying instance methods:
|
123
|
+
|
124
|
+
tlv_get(name)
|
125
|
+
tlv_set(name, value)
|
126
|
+
tlv_set_once(name, value)
|
127
|
+
|
128
|
+
Alternative block forms:
|
129
|
+
|
130
|
+
tlv_set(name) { |oldval| newval }
|
131
|
+
tlv_set_once(name) { |oldval| newval }
|
132
|
+
|
133
|
+
|
134
|
+
In all cases, the `name` can be a string or symbol, with or without a leading `@`.
|
135
|
+
|
136
|
+
### Example Usage
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
require 'thread_local_var_accessors'
|
140
|
+
|
141
|
+
class MyClass
|
142
|
+
include ThreadLocalVarAccessors
|
143
|
+
|
144
|
+
tlv_accessor :timeout, :max_time, :count, :limit
|
145
|
+
tlv_reader :sleep_time
|
146
|
+
tlv_writer :locked
|
147
|
+
|
148
|
+
def initialize(**args)
|
149
|
+
self.limit = args[:limit]
|
150
|
+
self.timeout = args[:timeout]
|
151
|
+
self.max_time = args[:max_time]
|
152
|
+
self.sleep_time = args[:sleep_time]
|
153
|
+
end
|
154
|
+
|
155
|
+
def run
|
156
|
+
while count < limit && delay_time < timeout
|
157
|
+
...
|
158
|
+
self.max_time ||= DEFAULT_MAX_TIME
|
159
|
+
...
|
160
|
+
sleep sleep_time
|
161
|
+
...
|
162
|
+
self.locked = true
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
```
|
167
|
+
|
168
|
+
There may be times where you may want to use the `tlv_`-prefix methods and not use the accessors.
|
169
|
+
|
170
|
+
The following are a set of equivalencies.
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
tlv_accessor :timeout
|
174
|
+
```
|
175
|
+
|
176
|
+
produces both the reader and writer methods, using the `tlv_get` and `tlv_set` methods.
|
177
|
+
|
178
|
+
```ruby
|
179
|
+
def timeout
|
180
|
+
tlv_get(:timeout)
|
181
|
+
end
|
182
|
+
|
183
|
+
def timeout=(val)
|
184
|
+
tlv_set(:timeout, val)
|
185
|
+
end
|
186
|
+
```
|
187
|
+
|
188
|
+
The advantage of the block method, especially for `tlv_set_once`, is that the
|
189
|
+
VALUE is only evaluated when the instance variable value is being set, and, the block will receive the old value as a parameter.
|
190
|
+
|
191
|
+
## Development
|
192
|
+
|
193
|
+
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.
|
194
|
+
|
195
|
+
For both development and testing, the environment variables described above must be defined.
|
196
|
+
|
197
|
+
## Testing
|
198
|
+
|
199
|
+
## Continuous Integration and Deployments
|
200
|
+
|
201
|
+
This repo is configured to the [gitflow](https://datasift.github.io/gitflow/IntroducingGitFlow.html) pattern, with the `develop` branch being the _default_ branch on PRs.
|
202
|
+
|
203
|
+
The `main` branch gets updated with a PR or with a manual merge-and-push from the `develop` branch by a repo admin.
|
204
|
+
|
205
|
+
When any branch is pushed, the continuous integration with causes the branch to be tested with all of the `rspec` tests _(except the integration tests)_.
|
206
|
+
|
207
|
+
When the `main` branch is updated and after its tests pass, the `deploy` action is invoked which causes the newest build of the gem to be pushed to rubygems.org.
|
208
|
+
|
209
|
+
## Original Author:
|
210
|
+
|
211
|
+
Alan K. Stebbens <aks@stebbens.org>
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# Rakefile for thread_local_var_accessors
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
require "rspec/core/rake_task"
|
4
|
+
require 'yard'
|
5
|
+
|
6
|
+
# Local CI testing
|
7
|
+
|
8
|
+
namespace :ci do
|
9
|
+
desc "Check CIRCLECI config"
|
10
|
+
task :check do
|
11
|
+
sh "circleci config validate", verbose: true
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "Run CIRCLECI config locally"
|
15
|
+
task :local do
|
16
|
+
sh "circleci local execute", verbose: true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# add spec unit tests
|
21
|
+
|
22
|
+
RSpec::Core::RakeTask.new(:spec)
|
23
|
+
|
24
|
+
namespace :spec do
|
25
|
+
desc "run Simplecov"
|
26
|
+
task :coverage do
|
27
|
+
sh 'CODE_COVERAGE=1 bundle exec rake spec'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# add yard task
|
32
|
+
|
33
|
+
YARD::Rake::YardocTask.new do |t|
|
34
|
+
t.files = ['README.md', 'lib/**/*.rb']
|
35
|
+
t.stats_options = ['--list-undoc']
|
36
|
+
end
|
37
|
+
|
38
|
+
task default: :spec
|
@@ -0,0 +1,188 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This module has methods making it easy to use the Concurrent::ThreadLocalVar
|
4
|
+
# as instance variables. This makes instance variables using this code as
|
5
|
+
# actually thread-local, without also leaking memory over time.
|
6
|
+
#
|
7
|
+
# See [Why Concurrent::ThreadLocalVar](https://github.com/ruby-concurrency/concurrent-ruby/blob/master/lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb#L10-L17)
|
8
|
+
# to understand why we use TLVs instead of `Thread.current.thread_variable_(set|get)`
|
9
|
+
#
|
10
|
+
# The following class methods declare `Concurrent::ThreadLocalVar` reader,
|
11
|
+
# writer, and accessors with these class methods:
|
12
|
+
#
|
13
|
+
# tlv_reader :var1, :var2, ...
|
14
|
+
# tlv_writer :var3, :var4, ...
|
15
|
+
# tlv_accessor :var5, :var6, ...
|
16
|
+
#
|
17
|
+
# - `tlv_reader` creates a method with the name `name`, that references
|
18
|
+
# the instance variable names '@name', which is expected to be either nil,
|
19
|
+
# or already have a `Concurrent::ThreadLocalVar` instance.
|
20
|
+
#
|
21
|
+
# - `tlv_writer` creates a method with the name `name=`, which accepts a single
|
22
|
+
# argument that is the new value. This method checks for an existing value
|
23
|
+
# on the instance variable named "@name", which should be a
|
24
|
+
# `Concurrent::ThreadLocalVar` instance. If `@name` value is nil, then a new
|
25
|
+
# `Concurrent::ThreadLocalVar` instance is assigned to it. In either case, the
|
26
|
+
# instance variable's TLV object is assigned the new value, which is returned.
|
27
|
+
#
|
28
|
+
# - `tlv_accessor` - creates both a `tlv_reader` and a `tlv_writer`.
|
29
|
+
#
|
30
|
+
# Just as with `attr_accessor` methods, obtaining values and setting values
|
31
|
+
# becomes very simple:
|
32
|
+
#
|
33
|
+
# tlv_accessor :timeout
|
34
|
+
# ...
|
35
|
+
# timeout # fetches the current TLV value, unique to each thread
|
36
|
+
# ...
|
37
|
+
# self.timeout = 0.5 # stores the TLV value just for this thread
|
38
|
+
#
|
39
|
+
# Alternative ways to initialize:
|
40
|
+
#
|
41
|
+
# ltv_set(:timeout, 0)
|
42
|
+
#
|
43
|
+
# ltv_set(:timeout) # ensure that @timeout is initialized to an LTV
|
44
|
+
# @timeout.value = 0
|
45
|
+
#
|
46
|
+
# The following methods are used within the above reader, writer, accessor
|
47
|
+
# methods:
|
48
|
+
#
|
49
|
+
# tlv_get(name) - fetches the value of TLV `name`
|
50
|
+
# tlv_set(name, value) - stores the value into the TLV `name`
|
51
|
+
#
|
52
|
+
# There is a block form to `tls_set`:
|
53
|
+
#
|
54
|
+
# tlv_set(name) { |old_val| new_val }
|
55
|
+
#
|
56
|
+
# The `name` argument to `tlv_get` and `tlv_set` is the same as given on
|
57
|
+
# the accessor, reader, writer methods: either a string or symbol name,
|
58
|
+
# automatically converted as needed to instance-variable syntax (eg: :@name),
|
59
|
+
# and setter-method name syntax (eg :name=).
|
60
|
+
#
|
61
|
+
# Example:
|
62
|
+
#
|
63
|
+
# tlv_accessor :timeout
|
64
|
+
#
|
65
|
+
# Creates reader and writer methods called "timeout", and "timeout=",
|
66
|
+
# respectively. Both of these methods interrogate the instance variable
|
67
|
+
# "@timeout", which is initialized (by `tlv_set`) to contain a
|
68
|
+
# `Concurrent::ThreadLocalVar.new` value.
|
69
|
+
#
|
70
|
+
# The writer methods support using attached blocks to receive the current
|
71
|
+
# value, if any, and should return the value to be stored.
|
72
|
+
#
|
73
|
+
# The `timeout` reader method would look like this:
|
74
|
+
#
|
75
|
+
# def timeout
|
76
|
+
# instance_variable_get(:@timeout)&.value
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# The 'timeout=' writer method would look like this:
|
80
|
+
#
|
81
|
+
# def timeout=(value)
|
82
|
+
# var = instance_variable_get(:@timeout) ||
|
83
|
+
# instance_variable_get(:@timeout, Concurrent::ThreadLocalVar.new)
|
84
|
+
# var.value = block_given? ? yield(var.value) : value
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
# Each thread referencing the instance variable, will get the same TLV object,
|
88
|
+
# but when the `.value` method is invoked, each thread will receive the initial
|
89
|
+
# value, or whatever local value may have been assigned subsequently.
|
90
|
+
#
|
91
|
+
# To obtain the value of such an TLV instance variable, do:
|
92
|
+
#
|
93
|
+
# @timeout.value
|
94
|
+
#
|
95
|
+
# To assign a new value to an TLV instance:
|
96
|
+
#
|
97
|
+
# @timeout.value = new_value
|
98
|
+
|
99
|
+
require 'concurrent-ruby'
|
100
|
+
|
101
|
+
# methods for making usage of ThreadLocalVars easy
|
102
|
+
module ThreadLocalVarAccessors
|
103
|
+
# @!visibility private
|
104
|
+
module MyRefinements
|
105
|
+
# allow to_sym to be called on either a String or a Symbol
|
106
|
+
refine Symbol do
|
107
|
+
# idempotent method: :symbol.to_sym => :symbol
|
108
|
+
def to_sym
|
109
|
+
self
|
110
|
+
end
|
111
|
+
|
112
|
+
# @return [Symbol] an instance variable; eg: :name => :@name.
|
113
|
+
# idempotent: returns instance variable names unchanged
|
114
|
+
def to_ivar
|
115
|
+
to_s.to_ivar
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
refine String do
|
120
|
+
# @return [Symbol] an instance variable; eg: "name" => :@name.
|
121
|
+
# idempotent: returns instance variable names unchanged
|
122
|
+
def to_ivar
|
123
|
+
(start_with?('@') ? self : "@#{self}").to_sym
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
using MyRefinements
|
128
|
+
|
129
|
+
module ClassMethods
|
130
|
+
def tlv_reader(*names)
|
131
|
+
names.each do |name|
|
132
|
+
define_method(name.to_sym) { tlv_get(name) }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# like attr_writer, but supports using block-values, which receive the
|
137
|
+
# current value, returning the new value
|
138
|
+
def tlv_writer(*names)
|
139
|
+
names.each do |name|
|
140
|
+
define_method("#{name}=".to_sym) do |new_value, &block|
|
141
|
+
tlv_set(name, new_value, &block)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def tlv_accessor(*names)
|
147
|
+
tlv_reader(*names)
|
148
|
+
tlv_writer(*names)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# instance methods
|
153
|
+
def tlv_get(name)
|
154
|
+
instance_variable_get(name.to_ivar)&.value
|
155
|
+
end
|
156
|
+
|
157
|
+
def tlv_set(name, value = nil, &block)
|
158
|
+
var = instance_variable_get(name.to_ivar) || tlv_new(name)
|
159
|
+
tlv_set_var(var, value, &block)
|
160
|
+
end
|
161
|
+
|
162
|
+
def tlv_set_once(name, value = nil, &block)
|
163
|
+
if (var = instance_variable_get(name.to_ivar)) && !var.value.nil?
|
164
|
+
var.value
|
165
|
+
elsif var # var is set, but its value is nil
|
166
|
+
tlv_set_var(var, value, &block)
|
167
|
+
else # var is not set
|
168
|
+
tlv_set_var(tlv_new(name), value, &block)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# @param [String|Symbol] name the TLV name
|
173
|
+
# @return [ThreadLocalVar] a new TLV set in the instance variable
|
174
|
+
def tlv_new(name)
|
175
|
+
instance_variable_set(name.to_ivar, Concurrent::ThreadLocalVar.new)
|
176
|
+
end
|
177
|
+
|
178
|
+
# @!visibility private
|
179
|
+
def self.included(base)
|
180
|
+
base.extend(ClassMethods)
|
181
|
+
end
|
182
|
+
|
183
|
+
private
|
184
|
+
|
185
|
+
def tlv_set_var(var, value)
|
186
|
+
var.value = block_given? ? yield(var.value) : value
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
$:.unshift File.expand_path('../lib', __FILE__)
|
4
|
+
require 'thread_local_var_accessors/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = 'thread_local_var_accessors'
|
8
|
+
s.version = ThreadLocalVarAccessors::VERSION
|
9
|
+
s.authors = ['Alan Stebbens']
|
10
|
+
s.email = ['aks@stebbens.org']
|
11
|
+
s.homepage = 'https://github.com/aks/thread_local_var_accessors'
|
12
|
+
s.licenses = ['MIT']
|
13
|
+
s.summary = 'Ruby gem to make ThreadLocalVars easy to use'
|
14
|
+
s.description = 'Provides methods to declare and use ThreadLocalVar instance variables'
|
15
|
+
|
16
|
+
s.files = Dir.glob('{bin/*,lib/**/*,[A-Z]*}')
|
17
|
+
s.platform = Gem::Platform::RUBY
|
18
|
+
s.require_paths = ['lib']
|
19
|
+
|
20
|
+
s.add_development_dependency 'bundler'
|
21
|
+
s.add_development_dependency 'fuubar'
|
22
|
+
s.add_development_dependency 'rake'
|
23
|
+
s.add_development_dependency 'redcarpet'
|
24
|
+
s.add_development_dependency 'rspec'
|
25
|
+
s.add_development_dependency 'rspec_junit'
|
26
|
+
s.add_development_dependency 'rspec_junit_formatter'
|
27
|
+
s.add_development_dependency 'rubocop'
|
28
|
+
s.add_development_dependency 'simplecov'
|
29
|
+
s.add_development_dependency 'spring'
|
30
|
+
s.add_development_dependency 'terminal-notifier-guard' if /Darwin/.match?(`uname -a`.strip)
|
31
|
+
s.add_development_dependency 'yard'
|
32
|
+
|
33
|
+
s.add_runtime_dependency 'concurrent-ruby'
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,234 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: thread_local_var_accessors
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alan Stebbens
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-03-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: fuubar
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: redcarpet
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec_junit
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec_junit_formatter
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rubocop
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: simplecov
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: spring
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: terminal-notifier-guard
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: yard
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: concurrent-ruby
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
type: :runtime
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
description: Provides methods to declare and use ThreadLocalVar instance variables
|
196
|
+
email:
|
197
|
+
- aks@stebbens.org
|
198
|
+
executables: []
|
199
|
+
extensions: []
|
200
|
+
extra_rdoc_files: []
|
201
|
+
files:
|
202
|
+
- Gemfile
|
203
|
+
- Gemfile.lock
|
204
|
+
- LICENSE
|
205
|
+
- LICENSE.md
|
206
|
+
- README.md
|
207
|
+
- Rakefile
|
208
|
+
- lib/thread_local_var_accessors.rb
|
209
|
+
- lib/thread_local_var_accessors/version.rb
|
210
|
+
- thread_local_var_accessors.gemspec
|
211
|
+
homepage: https://github.com/aks/thread_local_var_accessors
|
212
|
+
licenses:
|
213
|
+
- MIT
|
214
|
+
metadata: {}
|
215
|
+
post_install_message:
|
216
|
+
rdoc_options: []
|
217
|
+
require_paths:
|
218
|
+
- lib
|
219
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
220
|
+
requirements:
|
221
|
+
- - ">="
|
222
|
+
- !ruby/object:Gem::Version
|
223
|
+
version: '0'
|
224
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
225
|
+
requirements:
|
226
|
+
- - ">="
|
227
|
+
- !ruby/object:Gem::Version
|
228
|
+
version: '0'
|
229
|
+
requirements: []
|
230
|
+
rubygems_version: 3.1.6
|
231
|
+
signing_key:
|
232
|
+
specification_version: 4
|
233
|
+
summary: Ruby gem to make ThreadLocalVars easy to use
|
234
|
+
test_files: []
|