ractorize 0.0.1 → 0.0.3

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: 5ea9fd31cee6cae0ef89ff10256f4b3697e472e0311bb792c77f93f750be4f3a
4
- data.tar.gz: 7468ea66e37593b5ff8c5ac26087cdd0de1134bef7d1d9bb5c468371c5e1f537
3
+ metadata.gz: f4125b52255fca881ea212e46e55e4c795e79b39f8d8369580022e67db2eb9a8
4
+ data.tar.gz: 91f58df84879ac297d0111f5880faf8ea95804fdb4ae17345f7702e579fbfb42
5
5
  SHA512:
6
- metadata.gz: 7a41390e8c9f98222862a5ff2a486d78800d01cb96ccf8833ecfed9348121b5300c95b33dae9e0052ef2a875c49e6f1ad84dc2b8017a9e3925816b854a56fe23
7
- data.tar.gz: b1e621b39d666b81006e47b5d9cea508ab773e18810a94c1a637d77c70eb84a9081e9e67a54ddb580a7874687202106c17fc6e7b8c60115600ae700864d18b6b
6
+ metadata.gz: 691e4eb2b5a1d436b366e93256e7af917308ea3ca3292205791023f92948202464279d4e11e3251c9d842ac63e79a1d5e1a3c377747e07ced9956722a67e2d68
7
+ data.tar.gz: ef0e4f07687797d2036f326674150ba9ff1e5e07a604936109f4ff0e0811bb19de85d22a5834c8684ed49860cc4f27943d3a292960378c46a2c661f561e91b05
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## [0.0.3] - 2026-05-21
2
+
3
+ - Treat ==, !=, and ! as predicates and delegate them to the ractor (as well as #equal?)
4
+ - Don't wrap thunks in more thunks. Unwrap them in RACTOR_PROC which can help with Port#receive issues
5
+ - Send a frozen array to the ractor to avoid dup/clone
6
+ - Something somewhere in other projects calls Thunk#initialize_clone, so make a placeholder for it
7
+ - Do not bother moving shareable objects to the ractor, just send them
8
+
9
+ ## [0.0.2] - 2026-05-18
10
+
11
+ - Make sure ractorized objects are shareable. This requires them to be unusable after closing (or joining) them.
12
+ - Make sure methods don't collide with core methods: #join/#close -> #__join__/#__close__
13
+
1
14
  ## [0.0.1] - 2026-04-14
2
15
 
3
16
  - Initial release
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Ractorize
2
2
 
3
- Have an object you wish was a ractor but isn't? Well, this gem lets you ractorize it!
3
+ Have an object you wish were a ractor but isn't? Well, this gem lets you ractorize it!
4
4
 
5
5
  When you ractorize an object, you can just call the normal methods on the object as if it weren't a ractor.
6
6
  These method calls will automatically be sent as messages to a different ractor where that
@@ -50,12 +50,12 @@ also ractorize individual objects with `Ractorize[some_object]`.
50
50
  Notice how, whether it's ractorized or not, we can just use the same exact interface? Fun!
51
51
 
52
52
  You can find a script that benchmarks these the ractorized versus non-ractorized
53
- approach in `example_scripts/product-benchmark2`.
53
+ approach in `example_scripts/product-benchmark`.
54
54
 
55
- Here's an example run of the product-benchmark2 script:
55
+ Here's an example run of the product-benchmark script:
56
56
 
57
57
  ```
58
- $ example_scripts/product-benchmark2
58
+ $ example_scripts/product-benchmark
59
59
  benchmarking non-ractorized productizer
60
60
  product is 0.568147e51
61
61
  took 2.303 seconds
@@ -68,6 +68,54 @@ took 0.195 seconds
68
68
  $
69
69
  ```
70
70
 
71
+ ## Gotchas
72
+
73
+ ### Predicate methods not ending in "?" will always return truthy values!
74
+ If you try to use the return value of a ractorized object (or any instance of a ractorized class)
75
+ in a boolean expression, it will always be truthy!!
76
+
77
+ You need to instead call `#__value__` on it to force it into the real value. This will make it block, but
78
+ that's what you want anyways in such a situation.
79
+
80
+ Example:
81
+
82
+ ```ruby
83
+ class String
84
+ def is_empty = empty?
85
+ end
86
+
87
+ if Ractorize["asdf"].is_empty
88
+ puts "It's empty!"
89
+ else
90
+ puts "It's not empty!"
91
+ end
92
+ ```
93
+
94
+ This will incorrectly print out `It's empty!`! To make it work you can force it to block and wait
95
+ for the actual value and use the actual value with the `#__value__` method:
96
+
97
+ ```ruby
98
+ class String
99
+ def is_empty = empty?
100
+ end
101
+
102
+ if Ractorize["asdf"].is_empty.__value__
103
+ puts "It's empty!"
104
+ else
105
+ puts "It's not empty!"
106
+ end
107
+ ```
108
+
109
+ This will correctly print out `It's not empty!`.
110
+
111
+ ### Calling a method on a closed ractorized object might result in a deadlock!
112
+
113
+ It will usually raise a `Ractor::CloseError` but once in a while it can deadlock.
114
+
115
+ Note that if you call either `#__close__` or `#__join__` on the object, then the underlying ractor will be closed.
116
+
117
+ An easy way to avoid the deadlock is just don't make any use of such an object after closing it.
118
+
71
119
  ## Fine print
72
120
 
73
121
  Ractors are still experimental and so this gem is also still experimental.
@@ -3,45 +3,44 @@ require_relative "thunk"
3
3
 
4
4
  module Ractorize
5
5
  class RactorizedObject < BasicObject
6
- # Putting this in a constant so we can get test coverage on it since not sure how to get coverage
7
- # on something inside a ractor.
8
-
9
- attr_accessor :__object__
10
-
11
6
  def initialize(outside_object)
12
7
  @ractor = ::Ractor.new(&RACTOR_PROC)
13
8
 
14
9
  # It doesn't seem like we have a way to move the object into the ractor via its constructor so do
15
10
  # it with #<< instead.
16
- @ractor.<<(outside_object, move: true)
17
- end
18
-
19
- def close
20
- result = method_missing(:close)
11
+ if ::Ractor.shareable?(outside_object)
12
+ @ractor << outside_object
13
+ else
14
+ @ractor.send(outside_object, move: true)
15
+ end
21
16
 
22
- @__object__ = if Thunk === result
23
- result.__value__
24
- else
25
- result
26
- end
17
+ # Wow, this works! Scary?
18
+ ::Object.instance_method(:freeze).bind(self).call
27
19
  end
28
20
 
29
- def join
30
- close
21
+ def __close__ = method_missing(:__close__)
22
+
23
+ def __join__
24
+ __close__
31
25
  @ractor.join
32
26
  self
33
27
  end
34
28
 
35
29
  def method_missing(method_name, *args, **opts)
36
- return @__object__ if method_name == :close && @ractor.default_port.closed?
30
+ if @ractor.default_port.closed?
31
+ ::Kernel.raise ::Ractor::ClosedError,
32
+ "You already closed this Ractorized object! No more methods can be sent to it."
33
+ end
37
34
 
38
- if defined?(@__object__)
39
- @__object__.__send__(method_name, *args, **opts)
40
- else
41
- return_port = ::Ractor::Port.new
35
+ return_port = ::Ractor::Port.new
42
36
 
43
- @ractor << [method_name, args, opts, return_port]
37
+ @ractor << [method_name, args.dup.freeze, opts.dup.freeze, return_port].freeze
44
38
 
39
+ # Let's assume the user would rather block on all predicate methods than
40
+ # 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
43
+ else
45
44
  Thunk.new(return_port)
46
45
  end
47
46
  end
@@ -56,13 +55,12 @@ module Ractorize
56
55
  end
57
56
 
58
57
  def respond_to_missing?(method_name, include_all = false)
59
- value = method_missing(:respond_to?, method_name, include_all)
60
-
61
- if ::Ractorize::Thunk === value
62
- value.__value__
63
- else
64
- value
65
- end
58
+ method_missing(:respond_to?, method_name, include_all)
66
59
  end
60
+
61
+ def ==(other) = method_missing(:==, other)
62
+ def !=(other) = method_missing(:==, other)
63
+ def ! = method_missing(:!)
64
+ def equal?(other) = method_missing(:equal?, other)
67
65
  end
68
66
  end
@@ -6,6 +6,10 @@ module Ractorize
6
6
  self.__return_value_port__ = return_value_port
7
7
  end
8
8
 
9
+ def initialize_clone(...)
10
+ # is this actually necessary?? Seems so?
11
+ end
12
+
9
13
  def method_missing(method_name, *)
10
14
  __value__.send(method_name, *)
11
15
  end
data/src/ractorize.rb CHANGED
@@ -3,6 +3,8 @@ require_relative "ractorize/ractorized_object"
3
3
  require_relative "ractorize/ractorized_class"
4
4
 
5
5
  module Ractorize
6
+ # Putting this in a constant so we can get test coverage on it since not sure how to get coverage
7
+ # on something inside a ractor.
6
8
  RACTOR_PROC = proc do
7
9
  object = receive
8
10
 
@@ -10,16 +12,20 @@ module Ractorize
10
12
  method_name, method_args, opts, return_port = receive
11
13
 
12
14
  case method_name
13
- when :close
15
+ when :__close__
14
16
  return_port.<<(object, move: true)
15
17
  close
16
18
  break
17
19
  else
18
20
  value = object.__send__(method_name, *method_args, **opts)
19
21
 
22
+ value = value.__value__ while Thunk === value
23
+
20
24
  return_port << value
21
25
  end
22
26
  end
27
+
28
+ object
23
29
  end
24
30
 
25
31
  class << self
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.1
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miles Georgi
@@ -46,7 +46,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  requirements: []
49
- rubygems_version: 4.0.6
49
+ rubygems_version: 4.0.10
50
50
  specification_version: 4
51
51
  summary: Turn objects into ractors with ease!
52
52
  test_files: []