ractorize 0.0.9 → 0.0.11
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 +8 -0
- data/README.md +9 -0
- data/src/ractorize/garbage_collection/tracker.rb +27 -2
- data/src/ractorize/garbage_collection/tracking_ractor.rb +1 -14
- data/src/ractorize/garbage_collection.rb +19 -1
- data/src/ractorize/ractorized_object/ractorized_ractor.rb +20 -11
- data/src/ractorize/ractorized_object.rb +8 -8
- data/src/ractorize/thunk/thunk_ractor.rb +2 -2
- data/src/ractorize/thunk.rb +12 -5
- data/src/ractorize.rb +10 -10
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4600c739ac97a907068b19020d82d72c695df5dba8e47c56620cada3418de052
|
|
4
|
+
data.tar.gz: ea91548851760e76582f06e9dc6e49bc4626a648442ce7503b59b40df6c47ccf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 198181e33f7fdab8ca7a98713d39b0dadffe240273353e6d9f28de6fbda3be139d9cd4f92df99e678231a05bcb41a276547f16d2553bb6e1a09d56e632cb615f
|
|
7
|
+
data.tar.gz: adfd5a6ab472f0d1b3479ed51cc401b0112bc563798594863067c7f6c4cd3284210a1e7a148e2ffc9645607dd3bbab5e977ffcc60201ae62dea0edc71f40b30f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## [0.0.11] - 2026-07-31
|
|
2
|
+
|
|
3
|
+
- Log if a RactorizedRactor is taken down via ClosedError
|
|
4
|
+
|
|
5
|
+
## [0.0.10] - 2026-06-30
|
|
6
|
+
|
|
7
|
+
- Handle thunks being cloned
|
|
8
|
+
|
|
1
9
|
## [0.0.9] - 2026-06-29
|
|
2
10
|
|
|
3
11
|
- Fix bug that results from moving Time, which is neither shareable nor movable!
|
data/README.md
CHANGED
|
@@ -274,6 +274,15 @@ Note that if you call either `#__close__` or `#__join__` on the object, then the
|
|
|
274
274
|
|
|
275
275
|
An easy way to avoid the deadlock is just don't make any use of such an object after closing it.
|
|
276
276
|
|
|
277
|
+
### Objects returned from a method called on a ractorized object might be cloned!
|
|
278
|
+
|
|
279
|
+
You might have a "void" method where you don't care about the return value but it also doesn't
|
|
280
|
+
return `nil` instead it just returns the last expression arbitrarily.
|
|
281
|
+
|
|
282
|
+
This return value (or parts of it) will be cloned by CRuby/Ractor if it's not shareable!
|
|
283
|
+
|
|
284
|
+
Confusing stuff can happen with certain types of objects when cloned, such as open files.
|
|
285
|
+
|
|
277
286
|
## Fine print
|
|
278
287
|
|
|
279
288
|
Ractors are still experimental and so this gem is also still experimental.
|
|
@@ -2,11 +2,13 @@ module Ractorize
|
|
|
2
2
|
module GarbageCollection
|
|
3
3
|
class Tracker
|
|
4
4
|
attr_accessor :ractorized_object_id_to_ractor,
|
|
5
|
-
:thunk_id_to_ractor
|
|
5
|
+
:thunk_id_to_ractor,
|
|
6
|
+
:ractor_to_thunk_ids
|
|
6
7
|
|
|
7
8
|
def initialize
|
|
8
9
|
self.ractorized_object_id_to_ractor = ObjectSpace::WeakMap.new
|
|
9
10
|
self.thunk_id_to_ractor = ObjectSpace::WeakMap.new
|
|
11
|
+
self.ractor_to_thunk_ids = ObjectSpace::WeakKeyMap.new
|
|
10
12
|
end
|
|
11
13
|
|
|
12
14
|
def track_ractorized_object(ractorized_object)
|
|
@@ -24,9 +26,32 @@ module Ractorize
|
|
|
24
26
|
thunk_id_to_ractor[thunk_id] = ractor
|
|
25
27
|
end
|
|
26
28
|
|
|
29
|
+
def thunk_cloned(old_thunk_id, new_thunk_id, thunk_ractor)
|
|
30
|
+
thunk_ids = ractor_to_thunk_ids[thunk_ractor]
|
|
31
|
+
|
|
32
|
+
if thunk_ids
|
|
33
|
+
thunk_ids << new_thunk_id
|
|
34
|
+
else
|
|
35
|
+
ractor_to_thunk_ids[thunk_ractor] = [old_thunk_id, new_thunk_id]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
thunk_id_to_ractor[new_thunk_id] = thunk_ractor
|
|
39
|
+
end
|
|
40
|
+
|
|
27
41
|
def cleanup_after_thunk(thunk_id)
|
|
28
42
|
ractor = thunk_id_to_ractor.delete(thunk_id)
|
|
29
|
-
|
|
43
|
+
|
|
44
|
+
return unless ractor
|
|
45
|
+
|
|
46
|
+
thunk_ids = ractor_to_thunk_ids[ractor]
|
|
47
|
+
|
|
48
|
+
if thunk_ids
|
|
49
|
+
thunk_ids.delete(thunk_id)
|
|
50
|
+
|
|
51
|
+
return unless thunk_ids.empty?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
ractor << :__close__
|
|
30
55
|
rescue Ractor::ClosedError
|
|
31
56
|
# do nothing
|
|
32
57
|
end
|
|
@@ -10,20 +10,7 @@ module Ractorize
|
|
|
10
10
|
tracker = Tracker.new
|
|
11
11
|
|
|
12
12
|
loop do
|
|
13
|
-
|
|
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
|
|
13
|
+
tracker.send(*receive)
|
|
27
14
|
rescue TrackingRactor::ClosedError
|
|
28
15
|
# do nothing
|
|
29
16
|
end
|
|
@@ -14,7 +14,7 @@ module Ractorize
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def track_thunk(thunk)
|
|
17
|
-
# We have to define the finalizer here, not in the tracker, because it's not
|
|
17
|
+
# We have to define the finalizer here, not in the tracker, because it's not shareable
|
|
18
18
|
ObjectSpace.define_finalizer(thunk, &finalize_thunk_proc)
|
|
19
19
|
|
|
20
20
|
begin
|
|
@@ -24,6 +24,24 @@ module Ractorize
|
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
def thunk_cloned(old_thunk, new_thunk)
|
|
28
|
+
ractor = old_thunk.__thunk_ractor__
|
|
29
|
+
|
|
30
|
+
# We have to define the finalizer here, not in the tracker, because it's not shareable
|
|
31
|
+
# ObjectSpace.define_finalizer(new_thunk, &finalize_thunk_proc)
|
|
32
|
+
|
|
33
|
+
begin
|
|
34
|
+
TRACKING_RACTOR << [
|
|
35
|
+
:thunk_cloned,
|
|
36
|
+
old_thunk.__object_id__,
|
|
37
|
+
new_thunk.__object_id__,
|
|
38
|
+
ractor
|
|
39
|
+
].freeze
|
|
40
|
+
rescue TrackingRactor::ClosedError
|
|
41
|
+
# do nothing
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
27
45
|
def cleanup_after_ractorized_object(ractorized_object_id)
|
|
28
46
|
TRACKING_RACTOR << [:cleanup_after_ractorized_object, ractorized_object_id].freeze
|
|
29
47
|
rescue Ractor::ClosedError
|
|
@@ -3,6 +3,8 @@ require_relative "../base_ractor"
|
|
|
3
3
|
module Ractorize
|
|
4
4
|
class RactorizedObject < BasicObject
|
|
5
5
|
class RactorizedRactor < ::BaseRactor
|
|
6
|
+
class UnexpectedClosedError < StandardError; end
|
|
7
|
+
|
|
6
8
|
class << self
|
|
7
9
|
def new(name: nil)
|
|
8
10
|
super do
|
|
@@ -25,15 +27,13 @@ module Ractorize
|
|
|
25
27
|
|
|
26
28
|
klass.new(*args.freeze, **opts.freeze, &block)
|
|
27
29
|
else
|
|
28
|
-
# :
|
|
30
|
+
# simplecov:disable
|
|
29
31
|
raise "Invalid mode #{mode}"
|
|
30
|
-
# :
|
|
32
|
+
# simplecov:enable
|
|
31
33
|
end
|
|
32
34
|
|
|
33
35
|
loop do
|
|
34
|
-
# rubocop:disable Lint/UselessAssignment
|
|
35
36
|
value = method_name = method_args = opts = return_port = thunk_ractor = block_given = nil
|
|
36
|
-
# rubocop:enable Lint/UselessAssignment
|
|
37
37
|
method_name, method_args, opts, return_port, thunk_ractor, block_given = receive
|
|
38
38
|
|
|
39
39
|
case method_name
|
|
@@ -80,9 +80,9 @@ module Ractorize
|
|
|
80
80
|
# TODO: handle error situation
|
|
81
81
|
break
|
|
82
82
|
else
|
|
83
|
-
# :
|
|
83
|
+
# simplecov:disable
|
|
84
84
|
raise "Not sure how to handle outcome_type #{outcome_type}"
|
|
85
|
-
# :
|
|
85
|
+
# simplecov:enable
|
|
86
86
|
end
|
|
87
87
|
end
|
|
88
88
|
|
|
@@ -100,10 +100,10 @@ module Ractorize
|
|
|
100
100
|
rescue IOError => e
|
|
101
101
|
# Unclear why this sometimes manifests as this error instead of ClosedError but
|
|
102
102
|
# need to handle them both.
|
|
103
|
-
# :
|
|
103
|
+
# simplecov:disable
|
|
104
104
|
raise unless e.message == "closed stream"
|
|
105
|
-
# :
|
|
106
|
-
rescue
|
|
105
|
+
# simplecov:enable
|
|
106
|
+
rescue RactorizedRactor::ClosedError
|
|
107
107
|
# Whoa... this error inherits from StopIteration and will kill the loop!!!
|
|
108
108
|
# Nothing really to do here but keep the loop going and handle other
|
|
109
109
|
# method calls to the ractorized object from other ractors.
|
|
@@ -112,19 +112,28 @@ module Ractorize
|
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
nil
|
|
115
|
+
rescue RactorizedRactor::ClosedError => e
|
|
116
|
+
# simplecov:disable
|
|
117
|
+
puts "unexpected closed error!"
|
|
118
|
+
puts e.backtrace
|
|
119
|
+
error = UnexpectedClosedError.new
|
|
120
|
+
error.set_backtrace(e.backtrace)
|
|
121
|
+
|
|
122
|
+
raise error
|
|
123
|
+
# simplecov:enable
|
|
115
124
|
end
|
|
116
125
|
|
|
117
126
|
nil
|
|
118
127
|
# object
|
|
119
128
|
rescue => e
|
|
120
|
-
# :
|
|
129
|
+
# simplecov:disable
|
|
121
130
|
puts
|
|
122
131
|
puts "an unhandled error!!! #{e.class} #{e.message} #{e}"
|
|
123
132
|
puts e.backtrace
|
|
124
133
|
puts
|
|
125
134
|
|
|
126
135
|
raise
|
|
127
|
-
# :
|
|
136
|
+
# simplecov:enable
|
|
128
137
|
end
|
|
129
138
|
end
|
|
130
139
|
end
|
|
@@ -67,9 +67,9 @@ module Ractorize
|
|
|
67
67
|
@__ractor__ << [:__target_object_id__, return_port].freeze
|
|
68
68
|
@__target_object_id__ = return_port.receive
|
|
69
69
|
else
|
|
70
|
-
# :
|
|
70
|
+
# simplecov:disable
|
|
71
71
|
::Kernel.raise "Invalid mode #{mode}"
|
|
72
|
-
# :
|
|
72
|
+
# simplecov:enable
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
@__object_id__ = ::Object.instance_method(:object_id).bind_call(self)
|
|
@@ -131,9 +131,9 @@ module Ractorize
|
|
|
131
131
|
# Seems SimpleCov branch coverage doesn't like that we don't test the non-exhaustive
|
|
132
132
|
# pattern path, but since that's purely defensive I have no interest in testing it.
|
|
133
133
|
|
|
134
|
-
# :
|
|
134
|
+
# simplecov:disable
|
|
135
135
|
case data
|
|
136
|
-
# :
|
|
136
|
+
# simplecov:enable
|
|
137
137
|
in :return, value
|
|
138
138
|
stop = true
|
|
139
139
|
in :yield, [yielded_args, yielded_opts, yielded_block], block_result_port
|
|
@@ -167,19 +167,19 @@ module Ractorize
|
|
|
167
167
|
value = return_port.receive
|
|
168
168
|
return_port.close
|
|
169
169
|
|
|
170
|
-
# :
|
|
170
|
+
# simplecov:disable
|
|
171
171
|
::Kernel.raise ::Ractorize::Thunk::EscapingRactorError if ::Ractorize::Thunk === value
|
|
172
|
-
# :
|
|
172
|
+
# simplecov:enable
|
|
173
173
|
|
|
174
174
|
value
|
|
175
175
|
end
|
|
176
176
|
end
|
|
177
177
|
|
|
178
178
|
def respond_to?(method_name, include_all = false)
|
|
179
|
-
# :
|
|
179
|
+
# simplecov:disable
|
|
180
180
|
# This line is only here for when commenting out < BasicObject when debugging stuff
|
|
181
181
|
return super if ::Object === self
|
|
182
|
-
# :
|
|
182
|
+
# simplecov:enable
|
|
183
183
|
|
|
184
184
|
respond_to_missing?(method_name, include_all)
|
|
185
185
|
end
|
|
@@ -7,9 +7,9 @@ module Ractorize
|
|
|
7
7
|
def new
|
|
8
8
|
super do
|
|
9
9
|
# SimpleCov seems to want us to handle the case where nothing matches but that would be an error
|
|
10
|
-
# :
|
|
10
|
+
# simplecov:disable
|
|
11
11
|
case receive
|
|
12
|
-
# :
|
|
12
|
+
# simplecov:enable
|
|
13
13
|
in :__close__
|
|
14
14
|
# do nothing
|
|
15
15
|
in :success, value
|
data/src/ractorize/thunk.rb
CHANGED
|
@@ -10,13 +10,20 @@ module Ractorize
|
|
|
10
10
|
::Ractorize::GarbageCollection.track_thunk(self)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
def initialize_clone(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
def initialize_clone(original_thunk)
|
|
14
|
+
if __resolved__? && original_thunk.__resolved__?
|
|
15
|
+
# Seems this could happen if we had a frozen thunk whose @__value__ is not shareable
|
|
16
|
+
# Since the thunk's ractor is already gone nothing to worry about
|
|
17
|
+
return
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
self.__object_id__ = ::Object.instance_method(:object_id).bind_call(self)
|
|
21
|
+
|
|
22
|
+
::Ractorize::GarbageCollection.thunk_cloned(original_thunk, self)
|
|
18
23
|
end
|
|
19
24
|
|
|
25
|
+
def __resolved__? = defined?(@__value__)
|
|
26
|
+
|
|
20
27
|
def method_missing(...)
|
|
21
28
|
__value__.__send__(...)
|
|
22
29
|
end
|
data/src/ractorize.rb
CHANGED
|
@@ -25,16 +25,16 @@ module Ractorize
|
|
|
25
25
|
@auto_freeze = @auto_freeze ? @auto_freeze.dup : []
|
|
26
26
|
|
|
27
27
|
unless Ractor.shareable?(target)
|
|
28
|
-
# :
|
|
28
|
+
# simplecov:disable
|
|
29
29
|
raise "#{target} isn't shareable so can't use it to auto-freeze"
|
|
30
|
-
# :
|
|
30
|
+
# simplecov:enable
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
@auto_freeze << if class_or_proc
|
|
34
34
|
unless Ractor.shareable?(class_or_proc)
|
|
35
|
-
# :
|
|
35
|
+
# simplecov:disable
|
|
36
36
|
raise "#{class_or_proc} isn't shareable so can't use it to auto-freeze"
|
|
37
|
-
# :
|
|
37
|
+
# simplecov:enable
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
[target, class_or_proc]
|
|
@@ -49,16 +49,16 @@ module Ractorize
|
|
|
49
49
|
@move_arg = @move_arg ? @move_arg.dup : []
|
|
50
50
|
|
|
51
51
|
unless Ractor.shareable?(target)
|
|
52
|
-
# :
|
|
52
|
+
# simplecov:disable
|
|
53
53
|
raise "#{target} isn't shareable so can't use it to auto-freeze"
|
|
54
|
-
# :
|
|
54
|
+
# simplecov:enable
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
@move_arg << if class_or_proc
|
|
58
58
|
unless Ractor.shareable?(class_or_proc)
|
|
59
|
-
# :
|
|
59
|
+
# simplecov:disable
|
|
60
60
|
raise "#{class_or_proc} isn't shareable so can't use it to auto-freeze"
|
|
61
|
-
# :
|
|
61
|
+
# simplecov:enable
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
[target, class_or_proc]
|
|
@@ -189,9 +189,9 @@ module Ractorize
|
|
|
189
189
|
when :done
|
|
190
190
|
break
|
|
191
191
|
else
|
|
192
|
-
# :
|
|
192
|
+
# simplecov:disable
|
|
193
193
|
::Kernel.raise "Unknown class_by_arg arg type #{arg_type}"
|
|
194
|
-
# :
|
|
194
|
+
# simplecov:enable
|
|
195
195
|
end
|
|
196
196
|
end
|
|
197
197
|
|
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.11
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Miles Georgi
|
|
@@ -46,13 +46,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
46
46
|
- - ">="
|
|
47
47
|
- !ruby/object:Gem::Version
|
|
48
48
|
version: 4.0.0
|
|
49
|
+
- - "<"
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '4.1'
|
|
49
52
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
50
53
|
requirements:
|
|
51
54
|
- - ">="
|
|
52
55
|
- !ruby/object:Gem::Version
|
|
53
56
|
version: '0'
|
|
54
57
|
requirements: []
|
|
55
|
-
rubygems_version: 4.0.
|
|
58
|
+
rubygems_version: 4.0.16
|
|
56
59
|
specification_version: 4
|
|
57
60
|
summary: Turn objects into ractors with ease!
|
|
58
61
|
test_files: []
|