ractor-wrapper 0.3.0 → 0.4.0
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 +17 -0
- data/CLAUDE.md +76 -0
- data/README.md +322 -52
- data/lib/ractor/wrapper/version.rb +1 -1
- data/lib/ractor/wrapper.rb +497 -248
- metadata +5 -4
data/lib/ractor/wrapper.rb
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
##
|
|
4
|
-
# See ruby-
|
|
4
|
+
# See https://docs.ruby-lang.org/en/4.0/language/ractor_md.html for info on
|
|
5
|
+
# Ractors.
|
|
5
6
|
#
|
|
6
7
|
class Ractor
|
|
7
8
|
##
|
|
@@ -98,7 +99,7 @@ class Ractor
|
|
|
98
99
|
#
|
|
99
100
|
# # Create a wrapper around the database. A SQLite3::Database object
|
|
100
101
|
# # cannot be moved between Ractors, so we configure the wrapper to run
|
|
101
|
-
# # in the current Ractor.
|
|
102
|
+
# # in the current Ractor. We can also configure it to run multiple
|
|
102
103
|
# # worker threads because the database object itself is thread-safe.
|
|
103
104
|
# wrapper = Ractor::Wrapper.new(db, use_current_ractor: true, threads: 2)
|
|
104
105
|
#
|
|
@@ -111,17 +112,16 @@ class Ractor
|
|
|
111
112
|
# rows = wrapper.stub.execute("select * from numbers")
|
|
112
113
|
#
|
|
113
114
|
# # Here, we start two Ractors, and pass the stub to each one. The
|
|
114
|
-
# # wrapper's
|
|
115
|
-
#
|
|
116
|
-
# r1 = Ractor.new(wrapper.stub) do |db_stub|
|
|
115
|
+
# # wrapper's worker threads will handle the requests concurrently.
|
|
116
|
+
# r1 = Ractor.new(wrapper.stub) do |stub|
|
|
117
117
|
# 5.times do
|
|
118
|
-
#
|
|
118
|
+
# stub.execute("select * from numbers")
|
|
119
119
|
# end
|
|
120
120
|
# :ok
|
|
121
121
|
# end
|
|
122
|
-
# r2 = Ractor.new(wrapper.stub) do |
|
|
122
|
+
# r2 = Ractor.new(wrapper.stub) do |stub|
|
|
123
123
|
# 5.times do
|
|
124
|
-
#
|
|
124
|
+
# stub.execute("select * from numbers")
|
|
125
125
|
# end
|
|
126
126
|
# :ok
|
|
127
127
|
# end
|
|
@@ -138,7 +138,7 @@ class Ractor
|
|
|
138
138
|
# # When running a wrapper with :use_current_ractor, you do not need to
|
|
139
139
|
# # recover the object, because it was never moved. The recover_object
|
|
140
140
|
# # method is not available.
|
|
141
|
-
# # db2 = wrapper.recover_object # <= raises Ractor::Error
|
|
141
|
+
# # db2 = wrapper.recover_object # <= raises Ractor::Wrapper::Error
|
|
142
142
|
#
|
|
143
143
|
# ## Features
|
|
144
144
|
#
|
|
@@ -172,6 +172,22 @@ class Ractor
|
|
|
172
172
|
# through a wrapper.
|
|
173
173
|
#
|
|
174
174
|
class Wrapper
|
|
175
|
+
##
|
|
176
|
+
# Base class for errors raised by {Ractor::Wrapper}.
|
|
177
|
+
#
|
|
178
|
+
class Error < ::Ractor::Error; end
|
|
179
|
+
|
|
180
|
+
##
|
|
181
|
+
# Raised when a {Ractor::Wrapper} server has crashed unexpectedly.
|
|
182
|
+
#
|
|
183
|
+
class CrashedError < Error; end
|
|
184
|
+
|
|
185
|
+
##
|
|
186
|
+
# Raised when calling a method on a {Ractor::Wrapper} whose server has
|
|
187
|
+
# stopped and is no longer accepting calls.
|
|
188
|
+
#
|
|
189
|
+
class StoppedError < Error; end
|
|
190
|
+
|
|
175
191
|
##
|
|
176
192
|
# A stub that forwards calls to a wrapper.
|
|
177
193
|
#
|
|
@@ -206,234 +222,341 @@ class Ractor
|
|
|
206
222
|
end
|
|
207
223
|
|
|
208
224
|
##
|
|
209
|
-
#
|
|
210
|
-
#
|
|
225
|
+
# Configuration for a {Ractor::Wrapper}. An instance of this class is
|
|
226
|
+
# yielded by {Ractor::Wrapper#initialize} if a block is provided. Any
|
|
227
|
+
# settings made to the Configuration before the block returns take
|
|
228
|
+
# effect when the Wrapper is constructed.
|
|
211
229
|
#
|
|
212
|
-
class
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
@
|
|
221
|
-
@move_results = interpret_setting(move_results, move_data)
|
|
222
|
-
@move_block_arguments = interpret_setting(move_block_arguments, move_data)
|
|
223
|
-
@move_block_results = interpret_setting(move_block_results, move_data)
|
|
224
|
-
@execute_blocks_in_place = interpret_setting(execute_blocks_in_place, false)
|
|
225
|
-
freeze
|
|
230
|
+
class Configuration
|
|
231
|
+
##
|
|
232
|
+
# Set the name of the wrapper. This is shown in logging and is also
|
|
233
|
+
# used as the name of the wrapping Ractor.
|
|
234
|
+
#
|
|
235
|
+
# @param value [String, nil]
|
|
236
|
+
#
|
|
237
|
+
def name=(value)
|
|
238
|
+
@name = value ? value.to_s.freeze : nil
|
|
226
239
|
end
|
|
227
240
|
|
|
228
241
|
##
|
|
229
|
-
#
|
|
242
|
+
# Enable or disable internal debug logging.
|
|
230
243
|
#
|
|
231
|
-
|
|
232
|
-
|
|
244
|
+
# @param value [Boolean]
|
|
245
|
+
#
|
|
246
|
+
def enable_logging=(value)
|
|
247
|
+
@enable_logging = value ? true : false
|
|
233
248
|
end
|
|
234
249
|
|
|
235
250
|
##
|
|
236
|
-
#
|
|
251
|
+
# Set the number of worker threads. If the underlying object is
|
|
252
|
+
# thread-safe, a value of 2 or more allows concurrent calls. Leave at
|
|
253
|
+
# the default of 0 to handle calls sequentially without worker threads.
|
|
254
|
+
#
|
|
255
|
+
# @param value [Integer]
|
|
237
256
|
#
|
|
238
|
-
def
|
|
239
|
-
|
|
257
|
+
def threads=(value)
|
|
258
|
+
value = value.to_i
|
|
259
|
+
value = 0 if value.negative?
|
|
260
|
+
@threads = value
|
|
240
261
|
end
|
|
241
262
|
|
|
242
263
|
##
|
|
243
|
-
#
|
|
264
|
+
# If set to true, the wrapper server runs as Thread(s) inside the
|
|
265
|
+
# current Ractor rather than spawning a new isolated Ractor. Use this
|
|
266
|
+
# for objects that cannot be moved between Ractors.
|
|
267
|
+
#
|
|
268
|
+
# @param value [Boolean]
|
|
244
269
|
#
|
|
245
|
-
def
|
|
246
|
-
@
|
|
270
|
+
def use_current_ractor=(value)
|
|
271
|
+
@use_current_ractor = value ? true : false
|
|
247
272
|
end
|
|
248
273
|
|
|
249
274
|
##
|
|
250
|
-
#
|
|
275
|
+
# Configure how argument and return values are communicated for the given
|
|
276
|
+
# method.
|
|
277
|
+
#
|
|
278
|
+
# In general, the following values are recognized for the data-moving
|
|
279
|
+
# settings:
|
|
280
|
+
#
|
|
281
|
+
# * `:copy` - Method arguments or return values that are not shareable,
|
|
282
|
+
# are *deep copied* when communicated between the caller and the object.
|
|
283
|
+
# * `:move` - Method arguments or return values that are not shareable,
|
|
284
|
+
# are *moved* when communicated between the caller and the object. This
|
|
285
|
+
# means they are no longer available to the source; that is, the caller
|
|
286
|
+
# can no longer access objects that were moved to method arguments, and
|
|
287
|
+
# the wrapped object can no longer access objects that were used as
|
|
288
|
+
# return values.
|
|
289
|
+
# * `:void` - This option is available for return values and block
|
|
290
|
+
# results. It disables return values for the given method, and is
|
|
291
|
+
# intended to avoid copying or moving objects that are not intended to
|
|
292
|
+
# be return values. The recipient will receive `nil`.
|
|
293
|
+
#
|
|
294
|
+
# The following settings are recognized for the `block_environment`
|
|
295
|
+
# setting:
|
|
251
296
|
#
|
|
252
|
-
|
|
253
|
-
|
|
297
|
+
# * `:caller` - Blocks are executed in the caller's context. This means
|
|
298
|
+
# the wrapper sends a message back to the caller to execute the block
|
|
299
|
+
# in its original context. This means the block will have access to its
|
|
300
|
+
# lexical scope and any other data available to the calling Ractor.
|
|
301
|
+
# * `:wrapped` - Blocks are executed directly in the wrapped object's
|
|
302
|
+
# context. This does not require any communication, but it means the
|
|
303
|
+
# block is removed from the caller's environment and does not have
|
|
304
|
+
# access to the caller's lexical scope or Ractor-accessible data.
|
|
305
|
+
#
|
|
306
|
+
# All settings are optional. If not provided, they will fall back to a
|
|
307
|
+
# default. If you are configuring a particular method, by specifying the
|
|
308
|
+
# `method_name` argument, any unspecified setting will fall back to the
|
|
309
|
+
# method default settings (which you can set by omitting the method name.)
|
|
310
|
+
# If you are configuring the method default settings, by omitting the
|
|
311
|
+
# `method_name` argument, unspecified settings will fall back to `:copy`
|
|
312
|
+
# for the data movement settings, and `:caller` for the
|
|
313
|
+
# `block_environment` setting.
|
|
314
|
+
#
|
|
315
|
+
# @param method_name [Symbol,nil] The name of the method being configured,
|
|
316
|
+
# or `nil` to set defaults for all methods not configured explicitly.
|
|
317
|
+
# @param arguments [:move,:copy] How to communicate method arguments.
|
|
318
|
+
# @param results [:move,:copy,:void] How to communicate method return
|
|
319
|
+
# values.
|
|
320
|
+
# @param block_arguments [:move,:copy] How to communicate block arguments.
|
|
321
|
+
# @param block_results [:move,:copy,:void] How to communicate block
|
|
322
|
+
# result values.
|
|
323
|
+
# @param block_environment [:caller,:wrapped] How to execute blocks, and
|
|
324
|
+
# what scope blocks have access to.
|
|
325
|
+
#
|
|
326
|
+
def configure_method(method_name = nil,
|
|
327
|
+
arguments: nil,
|
|
328
|
+
results: nil,
|
|
329
|
+
block_arguments: nil,
|
|
330
|
+
block_results: nil,
|
|
331
|
+
block_environment: nil)
|
|
332
|
+
method_name = method_name.to_sym unless method_name.nil?
|
|
333
|
+
@method_settings[method_name] =
|
|
334
|
+
MethodSettings.new(arguments: arguments,
|
|
335
|
+
results: results,
|
|
336
|
+
block_arguments: block_arguments,
|
|
337
|
+
block_results: block_results,
|
|
338
|
+
block_environment: block_environment)
|
|
339
|
+
self
|
|
254
340
|
end
|
|
255
341
|
|
|
256
342
|
##
|
|
257
|
-
# @
|
|
343
|
+
# @private
|
|
344
|
+
# Return the name of the wrapper.
|
|
345
|
+
#
|
|
346
|
+
# @return [String, nil]
|
|
347
|
+
#
|
|
348
|
+
attr_reader :name
|
|
349
|
+
|
|
350
|
+
##
|
|
351
|
+
# @private
|
|
352
|
+
# Return whether logging is enabled.
|
|
353
|
+
#
|
|
354
|
+
# @return [Boolean]
|
|
355
|
+
#
|
|
356
|
+
attr_reader :enable_logging
|
|
357
|
+
|
|
358
|
+
##
|
|
359
|
+
# @private
|
|
360
|
+
# Return the number of worker threads.
|
|
361
|
+
#
|
|
362
|
+
# @return [Integer]
|
|
363
|
+
#
|
|
364
|
+
attr_reader :threads
|
|
365
|
+
|
|
366
|
+
##
|
|
367
|
+
# @private
|
|
368
|
+
# Return whether the wrapper runs in the current Ractor.
|
|
369
|
+
#
|
|
370
|
+
# @return [Boolean]
|
|
371
|
+
#
|
|
372
|
+
attr_reader :use_current_ractor
|
|
373
|
+
|
|
374
|
+
##
|
|
375
|
+
# @private
|
|
376
|
+
# Resolve the method settings by filling in the defaults for all fields
|
|
377
|
+
# not explicitly set, and return the final settings keyed by method name.
|
|
378
|
+
# The `nil` key will contain defaults for method names not explicitly
|
|
379
|
+
# configured. This hash will be frozen and shareable.
|
|
258
380
|
#
|
|
259
|
-
|
|
260
|
-
|
|
381
|
+
# @return [Hash{(Symbol,nil)=>MethodSettings}]
|
|
382
|
+
#
|
|
383
|
+
def final_method_settings
|
|
384
|
+
fallback = MethodSettings.new(arguments: :copy, results: :copy,
|
|
385
|
+
block_arguments: :copy, block_results: :copy,
|
|
386
|
+
block_environment: :caller)
|
|
387
|
+
defaults = MethodSettings.with_fallback(@method_settings[nil], fallback)
|
|
388
|
+
results = {nil => defaults}
|
|
389
|
+
@method_settings.each do |name, settings|
|
|
390
|
+
next if name.nil?
|
|
391
|
+
results[name] = MethodSettings.with_fallback(settings, defaults)
|
|
392
|
+
end
|
|
393
|
+
results.freeze
|
|
261
394
|
end
|
|
262
395
|
|
|
263
|
-
|
|
396
|
+
##
|
|
397
|
+
# @private
|
|
398
|
+
# Create an empty configuration.
|
|
399
|
+
#
|
|
400
|
+
def initialize
|
|
401
|
+
@method_settings = {}
|
|
402
|
+
configure_method(arguments: nil,
|
|
403
|
+
results: nil,
|
|
404
|
+
block_arguments: nil,
|
|
405
|
+
block_results: nil,
|
|
406
|
+
block_environment: nil)
|
|
407
|
+
end
|
|
408
|
+
end
|
|
264
409
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
410
|
+
##
|
|
411
|
+
# Settings for a method call. Specifies how a method's arguments and
|
|
412
|
+
# return value are communicated (i.e. copy or move semantics.)
|
|
413
|
+
#
|
|
414
|
+
class MethodSettings
|
|
415
|
+
# @private
|
|
416
|
+
def initialize(arguments: nil,
|
|
417
|
+
results: nil,
|
|
418
|
+
block_arguments: nil,
|
|
419
|
+
block_results: nil,
|
|
420
|
+
block_environment: nil)
|
|
421
|
+
unless [nil, :copy, :move].include?(arguments)
|
|
422
|
+
raise ::ArgumentError, "Unknown `arguments`: #{arguments.inspect} (must be :copy or :move)"
|
|
423
|
+
end
|
|
424
|
+
unless [nil, :copy, :move, :void].include?(results)
|
|
425
|
+
raise ::ArgumentError, "Unknown `results`: #{results.inspect} (must be :copy, :move, or :void)"
|
|
426
|
+
end
|
|
427
|
+
unless [nil, :copy, :move].include?(block_arguments)
|
|
428
|
+
raise ::ArgumentError, "Unknown `block_arguments`: #{block_arguments.inspect} (must be :copy or :move)"
|
|
429
|
+
end
|
|
430
|
+
unless [nil, :copy, :move, :void].include?(block_results)
|
|
431
|
+
raise ::ArgumentError, "Unknown `block_results`: #{block_results.inspect} (must be :copy, :move, or :void)"
|
|
432
|
+
end
|
|
433
|
+
unless [nil, :caller, :wrapped].include?(block_environment)
|
|
434
|
+
raise ::ArgumentError,
|
|
435
|
+
"Unknown `block_environment`: #{block_environment.inspect} (must be :caller or :wrapped)"
|
|
270
436
|
end
|
|
437
|
+
@arguments = arguments
|
|
438
|
+
@results = results
|
|
439
|
+
@block_arguments = block_arguments
|
|
440
|
+
@block_results = block_results
|
|
441
|
+
@block_environment = block_environment
|
|
442
|
+
freeze
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
##
|
|
446
|
+
# @return [:copy,:move] How to communicate method arguments
|
|
447
|
+
# @return [nil] if not set (will not happen in final settings)
|
|
448
|
+
#
|
|
449
|
+
attr_reader :arguments
|
|
450
|
+
|
|
451
|
+
##
|
|
452
|
+
# @return [:copy,:move,:void] How to communicate method return values
|
|
453
|
+
# @return [nil] if not set (will not happen in final settings)
|
|
454
|
+
#
|
|
455
|
+
attr_reader :results
|
|
456
|
+
|
|
457
|
+
##
|
|
458
|
+
# @return [:copy,:move] How to communicate arguments to a block
|
|
459
|
+
# @return [nil] if not set (will not happen in final settings)
|
|
460
|
+
#
|
|
461
|
+
attr_reader :block_arguments
|
|
462
|
+
|
|
463
|
+
##
|
|
464
|
+
# @return [:copy,:move,:void] How to communicate block results
|
|
465
|
+
# @return [nil] if not set (will not happen in final settings)
|
|
466
|
+
#
|
|
467
|
+
attr_reader :block_results
|
|
468
|
+
|
|
469
|
+
##
|
|
470
|
+
# @return [:caller,:wrapped] What environment blocks execute in
|
|
471
|
+
# @return [nil] if not set (will not happen in final settings)
|
|
472
|
+
#
|
|
473
|
+
attr_reader :block_environment
|
|
474
|
+
|
|
475
|
+
# @private
|
|
476
|
+
def self.with_fallback(settings, fallback)
|
|
477
|
+
new(
|
|
478
|
+
arguments: settings.arguments || fallback.arguments,
|
|
479
|
+
results: settings.results || fallback.results,
|
|
480
|
+
block_arguments: settings.block_arguments || fallback.block_arguments,
|
|
481
|
+
block_results: settings.block_results || fallback.block_results,
|
|
482
|
+
block_environment: settings.block_environment || fallback.block_environment
|
|
483
|
+
)
|
|
271
484
|
end
|
|
272
485
|
end
|
|
273
486
|
|
|
274
487
|
##
|
|
275
488
|
# Create a wrapper around the given object.
|
|
276
489
|
#
|
|
277
|
-
# If you pass an optional block,
|
|
278
|
-
#
|
|
279
|
-
# particular, method
|
|
280
|
-
#
|
|
490
|
+
# If you pass an optional block, a {Ractor::Wrapper::Configuration} object
|
|
491
|
+
# will be yielded to it, allowing additional configuration before the wrapper
|
|
492
|
+
# starts. In particular, per-method configuration must be set in this block.
|
|
493
|
+
# Block-provided settings override keyword arguments.
|
|
494
|
+
#
|
|
495
|
+
# See {Configuration} for more information about the method communication
|
|
496
|
+
# and block settings.
|
|
281
497
|
#
|
|
282
498
|
# @param object [Object] The non-shareable object to wrap.
|
|
283
499
|
# @param use_current_ractor [boolean] If true, the wrapper is run in a
|
|
284
500
|
# thread in the current Ractor instead of spawning a new Ractor (the
|
|
285
501
|
# default behavior). This option can be used if the wrapped object
|
|
286
|
-
# cannot be moved or must run in the main Ractor.
|
|
287
|
-
#
|
|
502
|
+
# cannot be moved or must run in the main Ractor. Can also be set via
|
|
503
|
+
# the configuration block.
|
|
504
|
+
# @param name [String] A name for this wrapper. Used during logging. Can
|
|
505
|
+
# also be set via the configuration block. Defaults to the object_id.
|
|
288
506
|
# @param threads [Integer] The number of worker threads to run.
|
|
289
507
|
# Defaults to 0, which causes the wrapper to run sequentially without
|
|
290
|
-
# spawning workers.
|
|
291
|
-
# @param
|
|
292
|
-
#
|
|
293
|
-
#
|
|
294
|
-
#
|
|
295
|
-
#
|
|
296
|
-
#
|
|
297
|
-
# @param
|
|
298
|
-
#
|
|
299
|
-
# @param
|
|
300
|
-
#
|
|
301
|
-
#
|
|
302
|
-
# @param move_block_results [boolean] If true, result values from blocks
|
|
303
|
-
# are moved instead of copied by default. If not set, uses the
|
|
304
|
-
# `:move_data` setting.
|
|
305
|
-
# @param execute_blocks_in_place [boolean] If true, blocks passed to
|
|
306
|
-
# methods are made shareable and passed into the wrapper to be executed
|
|
307
|
-
# in the wrapped environment. If false (the default), blocks are
|
|
308
|
-
# replaced by a proc that passes messages back out to the caller and
|
|
309
|
-
# executes the block in the caller's environment.
|
|
508
|
+
# spawning workers. Can also be set via the configuration block.
|
|
509
|
+
# @param arguments [:move,:copy] How to communicate method arguments by
|
|
510
|
+
# default. If not specified, defaults to `:copy`.
|
|
511
|
+
# @param results [:move,:copy,:void] How to communicate method return
|
|
512
|
+
# values by default. If not specified, defaults to `:copy`.
|
|
513
|
+
# @param block_arguments [:move,:copy] How to communicate block arguments
|
|
514
|
+
# by default. If not specified, defaults to `:copy`.
|
|
515
|
+
# @param block_results [:move,:copy,:void] How to communicate block result
|
|
516
|
+
# values by default. If not specified, defaults to `:copy`.
|
|
517
|
+
# @param block_environment [:caller,:wrapped] How to execute blocks, and
|
|
518
|
+
# what scope blocks have access to. If not specified, defaults to
|
|
519
|
+
# `:caller`.
|
|
310
520
|
# @param enable_logging [boolean] Set to true to enable logging. Default
|
|
311
|
-
# is false.
|
|
521
|
+
# is false. Can also be set via the configuration block.
|
|
522
|
+
# @yield [config] An optional configuration block.
|
|
523
|
+
# @yieldparam config [Ractor::Wrapper::Configuration]
|
|
312
524
|
#
|
|
313
525
|
def initialize(object,
|
|
314
526
|
use_current_ractor: false,
|
|
315
527
|
name: nil,
|
|
316
528
|
threads: 0,
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
execute_blocks_in_place: nil,
|
|
529
|
+
arguments: nil,
|
|
530
|
+
results: nil,
|
|
531
|
+
block_arguments: nil,
|
|
532
|
+
block_results: nil,
|
|
533
|
+
block_environment: nil,
|
|
323
534
|
enable_logging: false)
|
|
324
535
|
raise ::Ractor::MovedError, "cannot wrap a moved object" if ::Ractor::MovedObject === object
|
|
325
536
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
yield
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
537
|
+
config = Configuration.new
|
|
538
|
+
config.name = name || object_id.to_s
|
|
539
|
+
config.enable_logging = enable_logging
|
|
540
|
+
config.threads = threads
|
|
541
|
+
config.use_current_ractor = use_current_ractor
|
|
542
|
+
config.configure_method(arguments: arguments,
|
|
543
|
+
results: results,
|
|
544
|
+
block_arguments: block_arguments,
|
|
545
|
+
block_results: block_results,
|
|
546
|
+
block_environment: block_environment)
|
|
547
|
+
yield config if block_given?
|
|
548
|
+
|
|
549
|
+
@name = config.name
|
|
550
|
+
@enable_logging = config.enable_logging
|
|
551
|
+
@threads = config.threads
|
|
552
|
+
@method_settings = config.final_method_settings
|
|
553
|
+
@stub = Stub.new(self)
|
|
554
|
+
|
|
555
|
+
if config.use_current_ractor
|
|
340
556
|
setup_local_server(object)
|
|
341
557
|
else
|
|
342
558
|
setup_isolated_server(object)
|
|
343
559
|
end
|
|
344
|
-
@stub = Stub.new(self)
|
|
345
|
-
|
|
346
|
-
freeze
|
|
347
|
-
end
|
|
348
|
-
|
|
349
|
-
##
|
|
350
|
-
# Set the number of threads to run in the wrapper. If the underlying object
|
|
351
|
-
# is thread-safe, setting a value of 2 or more allows concurrent calls to
|
|
352
|
-
# it. If the underlying object is not thread-safe, you should leave this
|
|
353
|
-
# set to its default of 0, which disables worker threads and handles all
|
|
354
|
-
# calls sequentially.
|
|
355
|
-
#
|
|
356
|
-
# This method can be called only during an initialization block.
|
|
357
|
-
# All settings are frozen once the wrapper is active.
|
|
358
|
-
#
|
|
359
|
-
# @param value [Integer]
|
|
360
|
-
#
|
|
361
|
-
def threads=(value)
|
|
362
|
-
value = value.to_i
|
|
363
|
-
value = 0 if value.negative?
|
|
364
|
-
@threads = value
|
|
365
|
-
end
|
|
366
|
-
|
|
367
|
-
##
|
|
368
|
-
# Enable or disable internal debug logging.
|
|
369
|
-
#
|
|
370
|
-
# This method can be called only during an initialization block.
|
|
371
|
-
# All settings are frozen once the wrapper is active.
|
|
372
|
-
#
|
|
373
|
-
# @param value [Boolean]
|
|
374
|
-
#
|
|
375
|
-
def enable_logging=(value)
|
|
376
|
-
@enable_logging = value ? true : false
|
|
377
|
-
end
|
|
378
|
-
|
|
379
|
-
##
|
|
380
|
-
# Set the name of this wrapper. This is shown in logging, and is also used
|
|
381
|
-
# as the name of the wrapping Ractor.
|
|
382
|
-
#
|
|
383
|
-
# This method can be called only during an initialization block.
|
|
384
|
-
# All settings are frozen once the wrapper is active.
|
|
385
|
-
#
|
|
386
|
-
# @param value [String, nil]
|
|
387
|
-
#
|
|
388
|
-
def name=(value)
|
|
389
|
-
@name = value ? value.to_s.freeze : nil
|
|
390
|
-
end
|
|
391
|
-
|
|
392
|
-
##
|
|
393
|
-
# Configure the move semantics for the given method (or the default
|
|
394
|
-
# settings if no method name is given.) That is, determine whether
|
|
395
|
-
# arguments, return values, and/or exceptions are copied or moved when
|
|
396
|
-
# communicated with the wrapper. By default, all objects are copied.
|
|
397
|
-
#
|
|
398
|
-
# This method can be called only during an initialization block.
|
|
399
|
-
# All settings are frozen once the wrapper is active.
|
|
400
|
-
#
|
|
401
|
-
# @param method_name [Symbol, nil] The name of the method being configured,
|
|
402
|
-
# or `nil` to set defaults for all methods not configured explicitly.
|
|
403
|
-
# @param move_data [boolean] If true, communication for this method will
|
|
404
|
-
# move instead of copy arguments and return values. Default is false.
|
|
405
|
-
# This setting can be overridden by other `:move_*` settings.
|
|
406
|
-
# @param move_arguments [boolean] If true, arguments for this method are
|
|
407
|
-
# moved instead of copied. If not set, uses the `:move_data` setting.
|
|
408
|
-
# @param move_results [boolean] If true, return values for this method are
|
|
409
|
-
# moved instead of copied. If not set, uses the `:move_data` setting.
|
|
410
|
-
# @param move_block_arguments [boolean] If true, arguments to blocks passed
|
|
411
|
-
# to this method are moved instead of copied. If not set, uses the
|
|
412
|
-
# `:move_data` setting.
|
|
413
|
-
# @param move_block_results [boolean] If true, result values from blocks
|
|
414
|
-
# passed to this method are moved instead of copied. If not set, uses
|
|
415
|
-
# the `:move_data` setting.
|
|
416
|
-
# @param execute_blocks_in_place [boolean] If true, blocks passed to this
|
|
417
|
-
# method are made shareable and passed into the wrapper to be executed
|
|
418
|
-
# in the wrapped environment. If false (the default), blocks are
|
|
419
|
-
# replaced by a proc that passes messages back out to the caller and
|
|
420
|
-
# executes the block in the caller's environment.
|
|
421
|
-
#
|
|
422
|
-
def configure_method(method_name = nil,
|
|
423
|
-
move_data: false,
|
|
424
|
-
move_arguments: nil,
|
|
425
|
-
move_results: nil,
|
|
426
|
-
move_block_arguments: nil,
|
|
427
|
-
move_block_results: nil,
|
|
428
|
-
execute_blocks_in_place: nil)
|
|
429
|
-
method_name = method_name.to_sym unless method_name.nil?
|
|
430
|
-
@method_settings[method_name] =
|
|
431
|
-
MethodSettings.new(move_data: move_data,
|
|
432
|
-
move_arguments: move_arguments,
|
|
433
|
-
move_results: move_results,
|
|
434
|
-
move_block_arguments: move_block_arguments,
|
|
435
|
-
move_block_results: move_block_results,
|
|
436
|
-
execute_blocks_in_place: execute_blocks_in_place)
|
|
437
560
|
end
|
|
438
561
|
|
|
439
562
|
##
|
|
@@ -478,8 +601,7 @@ class Ractor
|
|
|
478
601
|
# @return [MethodSettings]
|
|
479
602
|
#
|
|
480
603
|
def method_settings(method_name)
|
|
481
|
-
method_name
|
|
482
|
-
@method_settings[method_name] || @method_settings[nil]
|
|
604
|
+
(method_name && @method_settings[method_name.to_sym]) || @method_settings[nil]
|
|
483
605
|
end
|
|
484
606
|
|
|
485
607
|
##
|
|
@@ -500,7 +622,7 @@ class Ractor
|
|
|
500
622
|
#
|
|
501
623
|
def call(method_name, *args, **kwargs, &)
|
|
502
624
|
reply_port = ::Ractor::Port.new
|
|
503
|
-
transaction =
|
|
625
|
+
transaction = make_transaction
|
|
504
626
|
settings = method_settings(method_name)
|
|
505
627
|
block_arg = make_block_arg(settings, &)
|
|
506
628
|
message = CallMessage.new(method_name: method_name,
|
|
@@ -511,7 +633,11 @@ class Ractor
|
|
|
511
633
|
settings: settings,
|
|
512
634
|
reply_port: reply_port)
|
|
513
635
|
maybe_log("Sending method", method_name: method_name, transaction: transaction)
|
|
514
|
-
|
|
636
|
+
begin
|
|
637
|
+
@port.send(message, move: settings.arguments == :move)
|
|
638
|
+
rescue ::Ractor::ClosedError
|
|
639
|
+
raise StoppedError, "Wrapper has stopped"
|
|
640
|
+
end
|
|
515
641
|
loop do
|
|
516
642
|
reply_message = reply_port.receive
|
|
517
643
|
case reply_message
|
|
@@ -519,14 +645,14 @@ class Ractor
|
|
|
519
645
|
handle_yield(reply_message, transaction, settings, method_name, &)
|
|
520
646
|
when ReturnMessage
|
|
521
647
|
maybe_log("Received result", method_name: method_name, transaction: transaction)
|
|
522
|
-
reply_port.close
|
|
523
648
|
return reply_message.value
|
|
524
649
|
when ExceptionMessage
|
|
525
650
|
maybe_log("Received exception", method_name: method_name, transaction: transaction)
|
|
526
|
-
reply_port.close
|
|
527
651
|
raise reply_message.exception
|
|
528
652
|
end
|
|
529
653
|
end
|
|
654
|
+
ensure
|
|
655
|
+
reply_port.close
|
|
530
656
|
end
|
|
531
657
|
|
|
532
658
|
##
|
|
@@ -550,6 +676,10 @@ class Ractor
|
|
|
550
676
|
##
|
|
551
677
|
# Blocks until the wrapper has fully stopped.
|
|
552
678
|
#
|
|
679
|
+
# Unlike `Thread#join` and `Ractor#join`, if a Wrapper crashes, the
|
|
680
|
+
# exception generally does *not* get raised out of `Wrapper#join`. Instead,
|
|
681
|
+
# it just returns self in the same way as normal termination.
|
|
682
|
+
#
|
|
553
683
|
# @return [self]
|
|
554
684
|
#
|
|
555
685
|
def join
|
|
@@ -557,13 +687,16 @@ class Ractor
|
|
|
557
687
|
@ractor.join
|
|
558
688
|
else
|
|
559
689
|
reply_port = ::Ractor::Port.new
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
690
|
+
begin
|
|
691
|
+
@port.send(JoinMessage.new(reply_port))
|
|
692
|
+
reply_port.receive
|
|
693
|
+
rescue ::Ractor::ClosedError
|
|
694
|
+
# Assume the wrapper has stopped if the port is not sendable
|
|
695
|
+
ensure
|
|
696
|
+
reply_port.close
|
|
697
|
+
end
|
|
563
698
|
end
|
|
564
699
|
self
|
|
565
|
-
rescue ::Ractor::ClosedError
|
|
566
|
-
self
|
|
567
700
|
end
|
|
568
701
|
|
|
569
702
|
##
|
|
@@ -577,13 +710,17 @@ class Ractor
|
|
|
577
710
|
# case, any calls to this method will raise Ractor::Error.
|
|
578
711
|
#
|
|
579
712
|
# Only one ractor may call this method; any additional calls will fail with
|
|
580
|
-
# a Ractor::Error.
|
|
713
|
+
# a Ractor::Wrapper::Error.
|
|
581
714
|
#
|
|
582
715
|
# @return [Object] The original wrapped object
|
|
583
716
|
#
|
|
584
717
|
def recover_object
|
|
585
|
-
raise
|
|
586
|
-
|
|
718
|
+
raise Error, "cannot recover an object from a local wrapper" unless @ractor
|
|
719
|
+
begin
|
|
720
|
+
@ractor.value
|
|
721
|
+
rescue ::Ractor::Error => e
|
|
722
|
+
raise ::Ractor::Wrapper::Error, e.message, cause: e
|
|
723
|
+
end
|
|
587
724
|
end
|
|
588
725
|
|
|
589
726
|
#### private items below ####
|
|
@@ -592,7 +729,7 @@ class Ractor
|
|
|
592
729
|
# @private
|
|
593
730
|
# Message sent to initialize a server.
|
|
594
731
|
#
|
|
595
|
-
InitMessage = ::Data.define(:object, :
|
|
732
|
+
InitMessage = ::Data.define(:object, :stub)
|
|
596
733
|
|
|
597
734
|
##
|
|
598
735
|
# @private
|
|
@@ -619,6 +756,12 @@ class Ractor
|
|
|
619
756
|
#
|
|
620
757
|
JoinMessage = ::Data.define(:reply_port)
|
|
621
758
|
|
|
759
|
+
##
|
|
760
|
+
# @private
|
|
761
|
+
# Message sent from a server in response to a join request.
|
|
762
|
+
#
|
|
763
|
+
JoinReplyMessage = ::Data.define
|
|
764
|
+
|
|
622
765
|
##
|
|
623
766
|
# @private
|
|
624
767
|
# Message sent to report a return value
|
|
@@ -647,8 +790,12 @@ class Ractor
|
|
|
647
790
|
maybe_log("Starting local server")
|
|
648
791
|
@ractor = nil
|
|
649
792
|
@port = ::Ractor::Port.new
|
|
793
|
+
freeze
|
|
794
|
+
wrapper_id = object_id
|
|
650
795
|
::Thread.new do
|
|
796
|
+
::Thread.current.name = "ractor-wrapper:server:#{wrapper_id}"
|
|
651
797
|
Server.run_local(object: object,
|
|
798
|
+
stub: @stub,
|
|
652
799
|
port: @port,
|
|
653
800
|
name: name,
|
|
654
801
|
enable_logging: enable_logging?,
|
|
@@ -669,14 +816,16 @@ class Ractor
|
|
|
669
816
|
threads: threads)
|
|
670
817
|
end
|
|
671
818
|
@port = @ractor.default_port
|
|
672
|
-
|
|
819
|
+
freeze
|
|
820
|
+
init_message = InitMessage.new(object: object, stub: @stub)
|
|
821
|
+
@port.send(init_message, move: true)
|
|
673
822
|
end
|
|
674
823
|
|
|
675
824
|
##
|
|
676
825
|
# Create a transaction ID, used for logging
|
|
677
826
|
#
|
|
678
827
|
def make_transaction
|
|
679
|
-
::Random.rand(7_958_661_109_946_400_884_391_936).to_s(36).freeze
|
|
828
|
+
::Random.rand(7_958_661_109_946_400_884_391_936).to_s(36).rjust(16, "0").freeze
|
|
680
829
|
end
|
|
681
830
|
|
|
682
831
|
##
|
|
@@ -685,7 +834,7 @@ class Ractor
|
|
|
685
834
|
def make_block_arg(settings, &)
|
|
686
835
|
if !block_given?
|
|
687
836
|
nil
|
|
688
|
-
elsif settings.
|
|
837
|
+
elsif settings.block_environment == :wrapped
|
|
689
838
|
::Ractor.shareable_proc(&)
|
|
690
839
|
else
|
|
691
840
|
:send_block_message
|
|
@@ -699,8 +848,9 @@ class Ractor
|
|
|
699
848
|
maybe_log("Yielding to block", method_name: method_name, transaction: transaction)
|
|
700
849
|
begin
|
|
701
850
|
block_result = yield(*message.args, **message.kwargs)
|
|
851
|
+
block_result = nil if settings.block_results == :void
|
|
702
852
|
maybe_log("Sending block result", method_name: method_name, transaction: transaction)
|
|
703
|
-
message.reply_port.send(ReturnMessage.new(block_result), move: settings.
|
|
853
|
+
message.reply_port.send(ReturnMessage.new(block_result), move: settings.block_results == :move)
|
|
704
854
|
rescue ::Exception => e # rubocop:disable Lint/RescueException
|
|
705
855
|
maybe_log("Sending block exception", method_name: method_name, transaction: transaction)
|
|
706
856
|
begin
|
|
@@ -745,8 +895,8 @@ class Ractor
|
|
|
745
895
|
# @private
|
|
746
896
|
# Create and run a server hosted in the current Ractor
|
|
747
897
|
#
|
|
748
|
-
def self.run_local(object:, port:, name:, enable_logging: false, threads: 0)
|
|
749
|
-
server = new(isolated: false, object:, port:, name:, enable_logging:, threads:)
|
|
898
|
+
def self.run_local(object:, stub:, port:, name:, enable_logging: false, threads: 0)
|
|
899
|
+
server = new(isolated: false, object:, stub:, port:, name:, enable_logging:, threads:)
|
|
750
900
|
server.run
|
|
751
901
|
end
|
|
752
902
|
|
|
@@ -756,18 +906,19 @@ class Ractor
|
|
|
756
906
|
#
|
|
757
907
|
def self.run_isolated(name:, enable_logging: false, threads: 0)
|
|
758
908
|
port = ::Ractor.current.default_port
|
|
759
|
-
server = new(isolated: true, object: nil, port:, name:, enable_logging:, threads:)
|
|
909
|
+
server = new(isolated: true, object: nil, stub: nil, port:, name:, enable_logging:, threads:)
|
|
760
910
|
server.run
|
|
761
911
|
end
|
|
762
912
|
|
|
763
913
|
# @private
|
|
764
|
-
def initialize(isolated:, object:, port:, name:, enable_logging:, threads:)
|
|
914
|
+
def initialize(isolated:, object:, stub:, port:, name:, enable_logging:, threads:)
|
|
765
915
|
@isolated = isolated
|
|
766
916
|
@object = object
|
|
917
|
+
@stub = stub
|
|
767
918
|
@port = port
|
|
768
919
|
@name = name
|
|
769
920
|
@enable_logging = enable_logging
|
|
770
|
-
@
|
|
921
|
+
@threads_requested = threads.positive? ? threads : false
|
|
771
922
|
@join_requests = []
|
|
772
923
|
end
|
|
773
924
|
|
|
@@ -779,14 +930,16 @@ class Ractor
|
|
|
779
930
|
#
|
|
780
931
|
def run
|
|
781
932
|
receive_remote_object if @isolated
|
|
782
|
-
start_workers if @
|
|
933
|
+
start_workers if @threads_requested
|
|
783
934
|
main_loop
|
|
784
|
-
stop_workers if @
|
|
935
|
+
stop_workers if @threads_requested
|
|
785
936
|
cleanup
|
|
786
937
|
@object
|
|
787
|
-
rescue ::
|
|
788
|
-
|
|
938
|
+
rescue ::Exception => e # rubocop:disable Lint/RescueException
|
|
939
|
+
@crash_exception = e
|
|
789
940
|
@object
|
|
941
|
+
ensure
|
|
942
|
+
crash_cleanup if @crash_exception
|
|
790
943
|
end
|
|
791
944
|
|
|
792
945
|
private
|
|
@@ -796,8 +949,10 @@ class Ractor
|
|
|
796
949
|
# separate Ractor.
|
|
797
950
|
#
|
|
798
951
|
def receive_remote_object
|
|
799
|
-
maybe_log("Waiting for
|
|
800
|
-
|
|
952
|
+
maybe_log("Waiting for initialization")
|
|
953
|
+
init_message = @port.receive
|
|
954
|
+
@object = init_message.object
|
|
955
|
+
@stub = init_message.stub
|
|
801
956
|
end
|
|
802
957
|
|
|
803
958
|
##
|
|
@@ -805,10 +960,11 @@ class Ractor
|
|
|
805
960
|
# shared queue. Called only if worker threading is enabled.
|
|
806
961
|
#
|
|
807
962
|
def start_workers
|
|
963
|
+
maybe_log("Spawning #{@threads_requested} worker threads")
|
|
808
964
|
@queue = ::Queue.new
|
|
809
|
-
|
|
810
|
-
(1..@
|
|
811
|
-
::Thread.new { worker_thread(worker_num) }
|
|
965
|
+
@active_workers = {}
|
|
966
|
+
(1..@threads_requested).each do |worker_num|
|
|
967
|
+
@active_workers[worker_num] = ::Thread.new { worker_thread(worker_num) }
|
|
812
968
|
end
|
|
813
969
|
end
|
|
814
970
|
|
|
@@ -834,14 +990,14 @@ class Ractor
|
|
|
834
990
|
case message
|
|
835
991
|
when CallMessage
|
|
836
992
|
maybe_log("Received CallMessage", call_message: message)
|
|
837
|
-
if @
|
|
993
|
+
if @threads_requested
|
|
838
994
|
@queue.enq(message)
|
|
839
995
|
else
|
|
840
996
|
handle_method(message)
|
|
841
997
|
end
|
|
842
998
|
when WorkerStoppedMessage
|
|
843
999
|
maybe_log("Received unexpected WorkerStoppedMessage")
|
|
844
|
-
@
|
|
1000
|
+
@active_workers.delete(message.worker_num) if @threads_requested
|
|
845
1001
|
break
|
|
846
1002
|
when StopMessage
|
|
847
1003
|
maybe_log("Received stop")
|
|
@@ -876,7 +1032,7 @@ class Ractor
|
|
|
876
1032
|
#
|
|
877
1033
|
def stop_workers
|
|
878
1034
|
@queue.close
|
|
879
|
-
|
|
1035
|
+
until @active_workers.empty?
|
|
880
1036
|
maybe_log("Waiting for message in stopping phase")
|
|
881
1037
|
message = @port.receive
|
|
882
1038
|
case message
|
|
@@ -884,7 +1040,7 @@ class Ractor
|
|
|
884
1040
|
refuse_method(message)
|
|
885
1041
|
when WorkerStoppedMessage
|
|
886
1042
|
maybe_log("Acknowledged WorkerStoppedMessage: #{message.worker_num}")
|
|
887
|
-
@
|
|
1043
|
+
@active_workers.delete(message.worker_num)
|
|
888
1044
|
when StopMessage
|
|
889
1045
|
maybe_log("Stop received when already stopping")
|
|
890
1046
|
when JoinMessage
|
|
@@ -901,8 +1057,6 @@ class Ractor
|
|
|
901
1057
|
def cleanup
|
|
902
1058
|
maybe_log("Closing inbox")
|
|
903
1059
|
@port.close
|
|
904
|
-
maybe_log("Responding to join requests")
|
|
905
|
-
@join_requests.each { |port| send_join_reply(port) }
|
|
906
1060
|
maybe_log("Draining inbox")
|
|
907
1061
|
loop do
|
|
908
1062
|
message = begin
|
|
@@ -924,6 +1078,94 @@ class Ractor
|
|
|
924
1078
|
send_join_reply(message.reply_port)
|
|
925
1079
|
end
|
|
926
1080
|
end
|
|
1081
|
+
maybe_log("Responding to join requests")
|
|
1082
|
+
@join_requests.each { |port| send_join_reply(port) }
|
|
1083
|
+
end
|
|
1084
|
+
|
|
1085
|
+
##
|
|
1086
|
+
# Called from the ensure block in run when an unexpected exception
|
|
1087
|
+
# terminated the server. Drains pending requests that are not otherwise
|
|
1088
|
+
# being handled, responding to all pending callers and join requesters,
|
|
1089
|
+
# and also joins any worker threads.
|
|
1090
|
+
#
|
|
1091
|
+
def crash_cleanup
|
|
1092
|
+
maybe_log("Running crash cleanup after: #{@crash_exception.message} (#{@crash_exception.class})")
|
|
1093
|
+
error = CrashedError.new("Server crashed: #{@crash_exception.message} (#{@crash_exception.class})")
|
|
1094
|
+
# `@queue` should not be nil in threaded mode, but we're checking
|
|
1095
|
+
# anyway just in case a crash happened during setup
|
|
1096
|
+
drain_queue_after_crash(@queue, error) if @threads_requested && @queue
|
|
1097
|
+
drain_inbox_after_crash(@port, error)
|
|
1098
|
+
# `@active_workers` should not be nil in threaded mode, but we're
|
|
1099
|
+
# checking anyway just in case a crash happened during setup
|
|
1100
|
+
join_workers_after_crash(@active_workers) if @threads_requested && @active_workers
|
|
1101
|
+
@join_requests.each { |port| send_join_reply(port) }
|
|
1102
|
+
rescue ::Exception => e # rubocop:disable Lint/RescueException
|
|
1103
|
+
maybe_log("Suppressed exception during crash_cleanup: #{e.message} (#{e.class})")
|
|
1104
|
+
end
|
|
1105
|
+
|
|
1106
|
+
##
|
|
1107
|
+
# Drains any remaining queued call messages after a crash, sending errors
|
|
1108
|
+
# to callers whose calls had not yet been dispatched to a worker thread.
|
|
1109
|
+
#
|
|
1110
|
+
def drain_queue_after_crash(queue, error)
|
|
1111
|
+
queue.close
|
|
1112
|
+
loop do
|
|
1113
|
+
message = queue.deq
|
|
1114
|
+
break if message.nil?
|
|
1115
|
+
begin
|
|
1116
|
+
message.reply_port.send(ExceptionMessage.new(error))
|
|
1117
|
+
rescue ::Ractor::Error
|
|
1118
|
+
maybe_log("Failed to send crash error to queued caller", call_message: message)
|
|
1119
|
+
end
|
|
1120
|
+
end
|
|
1121
|
+
rescue ::Exception => e # rubocop:disable Lint/RescueException
|
|
1122
|
+
maybe_log("Suppressed exception during drain_queue_after_crash: " \
|
|
1123
|
+
"#{e.message} (#{e.class})")
|
|
1124
|
+
end
|
|
1125
|
+
|
|
1126
|
+
##
|
|
1127
|
+
# Drains any remaining inbox messages after a crash, sending errors to
|
|
1128
|
+
# pending callers and responding to any join requests.
|
|
1129
|
+
#
|
|
1130
|
+
def drain_inbox_after_crash(port, error)
|
|
1131
|
+
begin
|
|
1132
|
+
port.close
|
|
1133
|
+
rescue ::Ractor::Error
|
|
1134
|
+
# Port was already closed (maybe because it was the cause of the crash)
|
|
1135
|
+
end
|
|
1136
|
+
loop do
|
|
1137
|
+
message = begin
|
|
1138
|
+
port.receive
|
|
1139
|
+
rescue ::Ractor::Error
|
|
1140
|
+
nil
|
|
1141
|
+
end
|
|
1142
|
+
break if message.nil?
|
|
1143
|
+
case message
|
|
1144
|
+
when CallMessage
|
|
1145
|
+
begin
|
|
1146
|
+
message.reply_port.send(ExceptionMessage.new(error))
|
|
1147
|
+
rescue ::Ractor::Error
|
|
1148
|
+
maybe_log("Failed to send crash error to caller", call_message: message)
|
|
1149
|
+
end
|
|
1150
|
+
when JoinMessage
|
|
1151
|
+
send_join_reply(message.reply_port)
|
|
1152
|
+
when WorkerStoppedMessage, StopMessage
|
|
1153
|
+
# Ignore
|
|
1154
|
+
end
|
|
1155
|
+
end
|
|
1156
|
+
rescue ::Exception => e # rubocop:disable Lint/RescueException
|
|
1157
|
+
maybe_log("Suppressed exception during drain_inbox_after_crash: #{e.message} (#{e.class})")
|
|
1158
|
+
end
|
|
1159
|
+
|
|
1160
|
+
##
|
|
1161
|
+
# Wait until all workers have stopped after a crash
|
|
1162
|
+
#
|
|
1163
|
+
def join_workers_after_crash(workers)
|
|
1164
|
+
workers.each_value do |thread|
|
|
1165
|
+
thread.join
|
|
1166
|
+
rescue ::Exception => e # rubocop:disable Lint/RescueException
|
|
1167
|
+
maybe_log("Suppressed exception during join_workers_after_crash: #{e.message} (#{e.class})")
|
|
1168
|
+
end
|
|
927
1169
|
end
|
|
928
1170
|
|
|
929
1171
|
##
|
|
@@ -947,7 +1189,7 @@ class Ractor
|
|
|
947
1189
|
begin
|
|
948
1190
|
@port.send(WorkerStoppedMessage.new(worker_num))
|
|
949
1191
|
rescue ::Ractor::ClosedError
|
|
950
|
-
maybe_log("
|
|
1192
|
+
maybe_log("Worker unable to report stop, possibly due to server crash", worker_num: worker_num)
|
|
951
1193
|
end
|
|
952
1194
|
end
|
|
953
1195
|
|
|
@@ -963,20 +1205,20 @@ class Ractor
|
|
|
963
1205
|
def handle_method(message, worker_num: nil)
|
|
964
1206
|
block = make_block(message)
|
|
965
1207
|
maybe_log("Running method", worker_num: worker_num, call_message: message)
|
|
1208
|
+
result = @object.__send__(message.method_name, *message.args, **message.kwargs, &block)
|
|
1209
|
+
result = @stub if result.equal?(@object)
|
|
1210
|
+
result = nil if message.settings.results == :void
|
|
1211
|
+
maybe_log("Sending return value", worker_num: worker_num, call_message: message)
|
|
1212
|
+
message.reply_port.send(ReturnMessage.new(result), move: message.settings.results == :move)
|
|
1213
|
+
rescue ::Exception => e # rubocop:disable Lint/RescueException
|
|
1214
|
+
maybe_log("Sending exception", worker_num: worker_num, call_message: message)
|
|
966
1215
|
begin
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
message.reply_port.send(ReturnMessage.new(result), move: message.settings.move_results?)
|
|
970
|
-
rescue ::Exception => e # rubocop:disable Lint/RescueException
|
|
971
|
-
maybe_log("Sending exception", worker_num: worker_num, call_message: message)
|
|
1216
|
+
message.reply_port.send(ExceptionMessage.new(e))
|
|
1217
|
+
rescue ::Exception # rubocop:disable Lint/RescueException
|
|
972
1218
|
begin
|
|
973
|
-
message.reply_port.send(ExceptionMessage.new(e))
|
|
974
|
-
rescue ::
|
|
975
|
-
|
|
976
|
-
message.reply_port.send(ExceptionMessage.new(::StandardError.new(e.inspect)))
|
|
977
|
-
rescue ::StandardError
|
|
978
|
-
maybe_log("Failure to send method response", worker_num: worker_num, call_message: message)
|
|
979
|
-
end
|
|
1219
|
+
message.reply_port.send(ExceptionMessage.new(::RuntimeError.new(e.inspect)))
|
|
1220
|
+
rescue ::Exception # rubocop:disable Lint/RescueException
|
|
1221
|
+
maybe_log("Failure to send method response", worker_num: worker_num, call_message: message)
|
|
980
1222
|
end
|
|
981
1223
|
end
|
|
982
1224
|
end
|
|
@@ -995,10 +1237,15 @@ class Ractor
|
|
|
995
1237
|
return message.block_arg unless message.block_arg == :send_block_message
|
|
996
1238
|
proc do |*args, **kwargs|
|
|
997
1239
|
reply_port = ::Ractor::Port.new
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1240
|
+
reply_message = begin
|
|
1241
|
+
args.map! { |arg| arg.equal?(@object) ? @stub : arg }
|
|
1242
|
+
kwargs.transform_values! { |arg| arg.equal?(@object) ? @stub : arg }
|
|
1243
|
+
yield_message = YieldMessage.new(args: args, kwargs: kwargs, reply_port: reply_port)
|
|
1244
|
+
message.reply_port.send(yield_message, move: message.settings.block_arguments == :move)
|
|
1245
|
+
reply_port.receive
|
|
1246
|
+
ensure
|
|
1247
|
+
reply_port.close
|
|
1248
|
+
end
|
|
1002
1249
|
case reply_message
|
|
1003
1250
|
when ExceptionMessage
|
|
1004
1251
|
raise reply_message.exception
|
|
@@ -1016,7 +1263,7 @@ class Ractor
|
|
|
1016
1263
|
def refuse_method(message)
|
|
1017
1264
|
maybe_log("Refusing method call", call_message: message)
|
|
1018
1265
|
begin
|
|
1019
|
-
error =
|
|
1266
|
+
error = StoppedError.new("Wrapper is shutting down")
|
|
1020
1267
|
message.reply_port.send(ExceptionMessage.new(error))
|
|
1021
1268
|
rescue ::Ractor::Error
|
|
1022
1269
|
maybe_log("Failed to send refusal message", call_message: message)
|
|
@@ -1027,7 +1274,7 @@ class Ractor
|
|
|
1027
1274
|
# This attempts to send a signal that a wrapper join has completed.
|
|
1028
1275
|
#
|
|
1029
1276
|
def send_join_reply(port)
|
|
1030
|
-
port.send(
|
|
1277
|
+
port.send(JoinReplyMessage.new.freeze)
|
|
1031
1278
|
rescue ::Ractor::ClosedError
|
|
1032
1279
|
maybe_log("Join reply port is closed")
|
|
1033
1280
|
end
|
|
@@ -1039,13 +1286,15 @@ class Ractor
|
|
|
1039
1286
|
return unless @enable_logging
|
|
1040
1287
|
transaction ||= call_message&.transaction
|
|
1041
1288
|
method_name ||= call_message&.method_name
|
|
1042
|
-
metadata = [::Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%L"), "Ractor::Wrapper
|
|
1043
|
-
metadata << "Worker
|
|
1044
|
-
metadata << "Transaction
|
|
1045
|
-
metadata << "Method
|
|
1289
|
+
metadata = [::Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%L"), "Ractor::Wrapper:#{@name}"]
|
|
1290
|
+
metadata << "Worker:#{worker_num}" if worker_num
|
|
1291
|
+
metadata << "Transaction:#{transaction}" if transaction
|
|
1292
|
+
metadata << "Method:#{method_name}" if method_name
|
|
1046
1293
|
metadata = metadata.join(" ")
|
|
1047
1294
|
$stderr.puts("[#{metadata}] #{str}")
|
|
1048
1295
|
$stderr.flush
|
|
1296
|
+
rescue ::StandardError
|
|
1297
|
+
# Swallow any errors during logging
|
|
1049
1298
|
end
|
|
1050
1299
|
end
|
|
1051
1300
|
end
|