[ruby-core:120076] [Ruby master Feature#20875] Atomic initialization for Ractor local storage
From:
"Dan0042 (Daniel DeLorme) via ruby-core" <ruby-core@...>
Date:
2024-12-02 16:15:18 UTC
List:
ruby-core #120076
Issue #20875 has been updated by Dan0042 (Daniel DeLorme).
Would it be possible to make `Ractor[:mtx] ||= Mutex.new` behave in an atomic way? Like maybe add a special `[]||=` method which is automatically called such that `Ractor[:mtx] ||= Mutex.new` becomes equivalent to `Ractor[:mtx] || Ractor.send(:"[]||=", :mtx, Mutex.new)`
I'm just throwing out the general idea here, because it would be nice to use common ruby idioms instead of yet another special API to handle concurrent behavior.
----------------------------------------
Feature #20875: Atomic initialization for Ractor local storage
https://bugs.ruby-lang.org/issues/20875#change-110819
* 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 -- [email protected]
To unsubscribe send an email to [email protected]
ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/