Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensuring class_getter runs exactly once under concurrent access #14905

Open
HertzDevil opened this issue Aug 14, 2024 · 3 comments
Open

Ensuring class_getter runs exactly once under concurrent access #14905

HertzDevil opened this issue Aug 14, 2024 · 3 comments

Comments

@HertzDevil
Copy link
Contributor

A class_getter with a block usually runs exactly once:

module Foo
  class_getter(x : Int32) { 1 }

  # same as:
  def self.x : Int32
    if (x = @@x).nil?
      @@x = 1
    else
      x
    end
  end
end

However, if the current fiber is ever suspended, the block might be run multiple times:

module Foo
  class_getter x : Int32 do
    puts "Foo.x" # this runs 5 times
    Fiber.yield
    1
  end
end

ch = Channel(Int32).new
5.times { spawn { ch.send Foo.x } }
5.times { ch.receive }

And if -Dpreview_mt is in effect, the block also gets run more than once, even without the Fiber.yield.

Often, Foo.x represents some kind of cached object that might be expensive to compute, like #14891 and the Unicode data tables; if we also disregard the block's return value, then this would include the various interrupt handlers guarded by Atomic::Flags too. There should be an easier way in the standard library to ensure this block is really run exactly once, regardless of the degree of concurrent access.

Constants are one alternative, but whether they run at program startup or on first access is rather opaque, and also they are slightly broken, such as in #13054.

This could apply to class_property as well, and less likely to the instance variants getter and property.

@HertzDevil HertzDevil changed the title Class getters with blocks that run exactly once Ensuring class_getter runs exactly once under concurrent access Aug 14, 2024
@straight-shoota
Copy link
Member

Perhaps we can jerry rig an atomic compare and set on the type id? Then we'll just need an otherwise unused type to signal that someone else is already in the process of calculating.

@HertzDevil
Copy link
Contributor Author

Wouldn't that only work for mixed unions?

@straight-shoota
Copy link
Member

straight-shoota commented Aug 14, 2024

I suppose we could force a mixed union using an appropriate type to signal work in progress?
The value of the class variable would effectively be typeof({{ yield }}) | WorkInProgress | Nil.
If the type of the block is a reference type, WorkInProgress could be a struct type and vice versa. That would mean two different signal types and select the appropriate one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants