The Ractor Shareability Audit
The Ractor Shareability Audit: `freeze` is a promise about one object; Ractor.shareable? is a promise about everything it can reach. The graph API says "frozen snapshot" - this audit asks the stricter question: which framework values could cross a Ractor boundary TODAY, which need make_shareable's deep freeze, and which can never go because they hold live machinery?
Scheduling & Concurrency
Round 14
Marc-André Lafortune
exit 0
bundle exec ruby examples/ractor_shareability.rb
a real captured run
THE RACTOR SHAREABILITY AUDIT (frozen is not the same promise) value frozen? shareable? after make_shareable graph snapshot true false a deep-frozen copy crosses graph[:order] true true (already crosses) graph[:stats] true true (already crosses) to_json_schema output false false a deep-frozen copy crosses a Task object false false a deep-frozen copy crosses TaskResult.success false false a deep-frozen copy crosses a RateLimit false false REFUSED: holds live machinery proof of travel: checked 1 properties in another Ractor the audit's grammar lesson: graph[:order] and graph[:stats] are data all the way down and cross as-is. the full snapshot is 'frozen' but REACHES unfrozen Task objects - a top-floor promise on a building with unlocked doors below; a deep-frozen COPY crosses fine, and copies are what you should send anyway. the RateLimit is the honest REFUSAL: it holds a real Mutex, and no amount of freezing turns a lock into a value - it's a machine, not a fact. that's the Ractor pattern in one line: send facts, keep machines. and note the auditor's own first-draft sin, preserved in the comment above: it deep-froze the system under audit and contaminated row after row - Ractor.shareable? is ruby's strictest freeze referee, and referees must not tamper with the evidence.
source
# frozen_string_literal: true # The Ractor Shareability Audit: `freeze` is a promise about one # object; Ractor.shareable? is a promise about everything it can # reach. The graph API says "frozen snapshot" - this audit asks the # stricter question: which framework values could cross a Ractor # boundary TODAY, which need make_shareable's deep freeze, and which # can never go because they hold live machinery? # # bundle exec ruby examples/ractor_shareability.rb # # Runs offline; verdicts come from Ractor itself, not from reading code. require class="s">"bundler/setup" require class="s">"agentic" Agentic.logger.level = class="y">:fatal Warning[class="y">:experimental] = false # Ractor is experimental; the audit knows def task_named(name) Agentic:class="y">:Task.new(description: name, agent_spec: {class="s">"name" => name, class="s">"instructions" => class="s">"w"}) end orchestrator = Agentic:class="y">:PlanOrchestrator.new a = task_named(class="s">"a") b = task_named(class="s">"b") orchestrator.add_task(a) orchestrator.add_task(b, [a]) spec = Agentic:class="y">:CapabilitySpecification.new( name: class="s">"quote", description: class="s">"q", version: class="s">"1.0.0", inputs: {mode: {type: class="s">"string", required: true, enum: %w[air sea]}}, rules: {gate: {relation: class="y">:requires, fields: [class="y">:mode]}} ) SUBJECTS = { class="s">"graph snapshot" => orchestrator.graph, class="s">"graph[class="y">:order]" => orchestrator.graph[class="y">:order], class="s">"graph[class="y">:stats]" => orchestrator.graph[class="y">:stats], class="s">"to_json_schema output" => spec.to_json_schema, class="s">"a Task object" => a, class="s">"TaskResult.success" => Agentic:class="y">:TaskResult.new(task_id: class="s">"t", success: true, output: class="s">"x"), class="s">"a RateLimit" => Agentic:class="y">:RateLimit.new(2) }.freeze # One verdict per subject, on a COPY wherever possible - an auditor # that deep-freezes the system under audit is contaminating its own # evidence (the first draft of this file did exactly that) def verdict(value) frozen = value.frozen? return [frozen, true, class="s">"(already crosses)"] if Ractor.shareable?(value) copy = begin Marshal.load(Marshal.dump(value)) rescue TypeError nil # holds procs, mutexes, IO - unmarshalable machinery end after = if copy begin Ractor.make_shareable(copy) class="s">"a deep-frozen copy crosses" rescue Ractor:class="y">:Error, TypeError => e class="s">"refused: #{e.class.name.split("::class="s">").last}" end else begin Ractor.make_shareable(value) class="s">"deep-frozen IN PLACE (mutates the original!)" rescue Ractor:class="y">:Error, TypeError class="s">"REFUSED: holds live machinery" end end [frozen, false, after] end puts class="s">"THE RACTOR SHAREABILITY AUDIT (frozen is not the same promise)" puts puts format(class="s">" %-24s %-8s %-11s %s", class="s">"value", class="s">"frozen?", class="s">"shareable?", class="s">"after make_shareable") SUBJECTS.each do |name, value| frozen, shareable, after = verdict(value) puts format(class="s">" %-24s %-8s %-11s %s", name, frozen, shareable, after) end # --- the payoff: ship a shareable value to a real Ractor ------------------------- schema = Ractor.make_shareable(spec.to_json_schema) answer = Ractor.new(schema) { |s| class="s">"checked #{s["propertiesclass="s">"].size} properties in another Ractor" }.take puts puts class="s">" proof of travel: #{answer}" puts puts class="s">" the audit's grammar lesson: graph[class="y">:order] and graph[class="y">:stats] are" puts class="s">" data all the way down and cross as-is. the full snapshot is" puts class="s">" 'frozen' but REACHES unfrozen Task objects - a top-floor promise" puts class="s">" on a building with unlocked doors below; a deep-frozen COPY" puts class="s">" crosses fine, and copies are what you should send anyway. the" puts class="s">" RateLimit is the honest REFUSAL: it holds a real Mutex, and no" puts class="s">" amount of freezing turns a lock into a value - it's a machine," puts class="s">" not a fact. that's the Ractor pattern in one line: send facts," puts class="s">" keep machines. and note the auditor's own first-draft sin," puts class="s">" preserved in the comment above: it deep-froze the system under" puts class="s">" audit and contaminated row after row - Ractor.shareable? is" puts class="s">" ruby's strictest freeze referee, and referees must not tamper" puts class="s">" with the evidence."