diff --git a/scripts/generate_object_properties.cr b/scripts/generate_object_properties.cr index 8c3eec909a3a..a231273e5706 100755 --- a/scripts/generate_object_properties.cr +++ b/scripts/generate_object_properties.cr @@ -82,6 +82,33 @@ struct Generator TEXT end + # NOTE: we explicitly cast .as(typeof(yield)) to avoid type inference + # regressions, see https://github.com/crystal-lang/crystal/issues/15556 + # + # FIXME: still parallel unsafe in case of parallel call to `setter` (unless + # type is a reference) because we can't trust mixed union load/store to be + # safe operations. + def def_safe_getter(suffix = "") + <<-TEXT + {% if block %} #{@var_prefix}__{{var_name}}_lock = Crystal::Lock.new {% end %} + + def #{@method_prefix}{{var_name}}#{suffix} {% if type %} : {{type}} {% end %} + {% if block %} + if (%value = #{@var_prefix}__{{var_name}}_lock.rlock { #{@var_prefix}{{var_name}} }).nil? + #{@var_prefix}__{{var_name}}_lock + .lock { #{@var_prefix}{{var_name}} ||= {{yield}} } + {% unless type %}.as(typeof({{yield}})){% end %} + else + %value + end + {% else %} + #{@var_prefix}{{var_name}} + {% end %} + end + + TEXT + end + def def_getter! <<-TEXT def #{@method_prefix}{{var_name}}? {% if type %} : {{type}}? {% end %} @@ -116,7 +143,7 @@ struct Generator macro #{@macro_prefix}property(*names, &block) {% for name in names %} #{def_vars} - #{def_getter} + #{@macro_prefix == "class_" ? def_safe_getter : def_getter} #{def_setter} {% end %} end @@ -128,7 +155,7 @@ struct Generator macro #{@macro_prefix}property?(*names, &block) {% for name in names %} #{def_vars} - #{def_getter "?"} + #{@macro_prefix == "class_" ? def_safe_getter("?") : def_getter("?")} #{def_setter} {% end %} end @@ -159,6 +186,8 @@ File.open(output, "w") do |f| f.puts "#" f.puts "# DO NOT EDIT" f.puts + f.puts %(require "crystal/lock") + f.puts f.puts "class Object" g = Generator.new(f, "", "", "@", "#") @@ -281,7 +310,7 @@ File.open(output, "w") do |f| macro class_getter(*names, &block) {% for name in names %} #{g.def_vars} - #{g.def_getter} + #{g.def_safe_getter} {% end %} end @@ -309,7 +338,7 @@ File.open(output, "w") do |f| macro class_getter?(*names, &block) {% for name in names %} #{g.def_vars} - #{g.def_getter "?"} + #{g.def_safe_getter "?"} {% end %} end diff --git a/spec/compiler/semantic/doc_spec.cr b/spec/compiler/semantic/doc_spec.cr index 53c3f4ecc9ae..46e8dcfd8b5d 100644 --- a/spec/compiler/semantic/doc_spec.cr +++ b/spec/compiler/semantic/doc_spec.cr @@ -678,8 +678,7 @@ describe "Semantic: doc" do it "expands record macro with comments (#16074)" do result = semantic <<-CRYSTAL, wants_doc: true - require "macros" - require "object/properties" + require "prelude" record Foo, # This is a multiline diff --git a/src/crystal/lock.cr b/src/crystal/lock.cr new file mode 100644 index 000000000000..5b7a65acac5b --- /dev/null +++ b/src/crystal/lock.cr @@ -0,0 +1,41 @@ +require "sync/mu" + +module Crystal + # :nodoc: + # + # Always checked alternative to Sync::Mutex and Sync::RWLock in a single and + # significantly smaller type (no crystal type id, no lock type, no reentrancy + # counter). + struct Lock + @mu = Sync::MU.new + @locked_by : Fiber? + + def lock(&) + unless @mu.try_lock? + raise Sync::Error::Deadlock.new if deadlock? + @mu.lock_slow + end + + begin + @locked_by = Fiber.current + yield + ensure + @locked_by = nil + @mu.unlock + end + end + + def rlock(&) + @mu.rlock + begin + yield + ensure + @mu.runlock + end + end + + private def deadlock? + @locked_by == Fiber.current + end + end +end diff --git a/src/crystal/pointer_linked_list.cr b/src/crystal/pointer_linked_list.cr index 75abfabdf578..05f8767840e2 100644 --- a/src/crystal/pointer_linked_list.cr +++ b/src/crystal/pointer_linked_list.cr @@ -7,8 +7,22 @@ struct Crystal::PointerLinkedList(T) module Node macro included - property previous : ::Pointer(self) = ::Pointer(self).null - property next : ::Pointer(self) = ::Pointer(self).null + @previous = Pointer(self).null + @next = Pointer(self).null + + def previous : ::Pointer(self) + @previous + end + + def previous=(@previous : ::Pointer(self)) + end + + def next : ::Pointer(self) + @next + end + + def next=(@next : ::Pointer(self)) + end end end diff --git a/src/object/properties.cr b/src/object/properties.cr index 4b46593063e6..a6921e683fa6 100644 --- a/src/object/properties.cr +++ b/src/object/properties.cr @@ -4,6 +4,8 @@ # # DO NOT EDIT +require "crystal/lock" + class Object # Defines getter methods to access instance variables. # @@ -334,10 +336,14 @@ class Object {% type = nil %} {% end %} + {% if block %} @@__{{var_name}}_lock = Crystal::Lock.new {% end %} + def self.{{var_name}} {% if type %} : {{type}} {% end %} {% if block %} - if (%value = @@{{var_name}}).nil? - @@{{var_name}} = {{yield}} + if (%value = @@__{{var_name}}_lock.rlock { @@{{var_name}} }).nil? + @@__{{var_name}}_lock + .lock { @@{{var_name}} ||= {{yield}} } + {% unless type %}.as(typeof({{yield}})){% end %} else %value end @@ -389,10 +395,14 @@ class Object {% type = nil %} {% end %} + {% if block %} @@__{{var_name}}_lock = Crystal::Lock.new {% end %} + def self.{{var_name}}? {% if type %} : {{type}} {% end %} {% if block %} - if (%value = @@{{var_name}}).nil? - @@{{var_name}} = {{yield}} + if (%value = @@__{{var_name}}_lock.rlock { @@{{var_name}} }).nil? + @@__{{var_name}}_lock + .lock { @@{{var_name}} ||= {{yield}} } + {% unless type %}.as(typeof({{yield}})){% end %} else %value end @@ -528,10 +538,14 @@ class Object {% type = nil %} {% end %} + {% if block %} @@__{{var_name}}_lock = Crystal::Lock.new {% end %} + def self.{{var_name}} {% if type %} : {{type}} {% end %} {% if block %} - if (%value = @@{{var_name}}).nil? - @@{{var_name}} = {{yield}} + if (%value = @@__{{var_name}}_lock.rlock { @@{{var_name}} }).nil? + @@__{{var_name}}_lock + .lock { @@{{var_name}} ||= {{yield}} } + {% unless type %}.as(typeof({{yield}})){% end %} else %value end @@ -569,10 +583,14 @@ class Object {% type = nil %} {% end %} + {% if block %} @@__{{var_name}}_lock = Crystal::Lock.new {% end %} + def self.{{var_name}}? {% if type %} : {{type}} {% end %} {% if block %} - if (%value = @@{{var_name}}).nil? - @@{{var_name}} = {{yield}} + if (%value = @@__{{var_name}}_lock.rlock { @@{{var_name}} }).nil? + @@__{{var_name}}_lock + .lock { @@{{var_name}} ||= {{yield}} } + {% unless type %}.as(typeof({{yield}})){% end %} else %value end diff --git a/src/sync/waiter.cr b/src/sync/waiter.cr index f9487615d1ed..9f0919ccf36f 100644 --- a/src/sync/waiter.cr +++ b/src/sync/waiter.cr @@ -10,7 +10,14 @@ module Sync include Crystal::PointerLinkedList::Node - property cv_mu : Pointer(MU) + @cv_mu : MU* + + def cv_mu : MU* + @cv_mu + end + + def cv_mu=(@cv_mu : MU*) + end def initialize(@type : Type, @cv_mu : Pointer(MU) = Pointer(MU).null) # protects against spurious wakeups (invalid manual fiber enqueues) that