From: matz@... Date: 2020-10-26T09:04:13+00:00 Subject: [ruby-core:100566] [Ruby master Feature#17278] On-demand sharing of constants for Ractor Issue #17278 has been updated by matz (Yukihiro Matsumoto). Status changed from Open to Feedback I understand the benefit of the proposal but the mechanism behind is too complex and can cause unexpected result with concurrency (e.g. race conditions). It's the code (design) smell. We need more time to investigate further. Matz. ---------------------------------------- Feature #17278: On-demand sharing of constants for Ractor https://bugs.ruby-lang.org/issues/17278#change-88197 * Author: Dan0042 (Daniel DeLorme) * Status: Feedback * Priority: Normal ---------------------------------------- ### Description This proposal aims to reduce (but not eliminate) the need for freezing/sharing boilerplate code needed by ractors. ```ruby A = [1, [2, [3, 4]]] H = {a: "a"} Ractor.new do p A #A is not actually modified anywhere, so ok end.take H[:b] = "b" #H was never touched by ractor, so ok ``` ## Background Ractors require objects to be preemptively deep-frozen in order to be shared between ractors. This has an especially visible and restrictive effect on globals and constants. I tried thinking of a different way, and maybe I found one. So please allow me to humbly present this possibility. ## Proposal A constant would be by default in a "auto-shareable" state (A) which can change atomically to either (B) "non-shareable" if it is modified by the main ractor (C) "shareable" (and frozen) if it is accessed by a non-main ractor In detail: 1. When an object is assigned to a constant, it is added to a list of ractor-reachable objects 2. When the first ractor is created, the objects in that list are recursively marked with FL_AUTOSHARE * after this point, constant assignments result directly in FL_AUTOSHARE 3. In the main ractor, a call to `rb_check_frozen` (meaning the object is being modified) will 1. if FL_AUTOSHARE is set (state A) * [with ractor lock] * unless object is shareable * unset FL_AUTOSHARE (state B) 2. raise error if frozen * ideally with different message if object has FL_SHAREABLE 4. When a non-main ractor accesses a non-shareable constant 1. if object referenced by constant has FL_AUTOSHARE set (state A) * [with ractor lock] * if all objects recursively are still marked with FL_AUTOSHARE * make_shareable (state C) * else * unset top objects's FL_AUTOSHARE (state B) 2. raise error if not shareable ## Result So in the case that these 2 things happen in parallel: 1) main ractor modifies content of constant X 2) non-main ractor accesses constant X There are 2 possible outcomes: a) main ractor error "can't modify frozen/shared object" b) non-main ractor error "can not access non-shareable objects in constant X" ## Benefits In the normal case where non-frozen constants are left untouched after being assigned, this allows to skip a lot of `.freeze` or `Ractor.make_shareable` or `# shareable_constant_value: true` boilerplate. When you get the error "can not access non-sharable objects in constant X by non-main Ractor", first you have to make that constant X shareable. Then this can trigger a secondary error that X is frozen, that you also have to debug. This way cuts the debugging in half by skipping directly to the FrozenError. ## Downsides When you get the error "can not access non-sharable objects in constant X by non-main Ractor" you may want to solve the issue by e.g. copying the constant X rather than freezing it. This way makes it slightly harder to find where X is being accessed in the non-main ractor. In the case of conflict, whether the error occurs in the main ractor or the non-main ractor can be non-deterministic. ## Applicability This probably applies as well to global variables, class variables, and class instance variables. -- https://bugs.ruby-lang.org/ Unsubscribe: <mailto:ruby-core-request@ruby-lang.org?subject=unsubscribe> <http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>