Project

General

Profile

Feature #12021

Final instance variables

Added by pitr.ch (Petr Chalupa) over 3 years ago. Updated about 3 years ago.

Status:
Open
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-core:73440]

Description

Having a final instance variables in Ruby will allow: to construct thread-save immutable objects without additional synchronisation on instance variable reading

# Immutable and thread-safe 
class TreeNode
  attr :left, :right, :value, final: true
  attr_reader :left, :right, :value

  def initialize(left, right, value)
    @left, @right, @value = left, right, value
  end
end

And to fix the an issue shown in the following example:

attr :lock, final: true
def initialize
  @lock = Mutex.new
  # ...
end  

def a_protected_method
  @lock.synchronize do
    # ...
  end
end

The issue lies in initialization of instance variable @lock which is not ensured to be visible in subsequent call to a_protected_method method on a different thread.

Summary can be found in this document https://docs.google.com/document/d/1c07qfDArx0bhK9sMr24elaIUdOGudiqBhTIRALEbrYY/edit#.

The aggregating issue of this effort can be found here.


Related issues

Related to CommonRuby - Feature #12019: Better low-level support for writing concurrent librariesOpenActions
Related to Ruby trunk - Feature #12334: Final/Readonly Support for Fields / Instance VariablesOpenActions

History

Updated by pitr.ch (Petr Chalupa) over 3 years ago

The above declares the final variables explicitly, there is also an alternative approach to threat all instance variable assignments in constructor as a final variable. Therefore protecting them implicitly, ensuring their visibility after the object is constructed. If one of the instance variables is reassigned later it will loose any visibility guaranties. This has to be explored more deeply though. The actual cost of having the protection always on implicitly has to be determined.

#2

Updated by Eregon (Benoit Daloze) over 3 years ago

  • Related to Feature #12019: Better low-level support for writing concurrent libraries added

Updated by pitr.ch (Petr Chalupa) over 3 years ago

A version accessible without JS is here https://docs.google.com/document/d/1c07qfDArx0bhK9sMr24elaIUdOGudiqBhTIRALEbrYY/pub. Sorry for not thinking about that.

#5

Updated by Eregon (Benoit Daloze) about 3 years ago

  • Related to Feature #12334: Final/Readonly Support for Fields / Instance Variables added

Updated by pitr.ch (Petr Chalupa) about 3 years ago

Consider following code:

QUEUE = Queue.new
WORKER = Thread.new { loop { QUEUE.pop.call } }

def async(&job)
  QUEUE.push job
  nil
end

def the_example
  local_var = :value
  async { p local_var }
end

Currently by RMM rules and on current implementations there is no documented
guarantee that the job executed asynchronously will print the :value. (JRuby
actually documents and warns that it may not print :value). (It safe on MRI
because of undocumented behavior of GIL.)

This behavior is quite inconvenient. It requires that APIs for threads, fibers
and other concurrent libraries (e.g. async, promises) need to pass the values
to the block through the factory methods as follows and internally ensure
visibility.

Thread.new(local_var) { |v| p v }
async(local_var) { |v| p v }
# etc.

Using this proposal also for Proc and its hidden field which holds the captured
scope could be used to fix this issue.

If the hidden field holding scope of Proc is classified as final then it
implies that any of the local variable assignments before the Proc construction
cannot be reordered with publishing of the proc instance. Therefore when the
proc is executed on a different thread it's guaranteed that the values in
captured local variables assigned before the proc construction will be visible.

Therefore following would be correct and safe:

local_var = :value
Thread.new { p local_var }
async { p local_var }

In relation to discussion happening in https://bugs.ruby-lang.org/issues/12020,
where is being discussed distinction between low and high level documentation:
The explanation above would fall into the low-level part, high-level
documentation for users of Proc would simply say that: "When Proc is created it
captures local variables and theirs latest values in the scope. (Subsequent
updates to local variables may or may not be visible to the Proc's body.)". The
visibility of values assigned before the Proc instance creation is currently
expected behavior due to GIL undocumented behavior.

Also available in: Atom PDF