Skip to content

Commit ea534de

Browse files
committed
♻️ Make Config.global ractor safe
Config.global now delegates to a frozen config, and updates are handled by swapping that frozen config for a new one.
1 parent 7903d66 commit ea534de

File tree

6 files changed

+50
-17
lines changed

6 files changed

+50
-17
lines changed

lib/net/imap/config.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
require_relative "config/attr_inheritance"
55
require_relative "config/attr_type_coercion"
66
require_relative "config/attr_version_defaults"
7+
require_relative "config/global"
78

89
module Net
910
class IMAP
@@ -632,7 +633,7 @@ def defaults_hash
632633
end
633634

634635
@default = AttrVersionDefaults.compile_default!
635-
@global = default.new
636+
@global = Global.setup!
636637
AttrVersionDefaults.compile_version_defaults!
637638

638639
end

lib/net/imap/config/attr_accessors.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ def self.attr_accessor(name) # :nodoc: internal API
3333
def self.attributes
3434
instance_methods.grep(/=\z/).map { _1.to_s.delete_suffix("=").to_sym }
3535
end
36-
private_class_method :attributes
3736

3837
def self.struct # :nodoc: internal API
3938
unless defined?(self::Struct)

lib/net/imap/config/attr_type_coercion.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def self.safe(&b)
4545
else
4646
def self.safe(&b) nil.instance_eval(&b).freeze end
4747
end
48-
private_class_method :safe
48+
# private_class_method :safe
4949

5050
Types = Hash.new do |h, type| type => Proc | nil; safe{type} end
5151
Types[:boolean] = Boolean = safe{-> {!!_1}}

lib/net/imap/config/attr_version_defaults.rb

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,15 @@ module AttrVersionDefaults
2626
# See Config.version_defaults.
2727
singleton_class.attr_reader :version_defaults
2828

29-
@version_defaults = Hash.new {|h, k|
29+
@version_defaults = {}
30+
version_defaults.default_proc = AttrTypeCoercion.safe {->(h, k){
3031
# NOTE: String responds to both so the order is significant.
3132
# And ignore non-numeric conversion to zero, because: "wat!?".to_r == 0
3233
(h.fetch(k.to_r, nil) || h.fetch(k.to_f, nil) if k.is_a?(Numeric)) ||
33-
(h.fetch(k.to_sym, nil) if k.respond_to?(:to_sym)) ||
34-
(h.fetch(k.to_r, nil) if k.respond_to?(:to_r) && k.to_r != 0r) ||
35-
(h.fetch(k.to_f, nil) if k.respond_to?(:to_f) && k.to_f != 0.0)
36-
}
34+
(h.fetch(k.to_sym, nil) if k.respond_to?(:to_sym)) ||
35+
(h.fetch(k.to_r, nil) if k.respond_to?(:to_r) && k.to_r != 0r) ||
36+
(h.fetch(k.to_f, nil) if k.respond_to?(:to_f) && k.to_f != 0.0)
37+
}}
3738

3839
# :stopdoc: internal APIs only
3940

lib/net/imap/config/global.rb

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,55 @@
1-
include "singleton"
1+
require "singleton"
22

33
module Net
44
class IMAP
55
class Config
66
class Global < Config
77
include Singleton
8+
extend Forwardable
9+
singleton_class.extend(Forwardable)
810

9-
def initialize(...)
10-
super
11+
singleton_class.attr_reader :snapshot
12+
13+
def_delegators :"self.class", :reset, :snapshot
14+
protected :snapshot
15+
16+
def self.setup!
17+
@snapshot = Config.default.new.freeze
18+
AttrAccessors.attributes.each do |attr|
19+
singleton_class.define_method(:"#{attr}=") do |val|
20+
@snapshot = snapshot.dup.update(attr => val).freeze
21+
end
22+
singleton_class.def_delegator :snapshot, attr
23+
def_delegators :"self.class", attr, :"#{attr}="
24+
end
25+
instance
26+
end
27+
28+
def initialize
29+
super(Config.default)
1130
@data = nil
1231
freeze
1332
end
1433

15-
AttrAccessors.attributes.each do |attr|
16-
singleton_class.define_method(attr) { snapshot.attr }
17-
singleton_class.define_method(:"#{attr}=") do |val|
18-
@snapshot = shapshot.with(attr => val)
34+
def new(**attrs) Config.new(self, **attrs) end
35+
36+
def self.reset(attr = nil)
37+
if attr.nil?
38+
@snapshot = Config.default.new.freeze
39+
self
40+
elsif snapshot.inherited?(attr)
41+
nil
42+
else
43+
old, new = send(attr), snapshot.dup
44+
new.reset(attr)
45+
@snapshot = new.freeze
46+
old
1947
end
2048
end
2149

2250
protected
2351

24-
def data = self.class.data
52+
def data = snapshot.data
2553

2654
end
2755
end

test/net/imap/test_config.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ class ConfigTest < Net::IMAP::TestCase
8888
assert_equal true, global.debug?
8989
global.reset(:debug)
9090
assert_equal false, global.debug?
91-
refute global.frozen?
91+
assert global.frozen?
9292
end
9393

9494
test "Net::IMAP.config" do
@@ -158,6 +158,10 @@ class ConfigTest < Net::IMAP::TestCase
158158
end
159159

160160
if defined?(Ractor)
161+
test ".global is deeply frozen (and Ractor shareable)" do
162+
assert Ractor.shareable?(Config.global)
163+
end
164+
161165
test ".version_defaults is deeply frozen (and Ractor shareable)" do
162166
assert Ractor.shareable? Config.version_defaults
163167
end

0 commit comments

Comments
 (0)