ractorize 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/src/ractorize/base_ractor.rb +2 -0
- data/src/ractorize/garbage_collection/tracker.rb +35 -0
- data/src/ractorize/garbage_collection/tracking_ractor.rb +38 -0
- data/src/ractorize/garbage_collection.rb +54 -0
- data/src/ractorize/ractorized_object/ractorized_ractor.rb +133 -0
- data/src/ractorize/ractorized_object.rb +67 -39
- data/src/ractorize/thunk/thunk_ractor.rb +23 -0
- data/src/ractorize/thunk.rb +12 -21
- data/src/ractorize.rb +42 -131
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 72a53fba452511ad30a39dc46714c285570a2904f167c960e97d519e63ee1829
|
|
4
|
+
data.tar.gz: 77c170a01ff7ec50bfb63c8c70b905be6d999bdaa2b8cd0e3826afd7bc5bb9d1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3655da0fc231e1d6b5c9bd063cf8e676ab6a9ce7f3b09421e48d085978257574bbbf988d538acb190dd0512d3838b5fe49a9436fa85e65f67a09aebfe137ff8b
|
|
7
|
+
data.tar.gz: 0dad22a3bf13eedff038063711e18e473b25f5e6e760b3b400de4603996240f71d3635788c8f06e2705da2a8103df236c5c76f94303ff44900122baedd368a9d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
## [0.0.9] - 2026-06-29
|
|
2
|
+
|
|
3
|
+
- Fix bug that results from moving Time, which is neither shareable nor movable!
|
|
4
|
+
- Handle deadlocks that seem to arise from re-entrant #inspect calls
|
|
5
|
+
|
|
6
|
+
## [0.0.8] - 2026-06-24
|
|
7
|
+
|
|
8
|
+
- Add GarbageCollector to close abandoned RactorizedObject ractors and Thunk ractors
|
|
9
|
+
- Switch thunks to work off of Ractor instead of Ractor::Port due to port/ractor leaks
|
|
10
|
+
- Block on #hash and delegate to super in #==/#!=
|
|
11
|
+
- Significant test suite improvements:
|
|
12
|
+
- Remove awkward RACTORIZE_PROC tests and instead use the shmactor gem to get to 100% branch coverage
|
|
13
|
+
- Parallelize the build and run a shmactor build to get full coverage of ractor procs
|
|
14
|
+
- Check for leaked ractors/ports after test suite and fail the build on any memory leaks
|
|
15
|
+
|
|
1
16
|
## [0.0.7] - 2026-06-06
|
|
2
17
|
|
|
3
18
|
- Closed Ractor::Ports sometimes give IOError instead of Ractor::ClosedError so handle both
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Ractorize
|
|
2
|
+
module GarbageCollection
|
|
3
|
+
class Tracker
|
|
4
|
+
attr_accessor :ractorized_object_id_to_ractor,
|
|
5
|
+
:thunk_id_to_ractor
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
self.ractorized_object_id_to_ractor = ObjectSpace::WeakMap.new
|
|
9
|
+
self.thunk_id_to_ractor = ObjectSpace::WeakMap.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def track_ractorized_object(ractorized_object)
|
|
13
|
+
ractorized_object_id_to_ractor[ractorized_object.__object_id__] = ractorized_object.__ractor__
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def cleanup_after_ractorized_object(ractorized_object_id)
|
|
17
|
+
ractor = ractorized_object_id_to_ractor.delete(ractorized_object_id)
|
|
18
|
+
ractor&.<<(:__close__)
|
|
19
|
+
rescue Ractor::ClosedError
|
|
20
|
+
# do nothing
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def track_thunk(thunk_id, ractor)
|
|
24
|
+
thunk_id_to_ractor[thunk_id] = ractor
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def cleanup_after_thunk(thunk_id)
|
|
28
|
+
ractor = thunk_id_to_ractor.delete(thunk_id)
|
|
29
|
+
ractor&.<<(:__close__)
|
|
30
|
+
rescue Ractor::ClosedError
|
|
31
|
+
# do nothing
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require_relative "../base_ractor"
|
|
2
|
+
require_relative "tracker"
|
|
3
|
+
|
|
4
|
+
module Ractorize
|
|
5
|
+
module GarbageCollection
|
|
6
|
+
class TrackingRactor < BaseRactor
|
|
7
|
+
class << self
|
|
8
|
+
def new
|
|
9
|
+
super(name: "GarbageCollection::TrackingRactor") do
|
|
10
|
+
tracker = Tracker.new
|
|
11
|
+
|
|
12
|
+
loop do
|
|
13
|
+
# SimpleCov branch coverage doesn't like that we aren't testing not matching anything
|
|
14
|
+
# but this does result in an error unlike case/when so no point in checking that.
|
|
15
|
+
# :nocov:
|
|
16
|
+
case receive
|
|
17
|
+
# :nocov:
|
|
18
|
+
in :track_ractorized_object, ractorized_object
|
|
19
|
+
tracker.track_ractorized_object(ractorized_object)
|
|
20
|
+
in :cleanup_after_ractorized_object, ractorized_object_id
|
|
21
|
+
tracker.cleanup_after_ractorized_object(ractorized_object_id)
|
|
22
|
+
in :track_thunk, thunk_id, thunk_ractor
|
|
23
|
+
tracker.track_thunk(thunk_id, thunk_ractor)
|
|
24
|
+
in :cleanup_after_thunk, thunk_id
|
|
25
|
+
tracker.cleanup_after_thunk(thunk_id)
|
|
26
|
+
end
|
|
27
|
+
rescue TrackingRactor::ClosedError
|
|
28
|
+
# do nothing
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
TRACKING_RACTOR = TrackingRactor.new
|
|
36
|
+
private_constant :TRACKING_RACTOR
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Ractorize
|
|
2
|
+
module GarbageCollection
|
|
3
|
+
class << self
|
|
4
|
+
def track_ractorized_object(ractorized_object)
|
|
5
|
+
# We have to define the finalizer here, not in the tracker, because it's not frozen yet
|
|
6
|
+
ObjectSpace.define_finalizer(ractorized_object, &finalize_proc)
|
|
7
|
+
Object.instance_method(:freeze).bind_call(ractorized_object)
|
|
8
|
+
|
|
9
|
+
begin
|
|
10
|
+
TRACKING_RACTOR << [:track_ractorized_object, ractorized_object].freeze
|
|
11
|
+
rescue TrackingRactor::ClosedError
|
|
12
|
+
# do nothing
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def track_thunk(thunk)
|
|
17
|
+
# We have to define the finalizer here, not in the tracker, because it's not frozen yet
|
|
18
|
+
ObjectSpace.define_finalizer(thunk, &finalize_thunk_proc)
|
|
19
|
+
|
|
20
|
+
begin
|
|
21
|
+
TRACKING_RACTOR << [:track_thunk, thunk.__object_id__, thunk.__thunk_ractor__].freeze
|
|
22
|
+
rescue TrackingRactor::ClosedError
|
|
23
|
+
# do nothing
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def cleanup_after_ractorized_object(ractorized_object_id)
|
|
28
|
+
TRACKING_RACTOR << [:cleanup_after_ractorized_object, ractorized_object_id].freeze
|
|
29
|
+
rescue Ractor::ClosedError
|
|
30
|
+
# do nothing
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def cleanup_after_thunk(thunk_id)
|
|
34
|
+
TRACKING_RACTOR << [:cleanup_after_thunk, thunk_id].freeze
|
|
35
|
+
rescue Ractor::ClosedError
|
|
36
|
+
# do nothing
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def finalize_proc
|
|
42
|
+
proc do |ractorized_object_id|
|
|
43
|
+
::Ractorize::GarbageCollection.cleanup_after_ractorized_object(ractorized_object_id)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def finalize_thunk_proc
|
|
48
|
+
proc do |thunk_id|
|
|
49
|
+
::Ractorize::GarbageCollection.cleanup_after_thunk(thunk_id)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
require_relative "../base_ractor"
|
|
2
|
+
|
|
3
|
+
module Ractorize
|
|
4
|
+
class RactorizedObject < BasicObject
|
|
5
|
+
class RactorizedRactor < ::BaseRactor
|
|
6
|
+
class << self
|
|
7
|
+
def new(name: nil)
|
|
8
|
+
super do
|
|
9
|
+
mode = receive
|
|
10
|
+
|
|
11
|
+
object = case mode
|
|
12
|
+
when :class
|
|
13
|
+
klass, args, opts, block = receive
|
|
14
|
+
target_class = klass
|
|
15
|
+
klass.new(*args.freeze, **opts.freeze, &block)
|
|
16
|
+
when :object
|
|
17
|
+
o = receive
|
|
18
|
+
target_class = o.class
|
|
19
|
+
o
|
|
20
|
+
when :class_arg_by_arg
|
|
21
|
+
klass = receive
|
|
22
|
+
target_class = klass
|
|
23
|
+
|
|
24
|
+
args, opts, block = Ractorize.extract_args(self)
|
|
25
|
+
|
|
26
|
+
klass.new(*args.freeze, **opts.freeze, &block)
|
|
27
|
+
else
|
|
28
|
+
# :nocov:
|
|
29
|
+
raise "Invalid mode #{mode}"
|
|
30
|
+
# :nocov:
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
loop do
|
|
34
|
+
# rubocop:disable Lint/UselessAssignment
|
|
35
|
+
value = method_name = method_args = opts = return_port = thunk_ractor = block_given = nil
|
|
36
|
+
# rubocop:enable Lint/UselessAssignment
|
|
37
|
+
method_name, method_args, opts, return_port, thunk_ractor, block_given = receive
|
|
38
|
+
|
|
39
|
+
case method_name
|
|
40
|
+
when :__close__
|
|
41
|
+
begin
|
|
42
|
+
# Time is not movable but also not shareable!
|
|
43
|
+
move = !Ractor.shareable?(object) && !(Time === object)
|
|
44
|
+
thunk_ractor&.send([:success, object].freeze, move:)
|
|
45
|
+
rescue RactorizedRactor::ClosedError
|
|
46
|
+
# do nothing
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
object = nil
|
|
50
|
+
close
|
|
51
|
+
break
|
|
52
|
+
when :__target_object_id__
|
|
53
|
+
return_port = method_args
|
|
54
|
+
|
|
55
|
+
target_object_id = ::Object.instance_method(:object_id).bind_call(object)
|
|
56
|
+
return_port << target_object_id
|
|
57
|
+
else
|
|
58
|
+
if method_name == :__invoke_arg_by_arg__
|
|
59
|
+
args_port = Ractorize::RactorizedObject::RactorizedRactor::Port.new
|
|
60
|
+
return_port << args_port
|
|
61
|
+
|
|
62
|
+
method_name = args_port.receive
|
|
63
|
+
method_args, opts = Ractorize.extract_args(args_port)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
if block_given
|
|
67
|
+
block_result_port = Ractorize::RactorizedObject::RactorizedRactor::Port.new
|
|
68
|
+
|
|
69
|
+
value = object.__send__(method_name, *method_args, **opts) do |*args, **opts, &b|
|
|
70
|
+
Ractorize.prepare_args(target_class, args, opts, skip_move: true)
|
|
71
|
+
|
|
72
|
+
return_port << [:yield, [args.dup.freeze, opts.dup.freeze, b].freeze, block_result_port].freeze
|
|
73
|
+
|
|
74
|
+
outcome_type, return_value = block_result_port.receive
|
|
75
|
+
|
|
76
|
+
case outcome_type
|
|
77
|
+
when :normal
|
|
78
|
+
return_value
|
|
79
|
+
when :break
|
|
80
|
+
# TODO: handle error situation
|
|
81
|
+
break
|
|
82
|
+
else
|
|
83
|
+
# :nocov:
|
|
84
|
+
raise "Not sure how to handle outcome_type #{outcome_type}"
|
|
85
|
+
# :nocov:
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
return_port << [:return, value].freeze
|
|
90
|
+
else
|
|
91
|
+
value = object.__send__(method_name, *method_args, **opts)
|
|
92
|
+
value = value.__value__ while Ractorize::Thunk === value
|
|
93
|
+
|
|
94
|
+
begin
|
|
95
|
+
if thunk_ractor
|
|
96
|
+
thunk_ractor.send([:success, value].freeze)
|
|
97
|
+
else
|
|
98
|
+
return_port << value
|
|
99
|
+
end
|
|
100
|
+
rescue IOError => e
|
|
101
|
+
# Unclear why this sometimes manifests as this error instead of ClosedError but
|
|
102
|
+
# need to handle them both.
|
|
103
|
+
# :nocov:
|
|
104
|
+
raise unless e.message == "closed stream"
|
|
105
|
+
# :nocov:
|
|
106
|
+
rescue Ractor::ClosedError
|
|
107
|
+
# Whoa... this error inherits from StopIteration and will kill the loop!!!
|
|
108
|
+
# Nothing really to do here but keep the loop going and handle other
|
|
109
|
+
# method calls to the ractorized object from other ractors.
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
nil
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
nil
|
|
118
|
+
# object
|
|
119
|
+
rescue => e
|
|
120
|
+
# :nocov:
|
|
121
|
+
puts
|
|
122
|
+
puts "an unhandled error!!! #{e.class} #{e.message} #{e}"
|
|
123
|
+
puts e.backtrace
|
|
124
|
+
puts
|
|
125
|
+
|
|
126
|
+
raise
|
|
127
|
+
# :nocov:
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -3,22 +3,32 @@ require_relative "thunk"
|
|
|
3
3
|
|
|
4
4
|
module Ractorize
|
|
5
5
|
class RactorizedObject < BasicObject
|
|
6
|
+
class << self
|
|
7
|
+
def method_should_use_thunk?(method_symbol)
|
|
8
|
+
method_symbol != :== && method_symbol != :! && method_symbol != :!= &&
|
|
9
|
+
method_symbol != :inspect && method_symbol != :to_s &&
|
|
10
|
+
!method_symbol.end_with?("?") && method_symbol != :hash
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
attr_reader :__object_id__, :__ractor__
|
|
15
|
+
|
|
6
16
|
def initialize(mode, *args, **opts, &block)
|
|
7
|
-
@
|
|
17
|
+
@__ractor__ = RactorizedRactor.new(name: "#{args.first}<#{args.first.object_id}>".freeze)
|
|
8
18
|
|
|
9
19
|
case mode
|
|
10
20
|
when :object
|
|
11
|
-
@
|
|
21
|
+
@__ractor__ << :object
|
|
12
22
|
|
|
13
23
|
outside_object = args.first
|
|
14
|
-
|
|
24
|
+
@__target_object_id__ = ::Object.instance_method(:object_id).bind_call(outside_object)
|
|
15
25
|
@__target_class__ = outside_object.class
|
|
16
26
|
|
|
17
27
|
if ::Ractor.shareable?(outside_object)
|
|
18
|
-
@
|
|
28
|
+
@__ractor__ << outside_object
|
|
19
29
|
else
|
|
20
30
|
::Ractorize.resolve_all_thunks(outside_object)
|
|
21
|
-
@
|
|
31
|
+
@__ractor__.send(outside_object, move: true)
|
|
22
32
|
end
|
|
23
33
|
when :class
|
|
24
34
|
klass, *args = args
|
|
@@ -28,60 +38,69 @@ module Ractorize
|
|
|
28
38
|
to_move = ::Ractorize.prepare_args(@__target_class__, args, opts)
|
|
29
39
|
|
|
30
40
|
if to_move&.any?
|
|
31
|
-
@
|
|
32
|
-
@
|
|
41
|
+
@__ractor__ << :class_arg_by_arg
|
|
42
|
+
@__ractor__ << klass
|
|
33
43
|
|
|
34
44
|
args.each do |arg|
|
|
35
|
-
@
|
|
36
|
-
@
|
|
45
|
+
@__ractor__ << :arg
|
|
46
|
+
@__ractor__.send(arg, move: to_move.include?(arg))
|
|
37
47
|
end
|
|
38
48
|
|
|
39
49
|
opts.each_pair do |name, value|
|
|
40
|
-
@
|
|
41
|
-
@
|
|
42
|
-
@
|
|
50
|
+
@__ractor__ << :kwarg
|
|
51
|
+
@__ractor__ << name
|
|
52
|
+
@__ractor__.send(value, move: to_move.include?(value))
|
|
43
53
|
end
|
|
44
54
|
|
|
45
55
|
if block
|
|
46
|
-
@
|
|
47
|
-
@
|
|
56
|
+
@__ractor__ << :block
|
|
57
|
+
@__ractor__ << block
|
|
48
58
|
end
|
|
49
59
|
|
|
50
|
-
@
|
|
60
|
+
@__ractor__ << :done
|
|
51
61
|
else
|
|
52
|
-
@
|
|
53
|
-
@
|
|
62
|
+
@__ractor__ << :class
|
|
63
|
+
@__ractor__ << [klass, args.freeze, opts.dup.freeze, block].freeze
|
|
54
64
|
end
|
|
65
|
+
|
|
66
|
+
return_port = ::Ractor::Port.new
|
|
67
|
+
@__ractor__ << [:__target_object_id__, return_port].freeze
|
|
68
|
+
@__target_object_id__ = return_port.receive
|
|
55
69
|
else
|
|
56
70
|
# :nocov:
|
|
57
71
|
::Kernel.raise "Invalid mode #{mode}"
|
|
58
72
|
# :nocov:
|
|
59
73
|
end
|
|
60
74
|
|
|
61
|
-
::Object.instance_method(:
|
|
75
|
+
@__object_id__ = ::Object.instance_method(:object_id).bind_call(self)
|
|
76
|
+
|
|
77
|
+
::Ractorize::GarbageCollection.track_ractorized_object(self)
|
|
62
78
|
end
|
|
63
79
|
|
|
64
80
|
def __close__ = method_missing(:__close__)
|
|
65
81
|
|
|
66
82
|
def __join__
|
|
67
83
|
__close__
|
|
68
|
-
@
|
|
84
|
+
@__ractor__.join
|
|
69
85
|
self
|
|
70
86
|
end
|
|
71
87
|
|
|
72
88
|
def method_missing(method_name, *args, **opts, &block)
|
|
73
|
-
if @
|
|
89
|
+
if @__ractor__.default_port.closed?
|
|
74
90
|
::Kernel.raise ::Ractor::ClosedError,
|
|
75
91
|
"You already closed this Ractorized instance of #{@__target_class__}!\n" \
|
|
76
92
|
"No more methods can be sent to it but you sent #{method_name}"
|
|
77
93
|
end
|
|
78
94
|
|
|
79
|
-
return_port = ::
|
|
95
|
+
return_port = ::Ractorize::RactorizedObject::RactorizedRactor::Port.new
|
|
96
|
+
thunk_ractor = if !block && RactorizedObject.method_should_use_thunk?(method_name)
|
|
97
|
+
::Ractorize::Thunk::ThunkRactor.new
|
|
98
|
+
end
|
|
80
99
|
|
|
81
100
|
to_move = ::Ractorize.prepare_args(@__target_class__, args, opts)
|
|
82
101
|
|
|
83
102
|
if to_move&.any?
|
|
84
|
-
@
|
|
103
|
+
@__ractor__ << [:__invoke_arg_by_arg__, [].freeze, {}.freeze, return_port, thunk_ractor, !!block]
|
|
85
104
|
|
|
86
105
|
args_port = return_port.receive
|
|
87
106
|
args_port << method_name
|
|
@@ -99,7 +118,7 @@ module Ractorize
|
|
|
99
118
|
|
|
100
119
|
args_port << :done
|
|
101
120
|
else
|
|
102
|
-
@
|
|
121
|
+
@__ractor__ << [method_name, args.dup.freeze, opts.dup.freeze, return_port, thunk_ractor, !!block].freeze
|
|
103
122
|
end
|
|
104
123
|
|
|
105
124
|
if block
|
|
@@ -121,28 +140,38 @@ module Ractorize
|
|
|
121
140
|
# TODO: yielded_block likely won't work when actually used
|
|
122
141
|
# so we should probably instead just raise an exception
|
|
123
142
|
# TODO: handle break and also raise in the block
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
143
|
+
begin
|
|
144
|
+
broke = true
|
|
145
|
+
block_result = block.call(*yielded_args.freeze, **yielded_opts.freeze, &yielded_block)
|
|
146
|
+
broke = false
|
|
147
|
+
ensure
|
|
148
|
+
block_result = block_result.__value__ while ::Ractorize::Thunk === block_result
|
|
149
|
+
|
|
150
|
+
block_result_port << if broke
|
|
151
|
+
# TODO: handle error situation
|
|
152
|
+
:break
|
|
153
|
+
else
|
|
154
|
+
[:normal, block_result].freeze
|
|
155
|
+
end
|
|
156
|
+
end
|
|
129
157
|
end
|
|
130
158
|
end
|
|
131
159
|
|
|
132
160
|
value
|
|
133
161
|
# Let's assume the user would rather block on all predicate methods than
|
|
134
162
|
# incorrectly get a non-truthy value (thunk is always truthy even if it evaluates as nil/false)
|
|
135
|
-
elsif
|
|
136
|
-
|
|
163
|
+
elsif thunk_ractor
|
|
164
|
+
return_port.close
|
|
165
|
+
Thunk.new(thunk_ractor)
|
|
166
|
+
else
|
|
137
167
|
value = return_port.receive
|
|
168
|
+
return_port.close
|
|
138
169
|
|
|
139
170
|
# :nocov:
|
|
140
171
|
::Kernel.raise ::Ractorize::Thunk::EscapingRactorError if ::Ractorize::Thunk === value
|
|
141
172
|
# :nocov:
|
|
142
173
|
|
|
143
174
|
value
|
|
144
|
-
else
|
|
145
|
-
Thunk.new(return_port)
|
|
146
175
|
end
|
|
147
176
|
end
|
|
148
177
|
|
|
@@ -159,18 +188,17 @@ module Ractorize
|
|
|
159
188
|
method_missing(:respond_to?, method_name, include_all)
|
|
160
189
|
end
|
|
161
190
|
|
|
162
|
-
def ==(other) = method_missing(:==, other)
|
|
163
|
-
def !=(other) = method_missing(:==, other)
|
|
191
|
+
def ==(other) = method_missing(:==, other) || super
|
|
192
|
+
def !=(other) = method_missing(:==, other) || super
|
|
164
193
|
def ! = method_missing(:!)
|
|
165
|
-
def equal?(other) = method_missing(:equal?, other)
|
|
166
|
-
|
|
194
|
+
# def equal?(other) = method_missing(:equal?, other) || super
|
|
167
195
|
def to_s = inspect
|
|
168
196
|
|
|
197
|
+
# delegating this to the target object increases risk of a deadlock since
|
|
198
|
+
# sometimes #inspect calls #inspect on other objects and can lead to reentry and thus deadlock
|
|
169
199
|
def inspect
|
|
170
200
|
object_id = ::Object.instance_method(:object_id).bind(self).call
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
"RactorizedObject<#{object_id}>[#{moved_object_inspect}]".freeze
|
|
201
|
+
"RactorizedObject<#{object_id}>[#{@__target_class__}<#{@__target_object_id__}>]}"
|
|
174
202
|
end
|
|
175
203
|
end
|
|
176
204
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require_relative "../base_ractor"
|
|
2
|
+
|
|
3
|
+
module Ractorize
|
|
4
|
+
class Thunk < BasicObject
|
|
5
|
+
class ThunkRactor < ::BaseRactor
|
|
6
|
+
class << self
|
|
7
|
+
def new
|
|
8
|
+
super do
|
|
9
|
+
# SimpleCov seems to want us to handle the case where nothing matches but that would be an error
|
|
10
|
+
# :nocov:
|
|
11
|
+
case receive
|
|
12
|
+
# :nocov:
|
|
13
|
+
in :__close__
|
|
14
|
+
# do nothing
|
|
15
|
+
in :success, value
|
|
16
|
+
value
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/src/ractorize/thunk.rb
CHANGED
|
@@ -2,14 +2,18 @@ module Ractorize
|
|
|
2
2
|
class Thunk < BasicObject
|
|
3
3
|
class EscapingRactorError < ::StandardError; end
|
|
4
4
|
|
|
5
|
-
attr_accessor :
|
|
5
|
+
attr_accessor :__thunk_ractor__, :__object_id__
|
|
6
6
|
|
|
7
|
-
def initialize(
|
|
8
|
-
self.
|
|
9
|
-
self.
|
|
7
|
+
def initialize(return_value_portlike)
|
|
8
|
+
self.__thunk_ractor__ = return_value_portlike
|
|
9
|
+
self.__object_id__ = ::Object.instance_method(:object_id).bind_call(self)
|
|
10
|
+
::Ractorize::GarbageCollection.track_thunk(self)
|
|
10
11
|
end
|
|
11
12
|
|
|
12
13
|
def initialize_clone(...)
|
|
14
|
+
# :nocov:
|
|
15
|
+
raise "CAREFUL! THUNK CLONED!!"
|
|
16
|
+
# :nocov:
|
|
13
17
|
# is this actually necessary?? Seems so?
|
|
14
18
|
end
|
|
15
19
|
|
|
@@ -24,24 +28,11 @@ module Ractorize
|
|
|
24
28
|
def __value__
|
|
25
29
|
return @__value__ if defined?(@__value__)
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
# :nocov:
|
|
31
|
-
::Kernel.raise EscapingRactorError,
|
|
32
|
-
"Somehow this thunk was passed between ractors but wasn't resolved first."
|
|
33
|
-
# :nocov:
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# :nocov:
|
|
37
|
-
::Kernel.raise EscapingRactorError if ::Ractorize::Thunk === value
|
|
38
|
-
# :nocov:
|
|
39
|
-
|
|
40
|
-
@__value__ = value
|
|
41
|
-
|
|
42
|
-
::Object.instance_method(:freeze).bind(self).call
|
|
31
|
+
@__value__ = __thunk_ractor__.join.value
|
|
32
|
+
self.__thunk_ractor__ = nil
|
|
33
|
+
::Object.instance_method(:freeze).bind_call(self)
|
|
43
34
|
|
|
44
|
-
|
|
35
|
+
@__value__
|
|
45
36
|
end
|
|
46
37
|
|
|
47
38
|
def ! = !__value__
|
data/src/ractorize.rb
CHANGED
|
@@ -4,6 +4,22 @@ require_relative "ractorize/ractorized_class"
|
|
|
4
4
|
|
|
5
5
|
module Ractorize
|
|
6
6
|
class << self
|
|
7
|
+
def ractorize_object(object)
|
|
8
|
+
RactorizedObject.new(:object, object)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def ractorize_class(klass)
|
|
12
|
+
RactorizedClass[klass]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def [](object)
|
|
16
|
+
if object.is_a?(Class)
|
|
17
|
+
ractorize_class(object)
|
|
18
|
+
else
|
|
19
|
+
ractorize_object(object)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
7
23
|
# TODO: figure out a way to magically get a ractor-shareable proc from a non-ractor-shareable proc
|
|
8
24
|
def auto_freeze(target, class_or_proc = nil)
|
|
9
25
|
@auto_freeze = @auto_freeze ? @auto_freeze.dup : []
|
|
@@ -64,6 +80,7 @@ module Ractorize
|
|
|
64
80
|
# Not sure why that is but we need to handle that case.
|
|
65
81
|
def resolve_all_thunks(structure)
|
|
66
82
|
each_thunk(structure, &:__value__)
|
|
83
|
+
nil
|
|
67
84
|
end
|
|
68
85
|
|
|
69
86
|
def to_move(target_class, args)
|
|
@@ -147,28 +164,8 @@ module Ractorize
|
|
|
147
164
|
to_move(target_class, args)
|
|
148
165
|
end
|
|
149
166
|
|
|
150
|
-
def each_thunk(structure, seen = Set.new, &
|
|
151
|
-
|
|
152
|
-
return if seen.include?(structure)
|
|
153
|
-
|
|
154
|
-
seen << structure
|
|
155
|
-
|
|
156
|
-
case structure
|
|
157
|
-
when Array
|
|
158
|
-
structure.each { each_thunk(it, seen, &block) }
|
|
159
|
-
when Hash
|
|
160
|
-
each_thunk(structure.keys, seen, &block)
|
|
161
|
-
each_thunk(structure.values, seen, &block)
|
|
162
|
-
when Struct
|
|
163
|
-
each_thunk(structure.values, seen, &block)
|
|
164
|
-
else
|
|
165
|
-
ivarsget = ::Object.instance_method(:instance_variables)
|
|
166
|
-
iget = ::Object.instance_method(:instance_variable_get)
|
|
167
|
-
|
|
168
|
-
ivarsget.bind(structure).call.each do |var|
|
|
169
|
-
each_thunk(iget.bind(structure).call(var), seen, &block)
|
|
170
|
-
end
|
|
171
|
-
end
|
|
167
|
+
def each_thunk(structure, seen = Set.new, &)
|
|
168
|
+
each_instance_of(Thunk, structure, seen, 0, &)
|
|
172
169
|
end
|
|
173
170
|
|
|
174
171
|
def extract_args(port_like)
|
|
@@ -200,123 +197,37 @@ module Ractorize
|
|
|
200
197
|
|
|
201
198
|
[args, opts, block]
|
|
202
199
|
end
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
# Putting this in a constant so we can get test coverage on it since not sure how to get coverage
|
|
206
|
-
# on something inside a ractor.
|
|
207
|
-
RACTOR_PROC = proc do
|
|
208
|
-
mode = receive
|
|
209
|
-
|
|
210
|
-
object = case mode
|
|
211
|
-
when :class
|
|
212
|
-
klass, args, opts, block = receive
|
|
213
|
-
target_class = klass
|
|
214
|
-
klass.new(*args.freeze, **opts.freeze, &block)
|
|
215
|
-
when :object
|
|
216
|
-
o = receive
|
|
217
|
-
target_class = o.class
|
|
218
|
-
o
|
|
219
|
-
when :class_arg_by_arg
|
|
220
|
-
klass = receive
|
|
221
|
-
target_class = klass
|
|
222
|
-
|
|
223
|
-
args, opts, block = Ractorize.extract_args(self)
|
|
224
|
-
|
|
225
|
-
klass.new(*args.freeze, **opts.freeze, &block)
|
|
226
|
-
else
|
|
227
|
-
# :nocov:
|
|
228
|
-
raise "Invalid mode #{mode}"
|
|
229
|
-
# :nocov:
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
loop do
|
|
233
|
-
method_name, method_args, opts, return_port, block_given = receive
|
|
234
|
-
|
|
235
|
-
case method_name
|
|
236
|
-
when :__close__
|
|
237
|
-
return_port.<<(object, move: true)
|
|
238
|
-
close
|
|
239
|
-
break
|
|
240
|
-
else
|
|
241
|
-
if method_name == :__invoke_arg_by_arg__
|
|
242
|
-
args_port = Ractor::Port.new
|
|
243
|
-
return_port << args_port
|
|
244
|
-
|
|
245
|
-
method_name = args_port.receive
|
|
246
|
-
method_args, opts = Ractorize.extract_args(args_port)
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
if block_given
|
|
250
|
-
block_result_port = Ractor::Port.new
|
|
251
200
|
|
|
252
|
-
|
|
253
|
-
Ractorize.prepare_args(target_class, args, opts, skip_move: true)
|
|
201
|
+
private
|
|
254
202
|
|
|
255
|
-
|
|
203
|
+
def each_instance_of(klass, structure, seen = Set.new, depth = 0, &block)
|
|
204
|
+
depth += 1
|
|
205
|
+
if klass === structure
|
|
206
|
+
block.call(structure)
|
|
207
|
+
end
|
|
208
|
+
return if seen.include?(structure)
|
|
256
209
|
|
|
257
|
-
|
|
210
|
+
seen << structure
|
|
258
211
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
212
|
+
case structure
|
|
213
|
+
when Array
|
|
214
|
+
structure.each { each_instance_of(klass, it, seen, depth, &block) }
|
|
215
|
+
when Hash
|
|
216
|
+
each_instance_of(klass, structure.keys, seen, depth, &block)
|
|
217
|
+
each_instance_of(klass, structure.values, seen, depth, &block)
|
|
218
|
+
when Struct
|
|
219
|
+
each_instance_of(klass, structure.values, seen, depth, &block)
|
|
220
|
+
else
|
|
221
|
+
ivarsget = ::Object.instance_method(:instance_variables)
|
|
222
|
+
iget = ::Object.instance_method(:instance_variable_get)
|
|
270
223
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
value
|
|
274
|
-
value = value.__value__ while Ractorize::Thunk === value
|
|
275
|
-
|
|
276
|
-
begin
|
|
277
|
-
return_port.send(value)
|
|
278
|
-
rescue IOError => e
|
|
279
|
-
# Unclear why this sometimes manifests as this error instead of ClosedError but
|
|
280
|
-
# need to handle them both.
|
|
281
|
-
# :nocov:
|
|
282
|
-
raise unless e.message == "closed stream"
|
|
283
|
-
# :nocov:
|
|
284
|
-
rescue Ractor::ClosedError
|
|
285
|
-
# Whoa... this error inherits from StopIteration and will kill the loop!!!
|
|
286
|
-
# Nothing really to do here but keep the loop going and handle other
|
|
287
|
-
# method calls to the ractorized object from other ractors.
|
|
288
|
-
end
|
|
224
|
+
ivarsget.bind(structure).call.each do |var|
|
|
225
|
+
value = iget.bind(structure).call(var)
|
|
226
|
+
each_instance_of(klass, value, seen, depth, &block)
|
|
289
227
|
end
|
|
290
228
|
end
|
|
291
|
-
end
|
|
292
|
-
|
|
293
|
-
object
|
|
294
|
-
rescue => e
|
|
295
|
-
# :nocov:
|
|
296
|
-
puts
|
|
297
|
-
puts "an unhandled error!!! #{e.class} #{e.message} #{e}"
|
|
298
|
-
puts e.backtrace
|
|
299
|
-
puts
|
|
300
229
|
|
|
301
|
-
|
|
302
|
-
# :nocov:
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
class << self
|
|
306
|
-
def ractorize_object(object)
|
|
307
|
-
RactorizedObject.new(:object, object)
|
|
308
|
-
end
|
|
309
|
-
|
|
310
|
-
def ractorize_class(klass)
|
|
311
|
-
RactorizedClass[klass]
|
|
312
|
-
end
|
|
313
|
-
|
|
314
|
-
def [](object)
|
|
315
|
-
if object.is_a?(Class)
|
|
316
|
-
ractorize_class(object)
|
|
317
|
-
else
|
|
318
|
-
ractorize_object(object)
|
|
319
|
-
end
|
|
230
|
+
nil
|
|
320
231
|
end
|
|
321
232
|
end
|
|
322
233
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ractorize
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Miles Georgi
|
|
@@ -21,9 +21,15 @@ files:
|
|
|
21
21
|
- README.md
|
|
22
22
|
- lib/ractorize.rb
|
|
23
23
|
- src/ractorize.rb
|
|
24
|
+
- src/ractorize/base_ractor.rb
|
|
25
|
+
- src/ractorize/garbage_collection.rb
|
|
26
|
+
- src/ractorize/garbage_collection/tracker.rb
|
|
27
|
+
- src/ractorize/garbage_collection/tracking_ractor.rb
|
|
24
28
|
- src/ractorize/ractorized_class.rb
|
|
25
29
|
- src/ractorize/ractorized_object.rb
|
|
30
|
+
- src/ractorize/ractorized_object/ractorized_ractor.rb
|
|
26
31
|
- src/ractorize/thunk.rb
|
|
32
|
+
- src/ractorize/thunk/thunk_ractor.rb
|
|
27
33
|
homepage: https://github.com/ractor-shack/ractorize
|
|
28
34
|
licenses:
|
|
29
35
|
- MPL-2.0
|