2020-03-09 13:22:11 -04:00
|
|
|
class Ractor
|
|
|
|
# Create a new Ractor with args and a block.
|
|
|
|
# args are passed via incoming channel.
|
2020-10-03 08:05:15 -04:00
|
|
|
# A block (Proc) will be isolated (can't access to outer variables)
|
2020-03-09 13:22:11 -04:00
|
|
|
#
|
|
|
|
# A ractor has default two channels:
|
|
|
|
# an incoming channel and an outgoing channel.
|
|
|
|
#
|
|
|
|
# Other ractors send objects to the ractor via the incoming channel and
|
|
|
|
# the ractor receives them.
|
|
|
|
# The ractor send objects via the outgoing channel and other ractors can
|
|
|
|
# receive them.
|
|
|
|
#
|
|
|
|
# The result of the block is sent via the outgoing channel
|
2020-09-18 01:15:32 -04:00
|
|
|
# and other
|
2020-03-09 13:22:11 -04:00
|
|
|
#
|
2020-10-11 10:03:02 -04:00
|
|
|
# r = Ractor.new do
|
|
|
|
# Ractor.receive # receive via r's mailbox => 1
|
|
|
|
# Ractor.receive # receive via r's mailbox => 2
|
|
|
|
# Ractor.yield 3 # yield a message (3) and wait for taking by another ractor.
|
|
|
|
# 'ok' # the return value will be yielded.
|
|
|
|
# # and r's incoming/outgoing ports are closed automatically.
|
|
|
|
# end
|
|
|
|
# r.send 1 # send a message (1) into r's mailbox.
|
|
|
|
# r << 2 # << is an alias of `send`.
|
|
|
|
# p r.take # take a message from r's outgoing port => 3
|
|
|
|
# p r.take # => 'ok'
|
|
|
|
# p r.take # raise Ractor::ClosedError
|
2020-03-09 13:22:11 -04:00
|
|
|
#
|
|
|
|
# other options:
|
|
|
|
# name: Ractor's name
|
2020-09-18 01:15:32 -04:00
|
|
|
#
|
2020-10-03 08:05:15 -04:00
|
|
|
def self.new(*args, name: nil, &block)
|
2020-03-09 13:22:11 -04:00
|
|
|
b = block # TODO: builtin bug
|
|
|
|
raise ArgumentError, "must be called with a block" unless block
|
|
|
|
loc = caller_locations(1, 1).first
|
|
|
|
loc = "#{loc.path}:#{loc.lineno}"
|
|
|
|
__builtin_ractor_create(loc, name, args, b)
|
|
|
|
end
|
|
|
|
|
|
|
|
# return current Ractor
|
|
|
|
def self.current
|
|
|
|
__builtin_cexpr! %q{
|
|
|
|
rb_ec_ractor_ptr(ec)->self
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.count
|
|
|
|
__builtin_cexpr! %q{
|
|
|
|
ULONG2NUM(GET_VM()->ractor.cnt);
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
# Multiplex multiple Ractor communications.
|
|
|
|
#
|
2020-10-11 10:03:02 -04:00
|
|
|
# r, obj = Ractor.select(r1, r2)
|
|
|
|
# #=> wait for taking from r1 or r2
|
|
|
|
# # returned obj is a taken object from Ractor r
|
2020-03-09 13:22:11 -04:00
|
|
|
#
|
2020-10-11 10:03:02 -04:00
|
|
|
# r, obj = Ractor.select(r1, r2, Ractor.current)
|
|
|
|
# #=> wait for taking from r1 or r2
|
|
|
|
# # or receive from incoming queue
|
|
|
|
# # If receive is succeed, then obj is received value
|
|
|
|
# # and r is :receive (Ractor.current)
|
2020-03-09 13:22:11 -04:00
|
|
|
#
|
2020-10-11 10:03:02 -04:00
|
|
|
# r, obj = Ractor.select(r1, r2, Ractor.current, yield_value: obj)
|
|
|
|
# #=> wait for taking from r1 or r2
|
|
|
|
# # or receive from incoming queue
|
|
|
|
# # or yield (Ractor.yield) obj
|
|
|
|
# # If yield is succeed, then obj is nil
|
|
|
|
# # and r is :yield
|
2020-03-09 13:22:11 -04:00
|
|
|
#
|
2020-10-03 08:05:15 -04:00
|
|
|
def self.select(*ractors, yield_value: yield_unspecified = true, move: false)
|
2020-12-05 00:04:48 -05:00
|
|
|
raise ArgumentError, 'specify at least one ractor or `yield_value`' if yield_unspecified && ractors.empty?
|
|
|
|
|
2020-03-09 13:22:11 -04:00
|
|
|
__builtin_cstmt! %q{
|
|
|
|
const VALUE *rs = RARRAY_CONST_PTR_TRANSIENT(ractors);
|
|
|
|
VALUE rv;
|
|
|
|
VALUE v = ractor_select(ec, rs, RARRAY_LENINT(ractors),
|
|
|
|
yield_unspecified == Qtrue ? Qundef : yield_value,
|
|
|
|
(bool)RTEST(move) ? true : false, &rv);
|
|
|
|
return rb_ary_new_from_args(2, rv, v);
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
# Receive an incoming message from Ractor's incoming queue.
|
2020-10-03 08:05:15 -04:00
|
|
|
def self.receive
|
2020-03-09 13:22:11 -04:00
|
|
|
__builtin_cexpr! %q{
|
2020-10-03 08:05:15 -04:00
|
|
|
ractor_receive(ec, rb_ec_ractor_ptr(ec))
|
2020-03-09 13:22:11 -04:00
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2020-10-03 08:05:15 -04:00
|
|
|
class << self
|
|
|
|
alias recv receive
|
|
|
|
end
|
|
|
|
|
2020-12-16 03:18:07 -05:00
|
|
|
# same as Ractor.receive
|
2020-10-03 08:05:15 -04:00
|
|
|
private def receive
|
2020-03-09 13:22:11 -04:00
|
|
|
__builtin_cexpr! %q{
|
2020-12-16 03:18:07 -05:00
|
|
|
ractor_receive(ec, rb_ec_ractor_ptr(ec))
|
2020-03-09 13:22:11 -04:00
|
|
|
}
|
|
|
|
end
|
2020-10-03 08:05:15 -04:00
|
|
|
alias recv receive
|
2020-03-09 13:22:11 -04:00
|
|
|
|
2020-12-08 00:04:18 -05:00
|
|
|
# Receive only a specific message.
|
|
|
|
#
|
|
|
|
# Instead of Ractor.receive, Ractor.receive_if can provide a pattern
|
|
|
|
# by a block and you can choose the receiving message.
|
|
|
|
#
|
|
|
|
# # Example:
|
|
|
|
# r = Ractor.new do
|
|
|
|
# p Ractor.receive_if{|msg| /foo/ =~ msg} #=> "foo3"
|
|
|
|
# p Ractor.receive_if{|msg| /bar/ =~ msg} #=> "bar1"
|
|
|
|
# p Ractor.receive_if{|msg| /baz/ =~ msg} #=> "baz2"
|
|
|
|
# end
|
|
|
|
# r << "bar1"
|
|
|
|
# r << "baz2"
|
|
|
|
# r << "foo3"
|
|
|
|
# r.take
|
|
|
|
#
|
|
|
|
# If the block returns truthy, the message will be removed from incoming queue
|
|
|
|
# and return this method with the message.
|
|
|
|
# When the block is escaped by break/return/exception and so on, the message also
|
|
|
|
# removed from the incoming queue.
|
|
|
|
# Otherwise, the messsage is remained in the incoming queue and check next received
|
|
|
|
# message by the given block.
|
|
|
|
#
|
|
|
|
# If there is no messages in the incoming queue, wait until arrival of other messages.
|
|
|
|
#
|
|
|
|
# Note that you can not call receive/receive_if in the given block recursively.
|
|
|
|
# It means that you should not do any tasks in the block.
|
|
|
|
#
|
|
|
|
# # Example:
|
|
|
|
# Ractor.current << true
|
|
|
|
# Ractor.receive_if{|msg| Ractor.receive}
|
|
|
|
# #=> `receive': can not call receive/receive_if recursively (Ractor::Error)
|
|
|
|
#
|
|
|
|
def self.receive_if &b
|
|
|
|
Primitive.ractor_receive_if b
|
|
|
|
end
|
|
|
|
|
2020-12-16 09:57:35 -05:00
|
|
|
private def receive_if &b
|
2020-12-08 00:04:18 -05:00
|
|
|
Primitive.ractor_receive_if b
|
|
|
|
end
|
|
|
|
|
2020-03-09 13:22:11 -04:00
|
|
|
# Send a message to a Ractor's incoming queue.
|
|
|
|
#
|
|
|
|
# # Example:
|
|
|
|
# r = Ractor.new do
|
2020-10-03 08:05:15 -04:00
|
|
|
# p Ractor.receive #=> 'ok'
|
2020-03-09 13:22:11 -04:00
|
|
|
# end
|
|
|
|
# r.send 'ok' # send to r's incoming queue.
|
2020-10-03 08:05:15 -04:00
|
|
|
def send(obj, move: false)
|
2020-03-09 13:22:11 -04:00
|
|
|
__builtin_cexpr! %q{
|
|
|
|
ractor_send(ec, RACTOR_PTR(self), obj, move)
|
|
|
|
}
|
|
|
|
end
|
2020-10-03 08:05:15 -04:00
|
|
|
alias << send
|
2020-03-09 13:22:11 -04:00
|
|
|
|
|
|
|
# yield a message to the ractor's outgoing port.
|
2020-10-03 08:05:15 -04:00
|
|
|
def self.yield(obj, move: false)
|
2020-03-09 13:22:11 -04:00
|
|
|
__builtin_cexpr! %q{
|
|
|
|
ractor_yield(ec, rb_ec_ractor_ptr(ec), obj, move)
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
# Take a message from ractor's outgoing port.
|
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
# r = Ractor.new{ 'oK' }
|
|
|
|
# p r.take #=> 'ok'
|
|
|
|
def take
|
|
|
|
__builtin_cexpr! %q{
|
|
|
|
ractor_take(ec, RACTOR_PTR(self))
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
def inspect
|
|
|
|
loc = __builtin_cexpr! %q{ RACTOR_PTR(self)->loc }
|
|
|
|
name = __builtin_cexpr! %q{ RACTOR_PTR(self)->name }
|
|
|
|
id = __builtin_cexpr! %q{ INT2FIX(RACTOR_PTR(self)->id) }
|
2020-09-18 01:15:32 -04:00
|
|
|
status = __builtin_cexpr! %q{
|
|
|
|
rb_str_new2(ractor_status_str(RACTOR_PTR(self)->status_))
|
|
|
|
}
|
|
|
|
"#<Ractor:##{id}#{name ? ' '+name : ''}#{loc ? " " + loc : ''} #{status}>"
|
2020-03-09 13:22:11 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def name
|
|
|
|
__builtin_cexpr! %q{ RACTOR_PTR(self)->name }
|
|
|
|
end
|
|
|
|
|
|
|
|
class RemoteError
|
|
|
|
attr_reader :ractor
|
|
|
|
end
|
|
|
|
|
2020-10-24 00:01:17 -04:00
|
|
|
# Closes the incoming port and returns its previous state.
|
2020-03-09 13:22:11 -04:00
|
|
|
def close_incoming
|
|
|
|
__builtin_cexpr! %q{
|
|
|
|
ractor_close_incoming(ec, RACTOR_PTR(self));
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2020-10-24 00:01:17 -04:00
|
|
|
# Closes the outgoing port and returns its previous state.
|
2020-03-09 13:22:11 -04:00
|
|
|
def close_outgoing
|
|
|
|
__builtin_cexpr! %q{
|
|
|
|
ractor_close_outgoing(ec, RACTOR_PTR(self));
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2020-10-19 22:21:49 -04:00
|
|
|
# utility method
|
|
|
|
def self.shareable? obj
|
|
|
|
__builtin_cexpr! %q{
|
|
|
|
rb_ractor_shareable_p(obj) ? Qtrue : Qfalse;
|
|
|
|
}
|
|
|
|
end
|
Ractor.make_shareable(obj)
Introduce new method Ractor.make_shareable(obj) which tries to make
obj shareable object. Protocol is here.
(1) If obj is shareable, it is shareable.
(2) If obj is not a shareable object and if obj can be shareable
object if it is frozen, then freeze obj. If obj has reachable
objects (rs), do rs.each{|o| Ractor.make_shareable(o)}
recursively (recursion is not Ruby-level, but C-level).
(3) Otherwise, raise Ractor::Error. Now T_DATA is not a shareable
object even if the object is frozen.
If the method finished without error, given obj is marked as
a sharable object.
To allow makng a shareable frozen T_DATA object, then set
`RUBY_TYPED_FROZEN_SHAREABLE` as type->flags. On default,
this flag is not set. It means user defined T_DATA objects are
not allowed to become shareable objects when it is frozen.
You can make any object shareable by setting FL_SHAREABLE flag,
so if you know that the T_DATA object is shareable (== thread-safe),
set this flag, at creation time for example. `Ractor` object is one
example, which is not a frozen, but a shareable object.
2020-10-20 11:54:03 -04:00
|
|
|
|
|
|
|
def self.make_shareable obj
|
|
|
|
__builtin_cexpr! %q{
|
|
|
|
rb_ractor_make_shareable(obj);
|
|
|
|
}
|
|
|
|
end
|
2020-03-09 13:22:11 -04:00
|
|
|
end
|