-
-
Notifications
You must be signed in to change notification settings - Fork 119
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Replace TracePoint with const_added for explicit namespaces
- Loading branch information
Showing
11 changed files
with
77 additions
and
219 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# frozen_string_literal: true | ||
|
||
module Zeitwerk::ExplicitNamespacesRegistry | ||
def const_added(cname) | ||
# Module#autoload triggers const_added too, but we are only interested in | ||
# "actual" added constants. | ||
unless autoload?(cname, false) | ||
Zeitwerk::ExplicitNamespace.__on_const_added(self, cname) | ||
end | ||
super | ||
end | ||
|
||
Module.prepend(self) | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,93 +1,76 @@ | ||
# 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 | ||
# Centralizes the logic 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 | ||
module Synchronized | ||
MUTEX = Mutex.new | ||
|
||
def on_const_added(...) | ||
MUTEX.synchronize { super } | ||
end | ||
|
||
def register(...) | ||
MUTEX.synchronize { super } | ||
end | ||
|
||
def unregister_loader(...) | ||
MUTEX.synchronize { super } | ||
end | ||
end | ||
|
||
# Maps cpaths of explicit namespaces with their corresponding loader. | ||
# Entries are added as the namespaces are found, and removed as they are | ||
# autoloaded. | ||
# | ||
# @sig Hash[String => Zeitwerk::Loader] | ||
@cpaths = {} | ||
|
||
class << self | ||
include RealModName | ||
extend Internal | ||
|
||
# Maps constant paths that correspond to explicit namespaces according to | ||
# the file system, to the loader responsible for them. | ||
# | ||
# @sig Hash[String, Zeitwerk::Loader] | ||
attr_reader :cpaths | ||
private :cpaths | ||
|
||
# @sig Mutex | ||
attr_reader :mutex | ||
private :mutex | ||
|
||
# @sig TracePoint | ||
attr_reader :tracer | ||
private :tracer | ||
# @sig (Module, Symbol) -> void | ||
internal def on_const_added(mod, cname) | ||
cpath = mod.equal?(Object) ? cname.name : "#{real_mod_name(mod)}::#{cname}" | ||
if loader = @cpaths.delete(cpath) | ||
loader.on_namespace_loaded(mod.const_get(cname, false)) | ||
end | ||
end | ||
|
||
# Asserts `cpath` corresponds to an explicit namespace for which `loader` | ||
# is responsible. | ||
# | ||
# @sig (String, Zeitwerk::Loader) -> void | ||
internal 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 | ||
@cpaths[cpath] = loader | ||
end | ||
|
||
# @sig (Zeitwerk::Loader) -> void | ||
internal def unregister_loader(loader) | ||
cpaths.delete_if { |_cpath, l| l == loader } | ||
disable_tracer_if_unneeded | ||
@cpaths.delete_if { _2.equal?(loader) } | ||
end | ||
|
||
# This is an internal method only used by the test suite. | ||
# | ||
# @sig (String) -> bool | ||
internal def registered?(cpath) | ||
cpaths.key?(cpath) | ||
@cpaths[cpath] | ||
end | ||
|
||
# This is an internal method only used by the test suite. | ||
# | ||
# @sig () -> void | ||
private def disable_tracer_if_unneeded | ||
mutex.synchronize do | ||
tracer.disable if cpaths.empty? | ||
end | ||
internal def clear | ||
@cpaths.clear | ||
end | ||
|
||
# @sig (TracePoint) -> void | ||
private 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 | ||
prepend Synchronized unless RUBY_ENGINE == "ruby" | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.