From: "Eregon (Benoit Daloze) via ruby-core" Date: 2024-12-09T12:09:53+00:00 Subject: [ruby-core:120137] [Ruby master Feature#20875] Atomic initialization for Ractor local storage Issue #20875 has been updated by Eregon (Benoit Daloze). ko1 (Koichi Sasada) wrote in #note-9: > can be acceptable if returning the assigned value is out-of-scope, even if it returns assigned value. What does this mean? It seems clear such a method should return the same as `Ractor[key]`, after potentially computing + storing the value. BTW, it sounds like `Map#computeIfAbsent` in Java and `Concurrent::Map#compute_if_absent` in concurrent-ruby. Maybe Ruby should have `Concurrent::Map` built-in, or `Hash#compute_if_absent` built-in. Then each Ractor could have such a map/hash and this could be solved like `Ractor[:mtx] ||= Ractor.map.compute_if_absent(:mtx) { Mutex.new }`. In fact maybe that map could be the storage of ractor-local variables, and then `Ractor.map.compute_if_absent(:mtx) { Mutex.new }` is enough. ---------------------------------------- Feature #20875: Atomic initialization for Ractor local storage https://bugs.ruby-lang.org/issues/20875#change-110886 * Author: ko1 (Koichi Sasada) * Status: Open ---------------------------------------- ## Motivation Now there is no way to initialize Ractor local storage in multi-thread. For example, if we want to introduce per-Ractor counter, which should be protected with a per-Ractor Mutex for multi-threading support. ```ruby def init Ractor[:cnt] = 0 Ractor[:mtx] = Mutex.new end def inc init unless Ractor[:cnt] Ractor[:mtx].synchronize do Ractor[:cnt] += 1 end end ``` In this code, if `inc` was called on multiple threads, `init` can be called with multiple threads and `cnt` can not be synchronized correctly. ## Proposal Let's introduce `Ractor.local_storage_init(sym){ block }` to initialize values in Ractor local storage. If there is no slot for `sym`, synchronize with per-Ractor mutex and call `block` and the slot will be filled with the evaluation with the block result. The return value of this method will be the filled value. Otherwise, returning corresponding value will be returned. The implementation is like that (in C): ```ruby class Ractor def self.local_storage_init(sym) Ractor.per_ractor_mutex.synchronize do if Ractor.local_storage_has_key?(sym) Ractor[:sym] else Ractor[:sym] = yield end end end end ``` The above examples will be rewritten with the following code: ```ruby def inc Ractor.local_storage_init(:mtx) do Ractor[:cnt] = 0 Mutex.new end.synchronize do Ractor[:cnt] += 1 end end ``` ## Discussion ### Approach There is another approach like `pthread_atfork`, maybe like `Ractor.atcreate{ init }`. A library registers a callback which will be called when a new ractor is created. However, there are many Ractors which don't use the library, so that `atcreate` can be huge overhead for Ractor creation. ### Naming I propose `local_storage_init`, but not sure it matches. I also proposed `Ractor.local_variable_init(sym)`, but Matz said he doesn't like this naming because it should not be a "variable". (there is a `Thread#thread_variable_get` method, though). On another aspect, `lcoal_storage_init` seems it clears all of ractor local storage slots. ### Reentrancy This proposal uses `Mutex`, so it is not reentrant. I believe it should be simple and using Monitor is too much. (but it is not big issue, though) ## Implementation https://github.com/ruby/ruby/pull/12014 -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/