thread_local_var_accessors 0.1.2 → 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: 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