Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEATURE: add interface compatible constant version of thread local var #823

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
45 changes: 45 additions & 0 deletions lib/concurrent-ruby/concurrent/atomic/constant_thread_local_var.rb
@@ -0,0 +1,45 @@
require 'concurrent/atomic/abstract_thread_local_var'

module Concurrent
# Has the same interface as {ThreadLocalVar} but the value can never be changed.
# The value is always the default provided to the constructor.
class ConstantThreadLocalVar < AbstractThreadLocalVar

# @note the value is always equal to default value
# @!macro thread_local_var_method_get
def value
default
end

# @!macro thread_local_var_method_set
# @raise ArgumentError if the value is not equal to the default
def value=(value)
if value != default
raise ArgumentError, "Constant thread local vars may not be altered"
end
end

# @!macro thread_local_var_method_bind
# @raise ArgumentError if the value is not equal to the default
def bind(value)
self.value = value
if block_given?
yield
end
end

protected

def allocate_storage
# nothing to do
end

def default
if @default_block
@default_block.call
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This evaluates the @default_block on each value read, so it may return different values each time value is called. I think that if the default_block is provided the ConstantThreadLocalVar should still store the computed value per thread (since it might differ). So the implementation could inherit from ThreadLocalVar, disallow using value= , and it would be good if it optimises @default case (the value is equal for all threads).

else
@default
end
end
end
end
1 change: 1 addition & 0 deletions lib/concurrent-ruby/concurrent/atomics.rb
Expand Up @@ -8,3 +8,4 @@
require 'concurrent/atomic/reentrant_read_write_lock'
require 'concurrent/atomic/semaphore'
require 'concurrent/atomic/thread_local_var'
require 'concurrent/atomic/constant_thread_local_var'
56 changes: 56 additions & 0 deletions spec/concurrent/atomic/constant_thread_local_var_spec.rb
@@ -0,0 +1,56 @@
require 'rbconfig'

module Concurrent

require 'concurrent/atomic/constant_thread_local_var'

RSpec.describe ConstantThreadLocalVar do

context "#value" do
it 'can return default value' do
v = described_class.new("apple")
expect(v.value).to eq("apple")

v = described_class.new do
"orange"
end
expect(v.value).to eq("orange")
end
end

context "#value=" do
it 'can set value to same value' do
v = described_class.new("apple")
v.value = "apple"
end

it 'will raise an ArgumentError when attempting to change immutable value' do
v = described_class.new do
"apple"
end

expect do
v.value = "orange"
end.to raise_error(ArgumentError)
end
end

context '#bind' do
it 'will raise when attempting to bind to a different value' do
v = described_class.new("apple")
expect do
v.bind("orange") {}
end.to raise_error(ArgumentError)
end

it 'works when bind value is the same' do

v = described_class.new("apple")
v.bind("apple") do
expect(v.value).to eq("apple")
end
end
end
end

end