From: marcandre-ruby-core@... Date: 2020-11-12T18:24:49+00:00 Subject: [ruby-core:100818] [Ruby master Feature#17323] Ractor::LVar to provide ractor-local storage Issue #17323 has been updated by marcandre (Marc-Andre Lafortune). I'm curious for better use cases. The above example is not very convincing: - there's no much use in sending this object to a Ractor to start with - deep-copying the cache is probably faster than having to recalculate part of it - more importantly, this would probably be the wrong solution if we had `SharedHash`: ```ruby class SharedHash def initialize(initial = {}) @ractor = Ractor.new(initial) do |hash| loop do case Ractor.receive in [:read, key, default] then Ractor.yield(hash.fetch(key, default)) in [:write, key, value] then hash[key] = value in :inspect | :to_s | :to_h => cmd then Ractor.yield(hash.send(cmd)) else raise ArgumentError end end end end def [](key) @ractor << [:read, key, nil] @ractor.take end def []=(key, value) @ractor << [:write, key, value] value end def inspect @ractor << :inspect @ractor.take end end class Fib attr_reader :cache def initialize @cache = SharedHash.new end private def _fib n if n < 2 1 else fib(n-1) + fib(n-2) end end def fib n @cache[n] ||= _fib(n) end end fiboner = Fib.new p fiboner.fib(10) #=> 89 pp fiboner.cache #=> {1=>1, 0=>1, 2=>2, 3=>3, 4=>5, 5=>8, 6=>13, 7=>21, 8=>34, 9=>55, 10=>89} Ractor.new fiboner do |f2| p f2.fib(5) #=> 8 already cached!gi p f2.cache #=> {1=>1, 0=>1, 2=>2, 3=>3, 4=>5, 5=>8, 6=>13, 7=>21, 8=>34, 9=>55, 10=>89} end.take ``` If the "start from a clear cache" is actually the right idea, than a solution could look like: ```ruby class Fib attr_reader :cache def initialize @cache = {} end def initialize_copy(_) @cache = {} end private def _fib n if n < 2 1 else fib(n-1) + fib(n-2) end end def fib n @cache[n] ||= _fib(n) end end fiboner = Fib.new p fiboner.fib(10) #=> 89 pp fiboner.cache #=> {1=>1, 0=>1, 2=>2, 3=>3, 4=>5, 5=>8, 6=>13, 7=>21, 8=>34, 9=>55, 10=>89} Ractor.new fiboner.dup, move: true do |f2| p f2.fib(5) #=> 8 p f2.cache.value #=> {1=>1, 0=>1, 2=>2, 3=>3, 4=>5, 5=>8} end.take ``` Above does not work yet, depends on #17286... It seems to me that having a way to define how to deep-copy an object might be important. ---------------------------------------- Feature #17323: Ractor::LVar to provide ractor-local storage https://bugs.ruby-lang.org/issues/17323#change-88458 * Author: ko1 (Koichi Sasada) * Status: Open * Priority: Normal ---------------------------------------- Ruby supports thread and fiber local storage: * `Thread#[sym]` provides *Fiber* local storage * `Thread#thread_variable_get(sym) These APIs can access other threads/fibers like that: ```ruby th = Thread.new{ Thread.current.thread_variable_set(:a, 10) } th.join # access from main thread to child thread p th.thread_variable_get(:a) ``` To make Ractor local storage, this kind of feature should not be allowed to protect isolation. This ticket propose alternative API `Ractor::LVar` that allows to provide Ractor local variable. ```ruby LV1 = Ractor::LVar.new p LV1.value #=> nil # default value LV1.value = 'hello' # can set unshareable objects because LVar is ractor local. Ractor.new do LV1.value = 'world' # set Ractor local variable end.take p LV1.value #=> 'hello' # Lvar.new can accept default_proc which should be isolated Proc. LV2 = Ractor::LVar.new{ "x" * 4 } p LV2.value #=> "xxxx" LV2.value = 'yyy' Ractor.new do p LV2.value #=> 'xxx' end p LV2.value #=> 'yyy' ``` This API doesn't support accessing from other ractors. `Ractor::LVar` is from `Ractor::TVar`, but I have no strong opinion about it. For example, `Ractor::LocalVariable` is longer and clearer. Implementation: https://github.com/ruby/ruby/pull/3762 -- https://bugs.ruby-lang.org/ Unsubscribe: