ractorize 0.0.3 → 0.0.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f4125b52255fca881ea212e46e55e4c795e79b39f8d8369580022e67db2eb9a8
4
- data.tar.gz: 91f58df84879ac297d0111f5880faf8ea95804fdb4ae17345f7702e579fbfb42
3
+ metadata.gz: 717f8ece3185d394b66aade1e78947eb5bb859535a8fa4afdaa88e33ad821cc9
4
+ data.tar.gz: af1aeaf9cc3c4b76625ba228ed5ed60cd77afde597cbcafd4ca059ca2ec52488
5
5
  SHA512:
6
- metadata.gz: 691e4eb2b5a1d436b366e93256e7af917308ea3ca3292205791023f92948202464279d4e11e3251c9d842ac63e79a1d5e1a3c377747e07ced9956722a67e2d68
7
- data.tar.gz: ef0e4f07687797d2036f326674150ba9ff1e5e07a604936109f4ff0e0811bb19de85d22a5834c8684ed49860cc4f27943d3a292960378c46a2c661f561e91b05
6
+ metadata.gz: 188fa21adfbdeff96e906f1c38ed41beeab1f4e41d6581a4494cd3245611ae457848b8f0571cd04a83ba453a539073ff4152c2ac5b72ea395d40ed4ada0df4f4
7
+ data.tar.gz: 2c5ffbd40dbc7f2399eed5a8350eaa86a372e2cdc2efec07389c4a16be9deeeba9c9dea1490784306126629682b4444a8b0ffc6b321469b647e71a45c2c5db16
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [0.0.5] - 2026-05-30
2
+
3
+ - Support auto-freezing certain method-arguments
4
+ - Support moving method arguments if they are not shareable
5
+ - Give ractors a name to help with debugging
6
+
7
+ ## [0.0.4] - 2026-05-23
8
+
9
+ - Prevent thunks from crossing ractor boundaries
10
+ - Create instances of ractorized classes inside the ractor instead of moving them
11
+ - Add support for methods that take blocks
12
+ - Add #to_s/#inspect to RactorizedObject to help with debugging
13
+ - Use #__send__ instead of #send to work with BasicObject
14
+
1
15
  ## [0.0.3] - 2026-05-21
2
16
 
3
17
  - Treat ==, !=, and ! as predicates and delegate them to the ractor (as well as #equal?)
data/README.md CHANGED
@@ -68,9 +68,160 @@ took 0.195 seconds
68
68
  $
69
69
  ```
70
70
 
71
+ ## Advanced usage/some niceties
72
+
73
+ ### Auto-freeze non-shareable stuff passed to ractorized objects/methods
74
+
75
+ Not really in the mood to track down all the strings you're sending to your ractorized objects
76
+ that happen to be non-shareable due to not being frozen? Or maybe you're in the mood
77
+ but don't control the code where they are being initialized? You can just auto-freeze them!
78
+
79
+ You can use `Ractorize.auto_freeze` for that.
80
+
81
+ A few flavors:
82
+
83
+ #### Auto-freezing any instance of a class
84
+
85
+ Let's just freeze all strings sent to any ractorized object.
86
+
87
+ ```ruby
88
+ Ractorize.auto_freeze(String)
89
+
90
+ h = Ractorize[{}]
91
+
92
+ key = "foo"
93
+ value = "bar"
94
+
95
+ puts "value frozen? #{value.frozen?}"
96
+ h[key] = value
97
+ puts "value frozen? #{value.frozen?}"
98
+ ```
99
+
100
+ This results in:
101
+
102
+ ```
103
+ key frozen? false value frozen? false
104
+ key frozen? true value frozen? true
105
+ ```
106
+
107
+ #### Only auto-freezing stuff passed to a specific type of ractorized object
108
+
109
+ You can specify that auto-freezing should only apply to ractorized objects of a specific class.
110
+
111
+ Let's say you want to freeze stuff passed to ractorized instances of Array but not interfere with
112
+ anything ractorized instances of Hash might be doing. You can do this like so:
113
+
114
+ ```ruby
115
+ Ractorize.auto_freeze(Array, String)
116
+
117
+ h = Ractorize[{}]
118
+
119
+ key = "foo"
120
+ value = "bar"
121
+
122
+ puts "Before Hash#[]= value frozen? #{value.frozen?}"
123
+ h[key] = value
124
+ puts "After Hash#[]= value frozen? #{value.frozen?}"
125
+
126
+ a = Ractorize[[]]
127
+
128
+ a.push(value)
129
+ puts "After Array#push value frozen? #{value.frozen?}"
130
+ ```
131
+
132
+ This prints out:
133
+
134
+ ```
135
+ Before Hash#[]= value frozen? false
136
+ After Hash#[]= value frozen? false
137
+ After Array#push value frozen? true
138
+ ```
139
+
140
+ So only sending the string to an Array resulted in auto-freezing it.
141
+
142
+ #### programmatically expressing when to auto-freeze
143
+
144
+ You can also pass a proc to express whether or not to autofreeze an object:
145
+
146
+ ```ruby
147
+ Ractorize.auto_freeze(Ractor.shareable_proc { it.is_a?(String) && it =~ /baz/ })
148
+
149
+ a = Ractorize[[]]
150
+
151
+ strings = ["foo", "bar", "baz"]
152
+
153
+ strings.each { a.push(it) }
154
+
155
+ puts strings.map(&:frozen?).inspect
156
+ ```
157
+
158
+ This outputs:
159
+
160
+ ```
161
+ [false, false, true]
162
+ ```
163
+
164
+ Notice that only the last string, which meets the criteria, was frozen.
165
+
166
+ ### How to move arguments to the receiving ractorized object
167
+
168
+ You can express that you'd like an argument to be moved to the receiving ractorized object.
169
+
170
+ This allows you to not have to worry about if the argument is shareable or not.
171
+
172
+ You will get errors, though, when trying to make use of the moved argument in the calling
173
+ code, just like when using ractors directly and moving objects between them.
174
+
175
+ The interface is identical to `.auto_feeze` but through the method `.move_arg`:
176
+
177
+ ```ruby
178
+ class Foo
179
+ def object_id_of(s) = s.object_id
180
+ end
181
+
182
+ foo = Ractorize[Foo.new]
183
+ s = "asdf"
184
+
185
+ puts "calling ractor s.object_id before #push: #{s.object_id}"
186
+ puts "object_id in receiving ractor Foo#object_id_of: #{foo.object_id_of(s)}"
187
+ puts "s.length in calling ractor: #{s.length}"
188
+ puts
189
+
190
+ puts "Configuring all String instances to be moved to receiving ractor"
191
+ puts
192
+ Ractorize.move_arg(String)
193
+
194
+ puts "calling ractor s.object_id before #push: #{s.object_id}"
195
+ puts "object_id in receiving ractor Foo#object_id_of: #{foo.object_id_of(s)}"
196
+ puts "s.length in calling ractor: #{s.length}"
197
+ ```
198
+
199
+ this outputs:
200
+
201
+ ```
202
+ calling ractor s.object_id before #push: 896
203
+ object_id in receiving ractor Foo#object_id_of: 904
204
+ s.length in calling ractor: 4
205
+
206
+ Configuring all String instances to be moved to receiving ractor
207
+
208
+ calling ractor s.object_id before #push: 896
209
+ object_id in receiving ractor Foo#object_id_of: 896
210
+ example_scripts/auto_freeze/move-arg:25:in 'Ractor::MovedObject#method_missing': can not send any methods to a moved object (Ractor::MovedError)
211
+ ```
212
+
213
+ Notice that before we configure String to be moved, `foo` receives a copy of `s`, hence the different
214
+ object_id.
215
+
216
+ But once we configure String to be moved, now `foo` receives `s` instead of a copy, hence the object_id
217
+ being the same.
218
+
219
+ However, then when we try to print out the length of `s` in the calling ractor, we get a `Ractor::MovedError`.
220
+
71
221
  ## Gotchas
72
222
 
73
- ### Predicate methods not ending in "?" will always return truthy values!
223
+ ### Predicate methods not ending in "?" in `if/unless/until/while/case/when/in` statements will always return truthy values!
224
+
74
225
  If you try to use the return value of a ractorized object (or any instance of a ractorized class)
75
226
  in a boolean expression, it will always be truthy!!
76
227
 
@@ -108,6 +259,13 @@ end
108
259
 
109
260
  This will correctly print out `It's not empty!`.
110
261
 
262
+ Note that this isn't necessary with methods ending in "?" as this will automatically block
263
+ and return the boolean value.
264
+
265
+ Also, the predicate methods `==`, `!=` and `!` also will automatically block and return the boolean
266
+ value, just like methods ending in "?". So you can freely do `if Ractorize["asdf"] == "asdf"` works
267
+ perfectly fine just like predicate methods ending in "?".
268
+
111
269
  ### Calling a method on a closed ractorized object might result in a deadlock!
112
270
 
113
271
  It will usually raise a `Ractor::CloseError` but once in a while it can deadlock.
@@ -1,16 +1,14 @@
1
1
  module Ractorize
2
2
  class RactorizedClass
3
3
  class << self
4
- attr_accessor :target_class
5
-
6
4
  def [](klass)
7
5
  ractorized_class = Class.new(RactorizedClass)
8
- ractorized_class.target_class = klass
6
+ ractorized_class.define_singleton_method(:target_class, Ractor.shareable_proc { klass })
9
7
  ractorized_class
10
8
  end
11
9
 
12
10
  def new(...)
13
- Ractorize.ractorize_object(target_class.new(...))
11
+ RactorizedObject.new(:class, target_class, ...)
14
12
  end
15
13
 
16
14
  def method_missing(method_name, ...)
@@ -3,18 +3,61 @@ require_relative "thunk"
3
3
 
4
4
  module Ractorize
5
5
  class RactorizedObject < BasicObject
6
- def initialize(outside_object)
7
- @ractor = ::Ractor.new(&RACTOR_PROC)
6
+ def initialize(mode, *args, **opts, &block)
7
+ @ractor = ::Ractor.new(name: "#{args.first}<#{args.first.object_id}>", &RACTOR_PROC)
8
8
 
9
- # It doesn't seem like we have a way to move the object into the ractor via its constructor so do
10
- # it with #<< instead.
11
- if ::Ractor.shareable?(outside_object)
12
- @ractor << outside_object
9
+ case mode
10
+ when :object
11
+ @ractor << :object
12
+
13
+ outside_object = args.first
14
+
15
+ @__target_class__ = outside_object.class
16
+
17
+ if ::Ractor.shareable?(outside_object)
18
+ @ractor << outside_object
19
+ else
20
+ ::Ractorize.resolve_all_thunks(outside_object)
21
+ @ractor.send(outside_object, move: true)
22
+ end
23
+ when :class
24
+ klass, *args = args
25
+
26
+ @__target_class__ = klass
27
+
28
+ to_move = ::Ractorize.prepare_args(@__target_class__, args, opts)
29
+
30
+ if to_move&.any?
31
+ @ractor << :class_arg_by_arg
32
+ @ractor << klass
33
+
34
+ args.each do |arg|
35
+ @ractor << :arg
36
+ @ractor.send(arg, move: to_move.include?(arg))
37
+ end
38
+
39
+ opts.each_pair do |name, value|
40
+ @ractor << :kwarg
41
+ @ractor << name
42
+ @ractor.send(value, move: to_move.include?(value))
43
+ end
44
+
45
+ if block
46
+ @ractor << :block
47
+ @ractor << block
48
+ end
49
+
50
+ @ractor << :done
51
+ else
52
+ @ractor << :class
53
+ @ractor << [klass, args.freeze, opts.dup.freeze, block].freeze
54
+ end
13
55
  else
14
- @ractor.send(outside_object, move: true)
56
+ # :nocov:
57
+ ::Kernel.raise "Invalid mode #{mode}"
58
+ # :nocov:
15
59
  end
16
60
 
17
- # Wow, this works! Scary?
18
61
  ::Object.instance_method(:freeze).bind(self).call
19
62
  end
20
63
 
@@ -26,7 +69,7 @@ module Ractorize
26
69
  self
27
70
  end
28
71
 
29
- def method_missing(method_name, *args, **opts)
72
+ def method_missing(method_name, *args, **opts, &block)
30
73
  if @ractor.default_port.closed?
31
74
  ::Kernel.raise ::Ractor::ClosedError,
32
75
  "You already closed this Ractorized object! No more methods can be sent to it."
@@ -34,12 +77,69 @@ module Ractorize
34
77
 
35
78
  return_port = ::Ractor::Port.new
36
79
 
37
- @ractor << [method_name, args.dup.freeze, opts.dup.freeze, return_port].freeze
80
+ to_move = ::Ractorize.prepare_args(@__target_class__, args, opts)
81
+
82
+ if to_move&.any?
83
+ @ractor << [:__invoke_arg_by_arg__, [].freeze, {}.freeze, return_port, !!block]
84
+
85
+ args_port = return_port.receive
86
+ args_port << method_name
38
87
 
88
+ args.each do |arg|
89
+ args_port << :arg
90
+ args_port.send(arg, move: to_move.include?(arg))
91
+ end
92
+
93
+ opts.each_pair do |name, value|
94
+ args_port << :kwarg
95
+ args_port << name
96
+ args_port.send(value, move: to_move.include?(value))
97
+ end
98
+
99
+ args_port << :done
100
+ else
101
+ @ractor << [method_name, args.dup.freeze, opts.dup.freeze, return_port, !!block].freeze
102
+ end
103
+
104
+ if block
105
+ stop = false
106
+ value = nil
107
+
108
+ until stop
109
+ data = return_port.receive
110
+
111
+ # Seems SimpleCov branch coverage doesn't like that we don't test the non-exhaustive
112
+ # pattern path, but since that's purely defensive I have no interest in testing it.
113
+
114
+ # :nocov:
115
+ case data
116
+ # :nocov:
117
+ in :return, value
118
+ stop = true
119
+ in :yield, [yielded_args, yielded_opts, yielded_block], block_result_port
120
+ # TODO: yielded_block likely won't work when actually used
121
+ # so we should probably instead just raise an exception
122
+ # TODO: handle break and also raise in the block
123
+ block_result = block.call(*yielded_args.freeze, **yielded_opts.freeze, &yielded_block)
124
+
125
+ block_result = block_result.__value__ while ::Ractorize::Thunk === block_result
126
+
127
+ block_result_port << [:normal, block_result].freeze
128
+ end
129
+ end
130
+
131
+ value
39
132
  # Let's assume the user would rather block on all predicate methods than
40
133
  # incorrectly get a non-truthy value (thunk is always truthy even if it evaluates as nil/false)
41
- if method_name == :== || method_name == :! || method_name == :!= || method_name.end_with?("?")
42
- return_port.receive
134
+ elsif method_name == :== || method_name == :! || method_name == :!= ||
135
+ method_name == :inspect || method_name == :to_s || method_name.end_with?("?")
136
+ value = return_port.receive
137
+
138
+ # :nocov:
139
+ ::Kernel.raise ::Ractorize::Thunk::EscapingRactorError if ::Ractorize::Thunk === value
140
+ # :nocov:
141
+
142
+ value
43
143
  else
44
144
  Thunk.new(return_port)
45
145
  end
@@ -62,5 +162,14 @@ module Ractorize
62
162
  def !=(other) = method_missing(:==, other)
63
163
  def ! = method_missing(:!)
64
164
  def equal?(other) = method_missing(:equal?, other)
165
+
166
+ def to_s = inspect
167
+
168
+ def inspect
169
+ object_id = ::Object.instance_method(:object_id).bind(self).call
170
+ moved_object_inspect = method_missing(:inspect)
171
+
172
+ "RactorizedObject<#{object_id}>[#{moved_object_inspect}]".freeze
173
+ end
65
174
  end
66
175
  end
@@ -1,8 +1,11 @@
1
1
  module Ractorize
2
2
  class Thunk < BasicObject
3
- attr_accessor :__return_value_port__
3
+ class EscapingRactorError < ::StandardError; end
4
+
5
+ attr_accessor :__return_value_port__, :__ractor__
4
6
 
5
7
  def initialize(return_value_port)
8
+ self.__ractor__ = ::Ractor.current
6
9
  self.__return_value_port__ = return_value_port
7
10
  end
8
11
 
@@ -10,8 +13,8 @@ module Ractorize
10
13
  # is this actually necessary?? Seems so?
11
14
  end
12
15
 
13
- def method_missing(method_name, *)
14
- __value__.send(method_name, *)
16
+ def method_missing(...)
17
+ __value__.__send__(...)
15
18
  end
16
19
 
17
20
  def respond_to_missing?(method_name, include_all = false)
@@ -21,15 +24,29 @@ module Ractorize
21
24
  def __value__
22
25
  return @__value__ if defined?(@__value__)
23
26
 
24
- @__value__ = __return_value_port__.receive
25
- end
27
+ value = if ::Ractor.current == __ractor__
28
+ __return_value_port__.receive
29
+ else
30
+ # :nocov:
31
+ ::Kernel.raise EscapingRactorError,
32
+ "Somehow this thunk was passed between ractors but wasn't resolved first."
33
+ # :nocov:
34
+ end
26
35
 
27
- def !
28
- !__value__
29
- end
36
+ # :nocov:
37
+ ::Kernel.raise EscapingRactorError if ::Ractorize::Thunk === value
38
+ # :nocov:
30
39
 
31
- def ==(other)
32
- __value__ == other || super
40
+ @__value__ = value
41
+
42
+ ::Object.instance_method(:freeze).bind(self).call
43
+
44
+ value
33
45
  end
46
+
47
+ def ! = !__value__
48
+ def ==(other) = __value__ == other || super
49
+ def !=(other) = __value__ != other || super
50
+ def equal?(other) = __value__.equal?(other) || super
34
51
  end
35
52
  end
data/src/ractorize.rb CHANGED
@@ -3,13 +3,234 @@ require_relative "ractorize/ractorized_object"
3
3
  require_relative "ractorize/ractorized_class"
4
4
 
5
5
  module Ractorize
6
+ class << self
7
+ # TODO: figure out a way to magically get a ractor-shareable proc from a non-ractor-shareable proc
8
+ def auto_freeze(target, class_or_proc = nil)
9
+ @auto_freeze = @auto_freeze ? @auto_freeze.dup : []
10
+
11
+ unless Ractor.shareable?(target)
12
+ # :nocov:
13
+ raise "#{target} isn't shareable so can't use it to auto-freeze"
14
+ # :nocov:
15
+ end
16
+
17
+ @auto_freeze << if class_or_proc
18
+ unless Ractor.shareable?(class_or_proc)
19
+ # :nocov:
20
+ raise "#{class_or_proc} isn't shareable so can't use it to auto-freeze"
21
+ # :nocov:
22
+ end
23
+
24
+ [target, class_or_proc]
25
+ else
26
+ target
27
+ end
28
+
29
+ @auto_freeze.freeze
30
+ end
31
+
32
+ def move_arg(target, class_or_proc = nil)
33
+ @move_arg = @move_arg ? @move_arg.dup : []
34
+
35
+ unless Ractor.shareable?(target)
36
+ # :nocov:
37
+ raise "#{target} isn't shareable so can't use it to auto-freeze"
38
+ # :nocov:
39
+ end
40
+
41
+ @move_arg << if class_or_proc
42
+ unless Ractor.shareable?(class_or_proc)
43
+ # :nocov:
44
+ raise "#{class_or_proc} isn't shareable so can't use it to auto-freeze"
45
+ # :nocov:
46
+ end
47
+
48
+ [target, class_or_proc]
49
+ else
50
+ target
51
+ end
52
+
53
+ @move_arg.freeze
54
+ end
55
+
56
+ def any_thunks?(structure)
57
+ # rubocop:disable Lint/UnreachableLoop
58
+ each_thunk(structure) { return true }
59
+ # rubocop:enable Lint/UnreachableLoop
60
+ false
61
+ end
62
+
63
+ # Unfortunately, can't read from a port that a different ractor made.
64
+ # Not sure why that is but we need to handle that case.
65
+ def resolve_all_thunks(structure)
66
+ each_thunk(structure, &:__value__)
67
+ end
68
+
69
+ def to_move(target_class, args)
70
+ return unless @move_arg
71
+
72
+ move_set = nil
73
+
74
+ args.each do |arg|
75
+ next if Ractor.shareable?(arg)
76
+
77
+ @move_arg.each do |rule|
78
+ if rule.is_a?(::Array)
79
+ target, rule = rule
80
+ next unless target == target_class
81
+ end
82
+
83
+ move_it = if rule.is_a?(::Proc)
84
+ rule.call(arg)
85
+ else
86
+ rule === arg
87
+ end
88
+
89
+ if move_it
90
+ move_set ||= Set.new
91
+ move_set << arg
92
+ break
93
+ end
94
+ end
95
+ end
96
+
97
+ move_set
98
+ end
99
+
100
+ def apply_auto_freeze(target_class, arg)
101
+ return unless @auto_freeze
102
+ return if Ractor.shareable?(arg)
103
+
104
+ # TODO: should we handle instance variables like we do with thunks?
105
+ case arg
106
+ when ::Hash
107
+ arg.each_pair do |key, value|
108
+ apply_auto_freeze(target_class, key)
109
+ apply_auto_freeze(target_class, value)
110
+ end
111
+ when ::Array
112
+ arg.each { apply_auto_freeze(target_class, it) }
113
+ end
114
+
115
+ return if Ractor.shareable?(arg)
116
+
117
+ @auto_freeze.each do |rule|
118
+ if rule.is_a?(::Array)
119
+ target, rule = rule
120
+ next unless target == target_class
121
+ end
122
+
123
+ freeze_it = if rule.is_a?(::Proc)
124
+ rule.call(arg)
125
+ else
126
+ rule === arg
127
+ end
128
+
129
+ if freeze_it
130
+ arg.freeze
131
+ break
132
+ end
133
+ end
134
+ end
135
+
136
+ def prepare_args(target_class, args, opts, skip_move: false)
137
+ unless opts.empty?
138
+ args = [*args, *opts.values]
139
+ end
140
+
141
+ args.each { apply_auto_freeze(target_class, it) }
142
+
143
+ ::Ractorize.resolve_all_thunks(args)
144
+
145
+ return nil if skip_move
146
+
147
+ to_move(target_class, args)
148
+ end
149
+
150
+ def each_thunk(structure, seen = Set.new, &block)
151
+ return block.call(structure) if Thunk === structure
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
172
+ end
173
+
174
+ def extract_args(port_like)
175
+ args = []
176
+ opts = {}
177
+ block = nil
178
+
179
+ loop do
180
+ arg_type = port_like.__send__(:receive)
181
+
182
+ case arg_type
183
+ when :arg
184
+ args << port_like.__send__(:receive)
185
+ when :kwarg
186
+ name = port_like.__send__(:receive)
187
+ value = port_like.__send__(:receive)
188
+
189
+ opts[name] = value
190
+ when :block
191
+ block = port_like.__send__(:receive)
192
+ when :done
193
+ break
194
+ else
195
+ # :nocov:
196
+ ::Kernel.raise "Unknown class_by_arg arg type #{arg_type}"
197
+ # :nocov:
198
+ end
199
+ end
200
+
201
+ [args, opts, block]
202
+ end
203
+ end
204
+
6
205
  # Putting this in a constant so we can get test coverage on it since not sure how to get coverage
7
206
  # on something inside a ractor.
8
207
  RACTOR_PROC = proc do
9
- object = receive
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
+ ::Kernel.raise "Invalid mode #{mode}"
229
+ # :nocov:
230
+ end
10
231
 
11
232
  loop do
12
- method_name, method_args, opts, return_port = receive
233
+ method_name, method_args, opts, return_port, block_given = receive
13
234
 
14
235
  case method_name
15
236
  when :__close__
@@ -17,20 +238,62 @@ module Ractorize
17
238
  close
18
239
  break
19
240
  else
20
- value = object.__send__(method_name, *method_args, **opts)
241
+ if method_name == :__invoke_arg_by_arg__
242
+ args_port = Ractor::Port.new
243
+ return_port << args_port
21
244
 
22
- value = value.__value__ while Thunk === value
245
+ method_name = args_port.receive
246
+ method_args, opts = ::Ractorize.extract_args(args_port)
247
+ end
23
248
 
24
- return_port << value
249
+ if block_given
250
+ block_result_port = ::Ractor::Port.new
251
+
252
+ value = object.__send__(method_name, *method_args, **opts) do |*args, **opts, &b|
253
+ ::Ractorize.prepare_args(target_class, args, opts, skip_move: true)
254
+
255
+ return_port << [:yield, [args.dup.freeze, opts.dup.freeze, b].freeze, block_result_port].freeze
256
+
257
+ outcome_type, return_value = block_result_port.receive
258
+
259
+ case outcome_type
260
+ when :normal
261
+ return_value
262
+ when :break
263
+ break return_value
264
+ else
265
+ # :nocov:
266
+ ::Kernel.raise "Not sure how to handle outcome_type #{outcome_type}"
267
+ # :nocov:
268
+ end
269
+ end
270
+
271
+ return_port << [:return, value].freeze
272
+ else
273
+ value = object.__send__(method_name, *method_args, **opts)
274
+
275
+ value = value.__value__ while ::Ractorize::Thunk === value
276
+
277
+ return_port << value
278
+ end
25
279
  end
26
280
  end
27
281
 
28
282
  object
283
+ rescue => e
284
+ # :nocov:
285
+ ::Kernel.puts
286
+ ::Kernel.puts "an error!!! #{e.class} #{e.message} #{e}"
287
+ ::Kernel.puts e.backtrace
288
+ ::Kernel.puts
289
+
290
+ raise
291
+ # :nocov:
29
292
  end
30
293
 
31
294
  class << self
32
295
  def ractorize_object(object)
33
- RactorizedObject.new(object)
296
+ RactorizedObject.new(:object, object)
34
297
  end
35
298
 
36
299
  def ractorize_class(klass)
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.3
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miles Georgi