Skip to content

Commit

Permalink
Fix: don't capture block in Crystal.once(flag, &)
Browse files Browse the repository at this point in the history
Avoids a breaking change at the expense of some optimization.
  • Loading branch information
ysbaddaden committed Jan 14, 2025
1 parent e2629a2 commit d4fb901
Showing 1 changed file with 27 additions and 18 deletions.
45 changes: 27 additions & 18 deletions src/crystal/once.cr
Original file line number Diff line number Diff line change
Expand Up @@ -46,39 +46,43 @@ end
end

# :nodoc:
#
# Identical to `__crystal_once` but takes a block with possibly closured
# data. Used by `class_[getter|property](declaration, &block)` for example.
@[AlwaysInline]
def self.once(flag : OnceState*, &block) : Nil
def self.once(flag : OnceState*, &) : Nil
return if flag.value.initialized?
once(flag, block.pointer, block.closure_data)
once_unreachable unless flag.value.initialized?
once_exec(flag) { yield }
end

# :nodoc:
#
# Using @[NoInline] so LLVM optimizes for the hot path (var already
# initialized).
@[NoInline]
def self.once(flag : OnceState*, initializer : Void*, closure_data : Void*) : Nil
once_exec(flag) { Proc(Nil).new(initializer, closure_data).call }

# safety check, and allows to safely call `#once_unreachable` in
# `__crystal_once`
unless flag.value.initialized?
System.print_error "BUG: failed to initialize constant or class variable\n"
LibC._exit(1)
end
end

private def self.once_exec(flag, &)
once_mutex.synchronize do
case flag.value
in .initialized?
return
in .uninitialized?
flag.value = OnceState::Processing
Proc(Nil).new(initializer, closure_data).call
yield
flag.value = OnceState::Initialized
in .processing?
raise "Recursion while initializing class variables and/or constants"
end
end

# safety check, and allows to safely call `#once_unreachable` in
# `__crystal_once`
unless flag.value.initialized?
System.print_error "BUG: failed to initialize constant or class variable\n"
LibC._exit(1)
end
end
end

Expand Down Expand Up @@ -115,10 +119,17 @@ end
@mutex = Mutex.new(:reentrant)
@rec = [] of Bool*

def once(flag : Bool*, &)
return if flag.value
once_exec(flag) { yield }
end

@[NoInline]
def once(flag : Bool*, initializer : Void*, closure_data : Void*)
return if flag.value
once_exec(flag) { Proc(Nil).new(initializer, closure_data).call }
end

private def once_exec(flag, &)
@mutex.synchronize do
return if flag.value

Expand All @@ -127,7 +138,7 @@ end
end
@rec << flag

Proc(Nil).new(initializer, closure_data).call
yield
flag.value = true

@rec.pop
Expand All @@ -147,11 +158,9 @@ end
end

# :nodoc:
@[AlwaysInline]
def self.once(flag : Bool*, &block) : Nil
def self.once(flag : Bool*, &) : Nil
return if flag.value
once_state.once(flag, block.pointer, block.closure_data)
once_unreachable unless flag.value
once_state.once(flag) { yield }
end
end

Expand Down

0 comments on commit d4fb901

Please sign in to comment.