thread_local_var_accessors 0.1.2 → 1.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f567c363dc63194dc30aeae5d661ea14c871f9f64cc65ed3d2c2947447d1fc91
4
- data.tar.gz: 30c3bc0763221a483de2370af6843c83b39ff6429f27f1b6ad796931fa9dae51
3
+ metadata.gz: 939ace98d853409e91249ccd6a34a1acfa707f77293984c9143e4acee3e011a1
4
+ data.tar.gz: bd876354d8cae177ee222d6fbe7110f417013485d568ce3bd031aa068c752883
5
5
  SHA512:
6
- metadata.gz: 59329574532e6730a71d3f44d011bedae930ee29a0b592f9ff8fbf7ed6d824a13cb65ab9f51edb40df372615c5975f459bd483fc467f5914af48f992a21f7cfc
7
- data.tar.gz: 822c3f467f172562efdab7a38cc73063b42b8937f09d71ad01345c52d80fa69bc438a60067756386aa8d4d8ba74055513cd4303875a3784b13a5d07e3120adcc
6
+ metadata.gz: 6a3c026cd82bf5676d412ae88a799645afa5e5f3c9a85b552489656a8c1a9340435dfbd8ab1e00c0400d28f4792c009bb7f19d100f7a6792d77be71fe7ba3a04
7
+ data.tar.gz: 72001afd55e3c86e9da161f8d596fb148dd33a009a33ca72cc126425a59f6e8238adab52f2360de4e14b29c037f44aa2c728e01456b8dc0c3f22253d242e5fc6
data/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ 2023-05-04: Version 1.0.0
2
+ - added support for default values:
3
+ - renamed `tlv_new` to `tlv_init` (leaving `tlv_new` as an alias)
4
+ - added new instance methods: `tlv_default`, `tlv_set_default`
5
+ - updated docs to explain how defaults are cross-threads
6
+ - updated the README.md
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- thread_local_var_accessors (0.1.2)
4
+ thread_local_var_accessors (1.1.0)
5
5
  concurrent-ruby
6
6
 
7
7
  GEM
@@ -9,6 +9,8 @@ GEM
9
9
  specs:
10
10
  ast (2.4.2)
11
11
  builder (3.2.4)
12
+ byebug (11.1.3)
13
+ coderay (1.1.3)
12
14
  concurrent-ruby (1.2.2)
13
15
  diff-lcs (1.5.0)
14
16
  docile (1.4.0)
@@ -16,9 +18,16 @@ GEM
16
18
  rspec-core (~> 3.0)
17
19
  ruby-progressbar (~> 1.4)
18
20
  json (2.6.3)
21
+ method_source (1.0.0)
19
22
  parallel (1.22.1)
20
23
  parser (3.2.1.1)
21
24
  ast (~> 2.4.1)
25
+ pry (0.14.2)
26
+ coderay (~> 1.1)
27
+ method_source (~> 1.0)
28
+ pry-byebug (3.10.1)
29
+ byebug (~> 11.0)
30
+ pry (>= 0.13, < 0.15)
22
31
  rainbow (3.1.1)
23
32
  rake (13.0.6)
24
33
  redcarpet (3.6.0)
@@ -74,6 +83,7 @@ PLATFORMS
74
83
  DEPENDENCIES
75
84
  bundler
76
85
  fuubar
86
+ pry-byebug
77
87
  rake
78
88
  redcarpet
79
89
  rspec
@@ -87,4 +97,4 @@ DEPENDENCIES
87
97
  yard
88
98
 
89
99
  BUNDLED WITH
90
- 2.4.6
100
+ 2.4.12
data/README.md CHANGED
@@ -41,6 +41,28 @@ For reference, see [ThreadLocalVars](https://ruby-concurrency.github.io/concurre
41
41
 
42
42
  ### Instance Methods
43
43
 
44
+ The following are a brief list of the instance variable in the `ThreadLocalVarAccessors` class.
45
+
46
+ These methods interrogate or set thread-local variable values:
47
+
48
+ tlv_get NAME
49
+ tlv_set NAME, VALUE
50
+ tlv_set NAME { VALUE }
51
+ tlv_set_once NAME, VALUE
52
+ tlv_set_once NAME { VALUE }
53
+
54
+ These methods manage the default values for thread-local variables:
55
+
56
+ tlv_new NAME, DEFAULT
57
+ tlv_new NAME { DEFAULT }
58
+ tlv_init NAME, DEFAULT
59
+ tlv_init NAME { DEFAULT }
60
+ tlv_default NAME
61
+ tlv_set_default NAME, VALUE
62
+ tlv_set_default NAME { VALUE }
63
+
64
+ ### Instance Variable Details
65
+
44
66
  With the accessor methods, obtaining values and setting values
45
67
  becomes very simple:
46
68
 
@@ -50,14 +72,39 @@ becomes very simple:
50
72
  ...
51
73
  self.timeout = 0.5 # stores the TLV value just for this thread
52
74
 
75
+ The `tlv_init` method creates a _new_ TLVar and sets its default value.
76
+
77
+ Note that the default value is used when any thread evaluates the instance
78
+ variable and there has been no thread-specific value assignment.
79
+
80
+ Note: It's a best practice to use `tlv_init` to set the thread-local
81
+ instance variables with non-nil defaults that may be inherited across multiple
82
+ threads.
83
+
84
+ On the other hand, if the ruby developer using threads does not rely on
85
+ any particular value (other than nil) to be inherited, then using `tlv_set`
86
+ is the right way to go.
87
+
88
+
89
+ The TLV default value is used across *all* threads.
90
+
91
+ tlv_init(:timeout, default)
92
+ tlv_init(:timeout) { default }
93
+
94
+ The `tlv_init` method is essentially the same as `tlv_set_default`: it sets the
95
+ default value (or block) of a given TLVar, without affecting any possible
96
+ thread-local variables already assigned.
97
+
53
98
  Alternative ways to initialize:
54
99
 
55
100
  tlv_set(:timeout, 0)
56
101
 
57
- tlv_set(:timeout) # ensure that @timeout is initialized to an [TLV](TLV)
102
+ tlv_set(:timeout) # ensure that @timeout is initialized to an TLV
58
103
  @timeout.value = 0
59
104
 
60
- The following methods are used within the above reader, writer, accessor
105
+ ### More Details
106
+
107
+ The following methods are used within the above reader, writer, and accessor
61
108
  methods:
62
109
 
63
110
  tlv_get(name) - fetches the value of TLV `name`
@@ -66,29 +113,29 @@ methods:
66
113
  There is a block form to `tls_set`:
67
114
 
68
115
  tlv_set(name) { |old_val| new_val }
69
-
116
+
70
117
  In addition, there is also a `tlv_set_once` method that can be used to set
71
118
  a TLV value only if has not currently be set already.
72
119
 
73
120
  tlv_set_once(name, value)
74
-
121
+
75
122
  tlv_set_once(name) { |old_val| new_value }
76
123
 
77
124
  For `tlv_accessor` instance variables, it's possible to use the assign operators, eg: `+=`, or `||=`.
78
125
  For example:
79
126
 
80
127
  tlv_accessor :timeout, :count
81
-
128
+
82
129
  self.timeout ||= DEFAULT_TIMEOUT
83
-
130
+
84
131
  self.count += 1
85
132
 
86
133
  ### TLV Name Arguments
87
134
 
88
135
  The `name` argument to `tlv_get` and `tlv_set` is the same as given on
89
136
  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=).
137
+ automatically converted as needed to instance-variable syntax (eg: `:@name`),
138
+ and setter-method name syntax (eg `:name=`).
92
139
 
93
140
 
94
141
  ## Installation
@@ -111,7 +158,7 @@ To use the class methods, they must be included into the current module or class
111
158
  include ThreadLocalVarAccessors
112
159
  ...
113
160
  end
114
-
161
+
115
162
  With the include above, you can use the class methods to declare instance getter and setter methods:
116
163
 
117
164
  class MyNewClass
@@ -120,10 +167,10 @@ With the include above, you can use the class methods to declare instance getter
120
167
  tlv_reader :name1
121
168
  tlv_writer :name2
122
169
  tlv_accessor :name3, :name4
123
-
170
+
124
171
  end
125
-
126
- The above invocations:
172
+
173
+ The above invocations:
127
174
 
128
175
  - create reader methods for `name1`, `name3`, and `name4`.
129
176
  - create writer methods for `name2`, `name3`, and `name4`.
@@ -137,7 +184,7 @@ When adapting legacy code to become thread-safe, it's sometimes necessary to use
137
184
  tlv_get(name)
138
185
  tlv_set(name, value)
139
186
  tlv_set_once(name, value)
140
-
187
+
141
188
  Alternative block forms:
142
189
 
143
190
  tlv_set(name) { |oldval| newval }
@@ -151,8 +198,8 @@ Ultimately, these methods are all doing these basic accesses of the correspondin
151
198
  @name1.value = per_thread_value
152
199
  ...
153
200
  @name1.value # returns the per_thread_value
154
-
155
- If you prefer the style above, then you don't really need these accessor methods.
201
+
202
+ If you prefer the style above, then you don't really need these accessor methods.
156
203
 
157
204
  ### Example Usage
158
205
 
@@ -166,12 +213,23 @@ class MyClass
166
213
  tlv_reader :sleep_time
167
214
  tlv_writer :locked
168
215
 
216
+ # if the ivars will not be inherited in new threads after initialization
169
217
  def initialize(**args)
218
+ # set the current thread's local value for each ivar
170
219
  self.limit = args[:limit]
171
220
  self.timeout = args[:timeout]
172
221
  self.max_time = args[:max_time]
173
222
  self.sleep_time = args[:sleep_time]
174
223
  end
224
+
225
+ # if the ivars might possibly be inherited in new threads after initialization
226
+ def alt_initialize(**args)
227
+ # for each ivar, set the default value, which is inherited across all threads
228
+ tlv_init :limit, args[:limit]
229
+ tlv_init :timeout, args[:timeout]
230
+ tlv_init :max_time, args[:max_time]
231
+ tlv_init :sleep_time, args[:sleep_time]
232
+ end
175
233
 
176
234
  def run
177
235
  while count < limit && delay_time < timeout
@@ -186,29 +244,6 @@ class MyClass
186
244
  end
187
245
  ```
188
246
 
189
- There may be times where you may want to use the `tlv_`-prefix methods and not use the accessors.
190
-
191
- The following are a set of equivalencies.
192
-
193
- ```ruby
194
- tlv_accessor :timeout
195
- ```
196
-
197
- produces both the reader and writer methods, using the `tlv_get` and `tlv_set` methods.
198
-
199
- ```ruby
200
- def timeout
201
- tlv_get(:timeout)
202
- end
203
-
204
- def timeout=(val)
205
- tlv_set(:timeout, val)
206
- end
207
- ```
208
-
209
- The advantage of the block method, especially for `tlv_set_once`, is that the
210
- VALUE is only evaluated when the instance variable value is being set, and, the block will receive the old value as a parameter.
211
-
212
247
  ## Development
213
248
 
214
249
  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.
@@ -228,5 +263,5 @@ When any branch is pushed, the continuous integration with causes the branch to
228
263
  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.
229
264
 
230
265
  ## Original Author:
231
-
266
+
232
267
  Alan K. Stebbens <aks@stebbens.org>
@@ -1,3 +1,3 @@
1
1
  module ThreadLocalVarAccessors
2
- VERSION = '0.1.2'
2
+ VERSION = '1.1.0'
3
3
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'concurrent-ruby'
4
+
3
5
  # This module has methods making it easy to use the Concurrent::ThreadLocalVar
4
6
  # as instance variables. This makes instance variables using this code as
5
7
  # actually thread-local, without also leaking memory over time.
@@ -7,6 +9,8 @@
7
9
  # 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
10
  # to understand why we use TLVs instead of `Thread.current.thread_variable_(set|get)`
9
11
  #
12
+ # Class Methods
13
+ #
10
14
  # The following class methods declare `Concurrent::ThreadLocalVar` reader,
11
15
  # writer, and accessors with these class methods:
12
16
  #
@@ -31,17 +35,93 @@
31
35
  # becomes very simple:
32
36
  #
33
37
  # tlv_accessor :timeout
34
- # ...
35
- # timeout # fetches the current TLV value, unique to each thread
36
- # ...
38
+ #
39
+ # Instance Methods
40
+ #
41
+ # Create a new TLV instance with an associated default, applied across all threads.
42
+ #
43
+ # tlv_new :timeout, default_timeout
44
+ # tlv_new :timeout { default_timeout }
45
+ #
46
+ # This method _always_ assignes the instance variable. It is equivalent to:
47
+ #
48
+ # @instance_var = ThreadLocalVar.new(default)
49
+ # @instance_var = ThreadLocalVar.new { default }
50
+ #
51
+ # Assign the current thread value for the TLV variable:
52
+ #
37
53
  # self.timeout = 0.5 # stores the TLV value just for this thread
38
54
  #
39
- # Alternative ways to initialize:
55
+ # which is equivalent to:
56
+ #
57
+ # @timeout ||= ThreadLocalVar.new
58
+ # @timeout.value = 0.5
59
+ #
60
+ # Reference the current thread value for the TLV variable:
61
+ #
62
+ # timeout # fetches the current TLV value, unique to each thread
63
+ #
64
+ # which is the same as:
65
+ #
66
+ # @timeout ||= ThreadLocalVar.new
67
+ # @timeout.value
68
+ #
69
+ # Alternative ways to initialize the thread-local value:
70
+ #
71
+ # tlv_set(:timeout, 0)
72
+ # tlv_set(:timeout) # ensure that @timeout is initialized to an LTV
73
+ # tlv_set_once(:timeout, value) # set timeout only if it isn't already set
74
+ #
75
+ # Each thread-local instance can be independently assigned a value, which defaults
76
+ # to the _default_ value, or _block_, that was associated with the original
77
+ # `ThreadLocalVar.new` method. This module also provides an easy way to do this.
78
+ #
79
+ # Initializes a TLV on the `@timeout` instance variable with a default value of
80
+ # 0.15 seconds:
81
+ #
82
+ # tlv_new(:timeout, 0.15)
83
+ #
84
+ # This does the same, but uses a block (a Proc object) to possibly return a
85
+ # dynamic default value, as the proc is invoked each time the TLV instance is
86
+ # evaluted in a Thread.
40
87
  #
41
- # ltv_set(:timeout, 0)
88
+ # tlv_new(:sleep_time) { computed_sleep_time }
42
89
  #
43
- # ltv_set(:timeout) # ensure that @timeout is initialized to an LTV
44
- # @timeout.value = 0
90
+ # The block-proc is evaluated at the time the default value is needed, not when
91
+ # the TLV is assigned to the instance variable. In other words, much later
92
+ # during process, when the instance variable value is evaluated, _that_ is when
93
+ # the default block is evaluated.
94
+ #
95
+ # The `tlv_new` method always assigns a new TLVar to the named instance variable.
96
+ #
97
+ # There is a corresponding method to either create a new TLVar instance or
98
+ # assign the default value to the existing TLVar instance: `tlv_init`.
99
+ #
100
+ # tlv_init(IVAR, DEFAULT)
101
+ # tlv_init(IVAR) { DEFAULT }
102
+ #
103
+ # Note that `tlv_init` does not assign the thread-local value; it assigns the
104
+ # _instance variable_ to a new TLV with the given default. If any thread
105
+ # evaluates that instance variable, the default value will be returned unless
106
+ # and until the current thread associates a new, thread-local value with the TLV
107
+ # instance.
108
+ #
109
+ # The purpose of `tlv_init` is to make the initialization of a TLVar be idempotent:
110
+ # a. if the TLVar does not yet exist, it is created with the default value.
111
+ # b. if the TLVar already exists, it's default value is set.
112
+ #
113
+ # In neither case are any thread-local value set; only the default.
114
+ #
115
+ # The default for an existing TLV can be redefined, using either an optional
116
+ # default value, or an optional default block.
117
+ #
118
+ # tlv_set_default(:timeout, new_default)
119
+ # tlv_set_default(:timeout) { new_default }
120
+ #
121
+ # The default for an existing TLV can also be obtained, independently of the
122
+ # current thread's local value, if any:
123
+ #
124
+ # tlv_default(:timeout)
45
125
  #
46
126
  # The following methods are used within the above reader, writer, accessor
47
127
  # methods:
@@ -86,7 +166,8 @@
86
166
  #
87
167
  # Each thread referencing the instance variable, will get the same TLV object,
88
168
  # but when the `.value` method is invoked, each thread will receive the initial
89
- # value, or whatever local value may have been assigned subsequently.
169
+ # value, or whatever local value may have been assigned subsequently, or the
170
+ # default, which is the same across all the threads.
90
171
  #
91
172
  # To obtain the value of such an TLV instance variable, do:
92
173
  #
@@ -95,10 +176,7 @@
95
176
  # To assign a new value to an TLV instance:
96
177
  #
97
178
  # @timeout.value = new_value
98
-
99
- require 'concurrent-ruby'
100
-
101
- # methods for making usage of ThreadLocalVars easy
179
+ #
102
180
  module ThreadLocalVarAccessors
103
181
  # @!visibility private
104
182
  module MyRefinements
@@ -150,17 +228,26 @@ module ThreadLocalVarAccessors
150
228
  end
151
229
 
152
230
  # instance methods
231
+ # Returns the value of the TLV instance variable, if any.
153
232
  def tlv_get(name)
154
233
  instance_variable_get(name.to_ivar)&.value
155
234
  end
156
235
 
236
+ # sets the thread-local value of the TLV instance variable, creating
237
+ # it if necessary. This method is equivalent to:
238
+ # @name ||= ThreadLocalVar.new
239
+ # @name.value = block_given? ? yield : value
157
240
  def tlv_set(name, value = nil, &block)
158
241
  var = instance_variable_get(name.to_ivar) || tlv_new(name)
159
242
  tlv_set_var(var, value, &block)
160
243
  end
161
244
 
245
+ # Sets the thread-local value of the TLV instance variable, but only
246
+ # if it is not already set. It is equivalent to:
247
+ # @name ||= ThreadLocalVar.new
248
+ # @name.value ||= block_given? yield : value
162
249
  def tlv_set_once(name, value = nil, &block)
163
- if (var = instance_variable_get(name.to_ivar)) && !var.value.nil?
250
+ if (var = instance_variable_get(name.to_ivar)) && !var&.value.nil?
164
251
  var.value
165
252
  elsif var # var is set, but its value is nil
166
253
  tlv_set_var(var, value, &block)
@@ -169,10 +256,47 @@ module ThreadLocalVarAccessors
169
256
  end
170
257
  end
171
258
 
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)
259
+ # Creates a new TLVar with the given default value or block.
260
+ # Equivalent to:
261
+ # @name = ThreadLocalVar.new(block_given? ? yield : default)
262
+ def tlv_new(name, default=nil, &block)
263
+ instance_variable_set(name.to_ivar, Concurrent::ThreadLocalVar.new(default, &block))
264
+ end
265
+
266
+ # Creates a new TLVar with a default, or assigns the default value to an
267
+ # existing TLVar, without affecting any existing thread-local values.
268
+ # Equivalent to:
269
+ # @name ||= ThreadLocalVar.new
270
+ # @name.instance_variable_set(:@default, block_given? ? yield : default)
271
+ def tlv_init(name, default=nil, &block)
272
+ tlv_set_default(name, default, &block)
273
+ end
274
+
275
+ # Fetches the default value for the TLVar
276
+ # Equivalent to:
277
+ # @name&.send(:default)
278
+ def tlv_default(name)
279
+ instance_variable_get(name.to_ivar)&.send(:default)
280
+ end
281
+
282
+ # Sets the default value or block for the TLV _(which is applied across all threads)_.
283
+ # Creates a new TLV if the instance variable is not initialized.
284
+ def tlv_set_default(name, default=nil, &block)1
285
+ tlv = instance_variable_get(name.to_ivar)
286
+ if tlv
287
+ raise ArgumentError, "tlv_set_default: can only use a default or a block, not both" if default && block
288
+
289
+ if block
290
+ tlv.instance_variable_set(:@default_block, block)
291
+ tlv.instance_variable_set(:@default, nil)
292
+ else
293
+ tlv.instance_variable_set(:@default_block, nil)
294
+ tlv.instance_variable_set(:@default, default)
295
+ end
296
+ tlv
297
+ else
298
+ tlv_new(name, default, &block)
299
+ end
176
300
  end
177
301
 
178
302
  # @!visibility private
@@ -19,6 +19,7 @@ Gem::Specification.new do |s|
19
19
 
20
20
  s.add_development_dependency 'bundler'
21
21
  s.add_development_dependency 'fuubar'
22
+ s.add_development_dependency 'pry-byebug'
22
23
  s.add_development_dependency 'rake'
23
24
  s.add_development_dependency 'redcarpet'
24
25
  s.add_development_dependency 'rspec'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thread_local_var_accessors
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alan Stebbens
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-04-05 00:00:00.000000000 Z
11
+ date: 2023-05-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry-byebug
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'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rake
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -199,6 +213,7 @@ executables: []
199
213
  extensions: []
200
214
  extra_rdoc_files: []
201
215
  files:
216
+ - CHANGELOG.md
202
217
  - Gemfile
203
218
  - Gemfile.lock
204
219
  - LICENSE