class Backports::Ractor
def [](key)
def [](key) Ractor.current.ractor_locals[key] end
def []=(key, value)
def []=(key, value) Ractor.current.ractor_locals[key] = value end
def close_incoming
def close_incoming r = ractor_incoming_queue.closed? ractor_incoming_queue.close r end
def close_outgoing
def close_outgoing r = ractor_outgoing_queue.closed? ractor_outgoing_queue.close r end
def count
def count ::ObjectSpace.each_object(Ractor).count(&:ractor_live?) end
def current
def current ::Thread.current.thread_variable_get(:backports_ractor) || ::Thread.current.thread_variable_set(:backports_ractor, ractor_find_current) end
def initialize(*args, &block)
def initialize(*args, &block) @ractor_incoming_queue = IncomingQueue.new @ractor_outgoing_queue = OutgoingQueue.new raise ::ArgumentError, 'must be called with a block' unless block kw = args.last if kw.is_a?(::Hash) && kw.size == 1 && kw.key?(:name) args.pop name = kw[:name] end @ractor_name = name && Backports.coerce_to_str(name) @id = Ractor.ractor_next_id if Ractor.main == nil # then initializing main Ractor @ractor_thread = ::Thread.current @ractor_origin = nil @ractor_thread.thread_variable_set(:backports_ractor, self) else @ractor_origin = caller(1, 1).first.split(':in `').first args.map! { |a| Ractor.ractor_isolate(a, false) } ractor_thread_start(args, block) end end
def inspect
def inspect state = RACTOR_STATE[@ractor_thread ? @ractor_thread.status : 'run'] info = [ "Ractor:##{@id}", name, @ractor_origin, state, ].compact.join(' ') "#<#{info}>" end
def make_shareable(obj)
def make_shareable(obj) return obj if ractor_check_shareability?(obj, true) raise Ractor::Error, '#freeze does not freeze object correctly' end
def name
def name @ractor_name end
def ractor_check_shareability?(obj, freeze_all)
def ractor_check_shareability?(obj, freeze_all) _shareable_self?(obj, freeze_all) do ted = {} rn false unless ractor_shareable_parts?(obj, freeze_all, visited) or_mark_set_shareable(visited)
def ractor_find_current
def ractor_find_current ThreadGroups[Thread.current.group]
def ractor_init
def ractor_init r_shareable = ::ObjectSpace::WeakMap.new = Ractor.new { nil } ThreadGroups[::ThreadGroup::Default] = @main
def ractor_isolate(val, move = false)
- Api: - private
def ractor_isolate(val, move = false) return val if move Cloner.deep_clone(val) end
def ractor_live?
- Api: - private
def ractor_live? !defined?(@ractor_thread) || # May happen if `count` is called from another thread before `initialize` has completed @ractor_thread.status end
def ractor_locals
- Api: - private
def ractor_locals @ractor_locals ||= {}.compare_by_identity end
def ractor_mark_set_shareable(visited)
def ractor_mark_set_shareable(visited) visited.each do |key| @ractor_shareable[key] = Ractor end end
def ractor_next_id
- Api: - private
def ractor_next_id @id ||= 0 @id += 1 end
def ractor_reset
- Api: - private
def ractor_reset ::ObjectSpace.each_object(Ractor).each do |r| next if r == Ractor.current next unless (th = r.ractor_thread) th.kill th.join end Ractor.current.ractor_incoming_queue.clear end
def ractor_shareable_by_nature?(obj, freeze_all)
def ractor_shareable_by_nature?(obj, freeze_all) bj :Module, Ractor :Regexp, ::Range, ::Numeric eze_all # Assume that these are literals that would have been frozen in 3.0 # unless we're making them shareable, in which case we might as well # freeze them for real. :Symbol, false, true, nil # Were only frozen in Ruby 2.3+ e
def ractor_shareable_parts?(obj, freeze_all, visited)
def ractor_shareable_parts?(obj, freeze_all, visited) true if visited.key?(obj) d[obj] = true _traverse(obj) do |part| rn false unless ractor_shareable_self?(part, freeze_all) do ctor_shareable_parts?(part, freeze_all, visited)
def ractor_shareable_self?(obj, freeze_all)
def ractor_shareable_self?(obj, freeze_all) return true if @ractor_shareable.key?(obj) return true if ractor_shareable_by_nature?(obj, freeze_all) if obj.frozen? || (freeze_all && obj.freeze) yield else false end end
def ractor_thread_start(args, block)
def ractor_thread_start(args, block) ad.new do tor_thread = ::Thread.current tor_thread_group = ::ThreadGroup.new orThreadGroups[@ractor_thread_group] = self tor_thread_group.add(@ractor_thread) read.current.thread_variable_set(:backports_ractor, self) lt = nil n sult = instance_exec(*args, &block) ue ::Exception => err # rubocop:disable Lint/RescueException gin raise RemoteError, "thrown by remote Ractor: #{err.message}" scue RemoteError => e # Hack to create exception with `cause` result = OutgoingQueue::WrappedException.new(e) d re ctor_thread_terminate(result)
def ractor_thread_terminate(result)
def ractor_thread_terminate(result) or_outgoing_queue.push(result, ack: false) unless ractor_outgoing_queue.closed? ::ClosedQueueError rn # ignore _incoming_queue.close _outgoing_queue.close(:soft) : synchronize? r_thread_group.list.each do |thread| ad.kill unless thread == Thread.current
def ractor_traverse(obj, &block)
def ractor_traverse(obj, &block) bj :Hash obj.default d obj.default_proc each do |key, value| eld key eld value :Range d obj.begin d obj.end :Array, ::Struct each(&block) :Complex d obj.real d obj.imaginary :Rational d obj.numerator d obj.denominator stance_variables.each do |var| d obj.instance_variable_get(var)
def receive
def receive _incoming_queue.pop
def receive
def receive current.__send__(:receive) end
def receive_if(&block)
def receive_if(&block) ::ArgumentError, 'no block given' unless block _incoming_queue.pop(&block)
def receive_if(&block)
def receive_if(&block) current.__send__(:receive_if, &block) end
def select(*ractors, yield_value: not_given = true, move: false)
def select(*ractors, yield_value: not_given = true, move: false) cur = Ractor.current queues = ractors.map do |r| r == cur ? r.ractor_incoming_queue : r.ractor_outgoing_queue end if !not_given out = current.ractor_outgoing_queue yield_value = ractor_isolate(yield_value, move) elsif ractors.empty? raise ::ArgumentError, 'specify at least one ractor or `yield_value`' end while true # rubocop:disable Style/InfiniteLoop # Don't `loop`, in case of `ClosedError` (not that there should be any) queues.each_with_index do |q, i| q.pop_non_blocking do |val| r = ractors[i] return [r == cur ? :receive : r, val] end end if out && out.num_waiting > 0 # Not quite atomic... out.push(yield_value, ack: true) return [:yield, nil] end sleep(0.001) end end
def send(obj, move: false)
def send(obj, move: false) ractor_incoming_queue << Ractor.ractor_isolate(obj, move) self rescue ::ClosedQueueError raise ClosedError, 'The incoming-port is already closed' end
def shareable?(obj)
def shareable?(obj) ractor_check_shareability?(obj, false) end
def take
def take ractor_outgoing_queue.pop(ack: true) end
def yield(value, move: false)
def yield(value, move: false) value = ractor_isolate(value, move) current.ractor_outgoing_queue.push(value, ack: true) rescue ::ClosedQueueError raise ClosedError, 'The outgoing-port is already closed' end