ractorize 0.0.1 → 0.0.2

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: 259d8a0f04fdab353871748575089e83199cfad7d81440d4e6f3a957366c9301
4
+ data.tar.gz: e05e67142fdd2c081ec6baf244df8f7dadf991437c12e7d27cc87c793c863956
5
5
  SHA512:
6
- metadata.gz: 7a41390e8c9f98222862a5ff2a486d78800d01cb96ccf8833ecfed9348121b5300c95b33dae9e0052ef2a875c49e6f1ad84dc2b8017a9e3925816b854a56fe23
7
- data.tar.gz: b1e621b39d666b81006e47b5d9cea508ab773e18810a94c1a637d77c70eb84a9081e9e67a54ddb580a7874687202106c17fc6e7b8c60115600ae700864d18b6b
6
+ metadata.gz: ebd8a74f6bd6040740c5d41adb81f4caba0508ec28f1025eedb8a2feef826f5f09d764535593d464d3355bebe64941138f5789c9792223df8fe095af7384d985
7
+ data.tar.gz: 30b2f4d65a70d7218b5e3d76b1f6c04f3cb0c784fe8cd639d53770705caa61163c5c84ba678cd725f8f19296c1c6d985c6310ff461dbade04015522f96a8b155
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## [0.0.2] - 2026-05-18
2
+
3
+ - Make sure ractorized objects are shareable. This requires them to be unusable after closing (or joining) them.
4
+ - Make sure methods don't collide with core methods: #join/#close -> #__join__/#__close__
5
+
1
6
  ## [0.0.1] - 2026-04-14
2
7
 
3
8
  - 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,40 @@ 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
11
  @ractor.<<(outside_object, move: true)
17
- end
18
12
 
19
- def close
20
- result = method_missing(:close)
21
-
22
- @__object__ = if Thunk === result
23
- result.__value__
24
- else
25
- result
26
- end
13
+ # Wow, this works! Scary?
14
+ ::Object.instance_method(:freeze).bind(self).call
27
15
  end
28
16
 
29
- def join
30
- close
17
+ def __close__ = method_missing(:__close__)
18
+
19
+ def __join__
20
+ __close__
31
21
  @ractor.join
32
22
  self
33
23
  end
34
24
 
35
25
  def method_missing(method_name, *args, **opts)
36
- return @__object__ if method_name == :close && @ractor.default_port.closed?
26
+ if @ractor.default_port.closed?
27
+ ::Kernel.raise ::Ractor::ClosedError,
28
+ "You already closed this Ractorized object! No more methods can be sent to it."
29
+ end
37
30
 
38
- if defined?(@__object__)
39
- @__object__.__send__(method_name, *args, **opts)
40
- else
41
- return_port = ::Ractor::Port.new
31
+ return_port = ::Ractor::Port.new
42
32
 
43
- @ractor << [method_name, args, opts, return_port]
33
+ @ractor << [method_name, args, opts, return_port]
44
34
 
35
+ # Let's assume the user would rather block on all predicate methods than
36
+ # incorrectly get a non-truthy value (thunk is always truthy even if it evaluates as nil/false)
37
+ if method_name.end_with?("?")
38
+ return_port.receive
39
+ else
45
40
  Thunk.new(return_port)
46
41
  end
47
42
  end
@@ -56,13 +51,7 @@ module Ractorize
56
51
  end
57
52
 
58
53
  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
54
+ method_missing(:respond_to?, method_name, include_all)
66
55
  end
67
56
  end
68
57
  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,7 +12,7 @@ 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
@@ -20,6 +22,8 @@ module Ractorize
20
22
  return_port << value
21
23
  end
22
24
  end
25
+
26
+ object
23
27
  end
24
28
 
25
29
  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.2
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: []