thread_local_var_accessors 1.0.0 → 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: 44a2d47618384051b762c595994639af0699cde590462760853d3be02a611ffa
4
- data.tar.gz: 4d4e3629f6bdfa482e5f080c6576d4a74d465a6b67c567b3c169db87e3b782fa
3
+ metadata.gz: 939ace98d853409e91249ccd6a34a1acfa707f77293984c9143e4acee3e011a1
4
+ data.tar.gz: bd876354d8cae177ee222d6fbe7110f417013485d568ce3bd031aa068c752883
5
5
  SHA512:
6
- metadata.gz: e840c90fd9f096762ee4fd12ba5c4cffc9fb6952af177375327e04395cd9734a572542f1301ada916704daa507c21aaf42d4543d4dd01538e85bdf652a631c34
7
- data.tar.gz: 826a2db9381d3f7ab70482b0c866427554e58e4c7d70e238964e34593437add0d276067a21aa2e2775f8b9dc050cf05e99776f56374f330dd2a9e952738ecda7
6
+ metadata.gz: 6a3c026cd82bf5676d412ae88a799645afa5e5f3c9a85b552489656a8c1a9340435dfbd8ab1e00c0400d28f4792c009bb7f19d100f7a6792d77be71fe7ba3a04
7
+ data.tar.gz: 72001afd55e3c86e9da161f8d596fb148dd33a009a33ca72cc126425a59f6e8238adab52f2360de4e14b29c037f44aa2c728e01456b8dc0c3f22253d242e5fc6
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- thread_local_var_accessors (1.0.0)
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
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
 
@@ -49,16 +71,29 @@ becomes very simple:
49
71
  timeout # fetches the current TLV value, unique to each thread
50
72
  ...
51
73
  self.timeout = 0.5 # stores the TLV value just for this thread
52
-
74
+
53
75
  The `tlv_init` method creates a _new_ TLVar and sets its default value.
54
76
 
55
- Note that the default value is used when any thread evaluates the instance
77
+ Note that the default value is used when any thread evaluates the instance
56
78
  variable and there has been no thread-specific value assignment.
57
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
+
58
89
  The TLV default value is used across *all* threads.
59
90
 
60
- tlv_init(:timeout, _default_)
61
- tlv_init(:timeout) { _default_ }
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.
62
97
 
63
98
  Alternative ways to initialize:
64
99
 
@@ -67,7 +102,9 @@ Alternative ways to initialize:
67
102
  tlv_set(:timeout) # ensure that @timeout is initialized to an TLV
68
103
  @timeout.value = 0
69
104
 
70
- 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
71
108
  methods:
72
109
 
73
110
  tlv_get(name) - fetches the value of TLV `name`
@@ -76,29 +113,29 @@ methods:
76
113
  There is a block form to `tls_set`:
77
114
 
78
115
  tlv_set(name) { |old_val| new_val }
79
-
116
+
80
117
  In addition, there is also a `tlv_set_once` method that can be used to set
81
118
  a TLV value only if has not currently be set already.
82
119
 
83
120
  tlv_set_once(name, value)
84
-
121
+
85
122
  tlv_set_once(name) { |old_val| new_value }
86
123
 
87
124
  For `tlv_accessor` instance variables, it's possible to use the assign operators, eg: `+=`, or `||=`.
88
125
  For example:
89
126
 
90
127
  tlv_accessor :timeout, :count
91
-
128
+
92
129
  self.timeout ||= DEFAULT_TIMEOUT
93
-
130
+
94
131
  self.count += 1
95
132
 
96
133
  ### TLV Name Arguments
97
134
 
98
135
  The `name` argument to `tlv_get` and `tlv_set` is the same as given on
99
136
  the accessor, reader, and writer methods: either a string or symbol name,
100
- automatically converted as needed to instance-variable syntax (eg: :@name),
101
- 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=`).
102
139
 
103
140
 
104
141
  ## Installation
@@ -121,7 +158,7 @@ To use the class methods, they must be included into the current module or class
121
158
  include ThreadLocalVarAccessors
122
159
  ...
123
160
  end
124
-
161
+
125
162
  With the include above, you can use the class methods to declare instance getter and setter methods:
126
163
 
127
164
  class MyNewClass
@@ -130,10 +167,10 @@ With the include above, you can use the class methods to declare instance getter
130
167
  tlv_reader :name1
131
168
  tlv_writer :name2
132
169
  tlv_accessor :name3, :name4
133
-
170
+
134
171
  end
135
-
136
- The above invocations:
172
+
173
+ The above invocations:
137
174
 
138
175
  - create reader methods for `name1`, `name3`, and `name4`.
139
176
  - create writer methods for `name2`, `name3`, and `name4`.
@@ -147,7 +184,7 @@ When adapting legacy code to become thread-safe, it's sometimes necessary to use
147
184
  tlv_get(name)
148
185
  tlv_set(name, value)
149
186
  tlv_set_once(name, value)
150
-
187
+
151
188
  Alternative block forms:
152
189
 
153
190
  tlv_set(name) { |oldval| newval }
@@ -161,8 +198,8 @@ Ultimately, these methods are all doing these basic accesses of the correspondin
161
198
  @name1.value = per_thread_value
162
199
  ...
163
200
  @name1.value # returns the per_thread_value
164
-
165
- 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.
166
203
 
167
204
  ### Example Usage
168
205
 
@@ -176,12 +213,23 @@ class MyClass
176
213
  tlv_reader :sleep_time
177
214
  tlv_writer :locked
178
215
 
216
+ # if the ivars will not be inherited in new threads after initialization
179
217
  def initialize(**args)
218
+ # set the current thread's local value for each ivar
180
219
  self.limit = args[:limit]
181
220
  self.timeout = args[:timeout]
182
221
  self.max_time = args[:max_time]
183
222
  self.sleep_time = args[:sleep_time]
184
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
185
233
 
186
234
  def run
187
235
  while count < limit && delay_time < timeout
@@ -196,29 +244,6 @@ class MyClass
196
244
  end
197
245
  ```
198
246
 
199
- There may be times where you may want to use the `tlv_`-prefix methods and not use the accessors.
200
-
201
- The following are a set of equivalencies.
202
-
203
- ```ruby
204
- tlv_accessor :timeout
205
- ```
206
-
207
- produces both the reader and writer methods, using the `tlv_get` and `tlv_set` methods.
208
-
209
- ```ruby
210
- def timeout
211
- tlv_get(:timeout)
212
- end
213
-
214
- def timeout=(val)
215
- tlv_set(:timeout, val)
216
- end
217
- ```
218
-
219
- The advantage of the block method, especially for `tlv_set_once`, is that the
220
- VALUE is only evaluated when the instance variable value is being set, and, the block will receive the old value as a parameter.
221
-
222
247
  ## Development
223
248
 
224
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.
@@ -238,5 +263,5 @@ When any branch is pushed, the continuous integration with causes the branch to
238
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.
239
264
 
240
265
  ## Original Author:
241
-
266
+
242
267
  Alan K. Stebbens <aks@stebbens.org>
@@ -1,3 +1,3 @@
1
1
  module ThreadLocalVarAccessors
2
- VERSION = '1.0.0'
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
  #
@@ -32,58 +36,85 @@
32
36
  #
33
37
  # tlv_accessor :timeout
34
38
  #
39
+ # Instance Methods
40
+ #
35
41
  # Create a new TLV instance with an associated default, applied across all threads.
36
42
  #
37
- # tlv_init :timeout, default_timeout
43
+ # tlv_new :timeout, default_timeout
44
+ # tlv_new :timeout { default_timeout }
38
45
  #
39
- # tlv_init :timeout { default_timeout }
46
+ # This method _always_ assignes the instance variable. It is equivalent to:
40
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
+ #
53
+ # self.timeout = 0.5 # stores the TLV value just for this thread
54
+ #
55
+ # which is equivalent to:
56
+ #
57
+ # @timeout ||= ThreadLocalVar.new
58
+ # @timeout.value = 0.5
41
59
  #
42
60
  # Reference the current thread value for the TLV variable:
43
61
  #
44
62
  # timeout # fetches the current TLV value, unique to each thread
45
63
  #
46
- # Assign the current thread value for the TLV variable:
64
+ # which is the same as:
47
65
  #
48
- # self.timeout = 0.5 # stores the TLV value just for this thread
66
+ # @timeout ||= ThreadLocalVar.new
67
+ # @timeout.value
49
68
  #
50
69
  # Alternative ways to initialize the thread-local value:
51
70
  #
52
- # ltv_set(:timeout, 0)
53
- #
54
- # ltv_set(:timeout) # ensure that @timeout is initialized to an LTV
55
- #
56
- # @timeout.value = 0
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
57
74
  #
58
75
  # Each thread-local instance can be independently assigned a value, which defaults
59
76
  # to the _default_ value, or _block_, that was associated with the original
60
- # `ThreadLocalVar.new` method. This module also provides an easy way to do this:
77
+ # `ThreadLocalVar.new` method. This module also provides an easy way to do this.
61
78
  #
62
79
  # Initializes a TLV on the `@timeout` instance variable with a default value of
63
80
  # 0.15 seconds:
64
81
  #
65
- # tlv_init(:timeout, 0.15)
82
+ # tlv_new(:timeout, 0.15)
66
83
  #
67
84
  # This does the same, but uses a block (a Proc object) to possibly return a
68
85
  # dynamic default value, as the proc is invoked each time the TLV instance is
69
86
  # evaluted in a Thread.
70
87
  #
71
- # tlv_init(:sleep_time) { computed_sleep_time }
88
+ # tlv_new(:sleep_time) { computed_sleep_time }
72
89
  #
73
90
  # The block-proc is evaluated at the time the default value is needed, not when
74
91
  # the TLV is assigned to the instance variable. In other words, much later
75
92
  # during process, when the instance variable value is evaluated, _that_ is when
76
93
  # the default block is evaluated.
77
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
+ #
78
103
  # Note that `tlv_init` does not assign the thread-local value; it assigns the
79
104
  # _instance variable_ to a new TLV with the given default. If any thread
80
105
  # evaluates that instance variable, the default value will be returned unless
81
- # and until each thread associates a new, thread-local value with the TLV.
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.
82
114
  #
83
115
  # The default for an existing TLV can be redefined, using either an optional
84
116
  # default value, or an optional default block.
85
117
  #
86
- #
87
118
  # tlv_set_default(:timeout, new_default)
88
119
  # tlv_set_default(:timeout) { new_default }
89
120
  #
@@ -145,10 +176,7 @@
145
176
  # To assign a new value to an TLV instance:
146
177
  #
147
178
  # @timeout.value = new_value
148
-
149
- require 'concurrent-ruby'
150
-
151
- # methods for making usage of ThreadLocalVars easy
179
+ #
152
180
  module ThreadLocalVarAccessors
153
181
  # @!visibility private
154
182
  module MyRefinements
@@ -200,17 +228,26 @@ module ThreadLocalVarAccessors
200
228
  end
201
229
 
202
230
  # instance methods
231
+ # Returns the value of the TLV instance variable, if any.
203
232
  def tlv_get(name)
204
233
  instance_variable_get(name.to_ivar)&.value
205
234
  end
206
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
207
240
  def tlv_set(name, value = nil, &block)
208
241
  var = instance_variable_get(name.to_ivar) || tlv_new(name)
209
242
  tlv_set_var(var, value, &block)
210
243
  end
211
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
212
249
  def tlv_set_once(name, value = nil, &block)
213
- if (var = instance_variable_get(name.to_ivar)) && !var.value.nil?
250
+ if (var = instance_variable_get(name.to_ivar)) && !var&.value.nil?
214
251
  var.value
215
252
  elsif var # var is set, but its value is nil
216
253
  tlv_set_var(var, value, &block)
@@ -219,26 +256,32 @@ module ThreadLocalVarAccessors
219
256
  end
220
257
  end
221
258
 
222
- # @param [String|Symbol] name the TLV name
223
- # @param [Object|nil] default the optional default value
224
- # @param [Proc] block the optional associated block
225
- # @return [ThreadLocalVar] a new TLV set in the instance variable
226
- # @example Default argument
227
- # tlv_init(:ivar, default_value)
228
- # @example Default block
229
- # tlv_init(:ivar) { default_value }
230
- def tlv_init(name, default=nil, &block)
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)
231
263
  instance_variable_set(name.to_ivar, Concurrent::ThreadLocalVar.new(default, &block))
232
264
  end
233
- alias tlv_new tlv_init
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
234
274
 
235
275
  # Fetches the default value for the TLVar
276
+ # Equivalent to:
277
+ # @name&.send(:default)
236
278
  def tlv_default(name)
237
279
  instance_variable_get(name.to_ivar)&.send(:default)
238
280
  end
239
281
 
240
- # Sets the default value or block for the TLV _(which is applied across all threads)_
241
- def tlv_set_default(name, default=nil, &block)
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
242
285
  tlv = instance_variable_get(name.to_ivar)
243
286
  if tlv
244
287
  raise ArgumentError, "tlv_set_default: can only use a default or a block, not both" if default && block
@@ -250,8 +293,9 @@ module ThreadLocalVarAccessors
250
293
  tlv.instance_variable_set(:@default_block, nil)
251
294
  tlv.instance_variable_set(:@default, default)
252
295
  end
296
+ tlv
253
297
  else
254
- tlv_init(name, default, &block)
298
+ tlv_new(name, default, &block)
255
299
  end
256
300
  end
257
301
 
@@ -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: 1.0.0
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-05-26 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