Skip to content

Commit 99a55e8

Browse files
committed
Make what is automatically moved configurable and add an auto-freeze feature
1 parent 0ec93d5 commit 99a55e8

6 files changed

Lines changed: 365 additions & 15 deletions

File tree

.rubocop.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,6 @@ Style/DoubleNegation:
2525

2626
Naming/MethodParameterName:
2727
Enabled: false
28+
29+
RSpec/DescribedClass:
30+
Enabled: false

spec/auto_freeze_spec.rb

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
RSpec.describe Ractorize do
2+
describe ".auto_freeze" do
3+
around do |example|
4+
old_to_move = described_class.instance_variable_get(:@auto_freeze).dup
5+
6+
example.run
7+
ensure
8+
described_class.instance_variable_set(:@auto_freeze, old_to_move)
9+
end
10+
11+
let(:foo_klass) do
12+
stub_class("Foo") do
13+
def foo(bar) = bar
14+
end
15+
end
16+
let(:ractorized_foo) { Ractorize[foo_klass.new] }
17+
let(:bar_klass) do
18+
stub_class("Bar") do
19+
def bar(baz) = baz
20+
end
21+
end
22+
let(:ractorized_bar) { Ractorize[bar_klass.new] }
23+
24+
context "when auto-freezing all strings" do
25+
before do
26+
Ractorize.auto_freeze(String)
27+
end
28+
29+
it "automatically freezes strings that are passed to a ractorized object to prevent duplication" do
30+
s = "asdf"
31+
32+
expect {
33+
expect(ractorized_foo.foo(s)).to eq(s)
34+
}.to change(s, :frozen?).from(false).to(true)
35+
end
36+
37+
context "when the thing to freeze is super deep in the structure" do
38+
it "still freezes it" do
39+
s = "asdf"
40+
structure = {
41+
foo: [1, 2, s]
42+
}
43+
44+
expect {
45+
expect(ractorized_foo.foo(structure)).to eq(structure)
46+
}.to change(s, :frozen?).from(false).to(true)
47+
end
48+
49+
context "when freezing something makes something shareable" do
50+
it "works just fine without needing to consider the rest of the structure" do
51+
s = "asdf"
52+
structure = {
53+
foo: [1, 2, s].freeze
54+
}.freeze
55+
56+
expect {
57+
expect(ractorized_foo.foo(structure)).to eq(structure)
58+
}.to change { Ractor.shareable?(structure) }.from(false).to(true)
59+
end
60+
end
61+
end
62+
end
63+
64+
context "when auto-freezing a class for different ractorized object classes" do
65+
before do
66+
Ractorize.auto_freeze(foo_klass, String)
67+
end
68+
69+
it "freezes objects only when the target class matches the rule" do
70+
s = "asdf"
71+
72+
expect {
73+
expect(ractorized_bar.bar(s)).to eq(s)
74+
}.to_not change(s, :frozen?)
75+
76+
expect {
77+
expect(ractorized_foo.foo(s)).to eq(s)
78+
}.to change(s, :frozen?).from(false).to(true)
79+
end
80+
end
81+
82+
context "when it's already frozen" do
83+
it "works just fine" do
84+
s = "asdf"
85+
expect(ractorized_foo.foo(s)).to eq(s)
86+
end
87+
end
88+
89+
context "when using procs to determine if something should be frozen" do
90+
before do
91+
Ractorize.auto_freeze(Ractor.shareable_proc { it =~ /quux/ })
92+
Ractorize.auto_freeze(Ractor.shareable_proc { it =~ /baz/ })
93+
end
94+
95+
it "only freezes when it matches the rule proc" do
96+
s = "foo"
97+
98+
expect {
99+
expect(ractorized_foo.foo(s)).to eq(s)
100+
}.to_not change(s, :frozen?)
101+
102+
s = "bar"
103+
104+
expect {
105+
expect(ractorized_foo.foo(s)).to eq(s)
106+
}.to_not change(s, :frozen?)
107+
108+
s = "baz"
109+
110+
expect {
111+
expect(ractorized_foo.foo(s)).to eq(s)
112+
}.to change(s, :frozen?).from(false).to(true)
113+
end
114+
end
115+
end
116+
end

spec/move_arg_spec.rb

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
RSpec.describe Ractorize do
2+
describe ".move_arg" do
3+
around do |example|
4+
old_to_move = described_class.instance_variable_get(:@move_arg).dup
5+
6+
example.run
7+
ensure
8+
described_class.instance_variable_set(:@move_arg, old_to_move)
9+
end
10+
11+
let(:foo_klass) do
12+
stub_class("Foo") do
13+
def foo(bar) = bar
14+
end
15+
end
16+
let(:ractorized_foo) { Ractorize[foo_klass.new] }
17+
let(:bar_klass) do
18+
stub_class("Bar") do
19+
def bar(baz) = baz
20+
end
21+
end
22+
let(:ractorized_bar) { Ractorize[bar_klass.new] }
23+
24+
context "when moving all strings" do
25+
before do
26+
Ractorize.move_arg(String)
27+
end
28+
29+
it "automatically moves strings that are passed to a ractorized object to prevent duplication" do
30+
s = "asdf"
31+
32+
expect {
33+
expect(ractorized_foo.foo(s)).to eq("asdf")
34+
}.to change { Ractor::MovedObject === s }.from(false).to(true)
35+
end
36+
end
37+
38+
context "when using procs to determine if something should be moved" do
39+
before do
40+
Ractorize.move_arg(Ractor.shareable_proc { it =~ /baz/ })
41+
end
42+
43+
it "only moves when it matches the rule proc" do
44+
s = "foo"
45+
46+
expect {
47+
expect(ractorized_foo.foo(s)).to eq("foo")
48+
}.to_not change { Ractor::MovedObject === s }
49+
50+
s = "bar"
51+
52+
expect {
53+
expect(ractorized_foo.foo(s)).to eq("bar")
54+
}.to_not change { Ractor::MovedObject === s }
55+
56+
s = "baz"
57+
58+
expect {
59+
expect(ractorized_foo.foo(s)).to eq("baz")
60+
}.to change { Ractor::MovedObject === s }.from(false).to(true)
61+
end
62+
end
63+
64+
context "when auto-freezing a class for different ractorized object classes" do
65+
before do
66+
Ractorize.move_arg(foo_klass, String)
67+
end
68+
69+
it "freezes objects only when the target class matches the rule" do
70+
s = "asdf"
71+
72+
expect {
73+
expect(ractorized_bar.bar(s)).to eq(s)
74+
}.to_not change { Ractor::MovedObject === s }
75+
76+
expect {
77+
expect(ractorized_foo.foo(s)).to eq("asdf")
78+
}.to change { Ractor::MovedObject === s }.from(false).to(true)
79+
end
80+
end
81+
end
82+
end

spec/ractorize_spec.rb

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
11
RSpec.describe Ractorize do
2+
def self.move_string_args
3+
before do
4+
old_to_move = described_class.instance_variable_get(:@to_move).dup
5+
6+
begin
7+
described_class.move_arg(String)
8+
ensure
9+
described_class.instance_variable_set(:@to_move, old_to_move)
10+
end
11+
end
12+
end
13+
214
let(:doubler_class) do
315
stub_class("Doubler") do
416
class << self
@@ -48,6 +60,8 @@ def double = @i *= 2
4860
end
4961

5062
context "when passing non-shareable keyword args" do
63+
move_string_args
64+
5165
let(:klass) do
5266
stub_class("Foo") do
5367
def foo(bar:) = bar
@@ -83,7 +97,9 @@ def foo(bar:) = bar
8397
end
8498
end
8599

86-
context "when creating an instance with non-shareable args" do
100+
context "when creating an instance with to-move args" do
101+
move_string_args
102+
87103
let(:args) { ["foo"] }
88104
let(:klass) do
89105
stub_class("Foo") do

0 commit comments

Comments
 (0)