File: //usr/local/rvm/gems/default/gems/zeitwerk-2.5.1/lib/zeitwerk/explicit_namespace.rb
# frozen_string_literal: true
module Zeitwerk
# Centralizes the logic for the trace point used to detect the creation of
# explicit namespaces, needed to descend into matching subdirectories right
# after the constant has been defined.
#
# The implementation assumes an explicit namespace is managed by one loader.
# Loaders that reopen namespaces owned by other projects are responsible for
# loading their constant before setup. This is documented.
module ExplicitNamespace # :nodoc: all
class << self
include RealModName
# Maps constant paths that correspond to explicit namespaces according to
# the file system, to the loader responsible for them.
#
# @private
# @sig Hash[String, Zeitwerk::Loader]
attr_reader :cpaths
# @private
# @sig Mutex
attr_reader :mutex
# @private
# @sig TracePoint
attr_reader :tracer
# Asserts `cpath` corresponds to an explicit namespace for which `loader`
# is responsible.
#
# @private
# @sig (String, Zeitwerk::Loader) -> void
def register(cpath, loader)
mutex.synchronize do
cpaths[cpath] = loader
# We check enabled? because, looking at the C source code, enabling an
# enabled tracer does not seem to be a simple no-op.
tracer.enable unless tracer.enabled?
end
end
# @private
# @sig (Zeitwerk::Loader) -> void
def unregister_loader(loader)
cpaths.delete_if { |_cpath, l| l == loader }
disable_tracer_if_unneeded
end
private
# @sig () -> void
def disable_tracer_if_unneeded
mutex.synchronize do
tracer.disable if cpaths.empty?
end
end
# @sig (TracePoint) -> void
def tracepoint_class_callback(event)
# If the class is a singleton class, we won't do anything with it so we
# can bail out immediately. This is several orders of magnitude faster
# than accessing its name.
return if event.self.singleton_class?
# It might be tempting to return if name.nil?, to avoid the computation
# of a hash code and delete call. But Ruby does not trigger the :class
# event on Class.new or Module.new, so that would incur in an extra call
# for nothing.
#
# On the other hand, if we were called, cpaths is not empty. Otherwise
# the tracer is disabled. So we do need to go ahead with the hash code
# computation and delete call.
if loader = cpaths.delete(real_mod_name(event.self))
loader.on_namespace_loaded(event.self)
disable_tracer_if_unneeded
end
end
end
@cpaths = {}
@mutex = Mutex.new
# We go through a method instead of defining a block mainly to have a better
# label when profiling.
@tracer = TracePoint.new(:class, &method(:tracepoint_class_callback))
end
end