thread_local_var_accessors 1.0.0 → 1.1.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: 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