zeitwerk 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5d964999444d0bc99d003d604cb2634d2753c579a4aebcce4fccb4c9ded78530
4
- data.tar.gz: 95fc0030faf0472c8359c3665a09fa68dc3c79bff1d813e55d0f03f68c6f5282
3
+ metadata.gz: 2af29bb519da160c83620c23a1f1c119c7b534a3e3db308b2635b344b435dfa1
4
+ data.tar.gz: ec2dc810c74887befea3550b3305b0b096610cbb06a2983dad0c041c9bed34dc
5
5
  SHA512:
6
- metadata.gz: 81f2ac76566c1cda996eeac723fa7579dba24011f32fab94458ced0faf5eb7fe920ee2e016ce45ec83fc0d7b17244a1bf98764d71e667cbe067fa8ee87f01ca3
7
- data.tar.gz: a90efd80c7caba53d6ec12bc20ee1a76f8d49a6a2e59a82b60a646486eac758348ff1250eee42585dbb2294718a41d59d97c9254ee51b802cddd1ce7add46ead
6
+ metadata.gz: 8cafd96d9f21546ea8dcb58f8b589d59b5de994492a2f89f6cd4eb2701d673e58c888884b672ddd47580778090f4af918c908e6325d5690ce8297b0e152eb391
7
+ data.tar.gz: e1fba9ebf4e35fa10c2577bdb4a19124ee26e3d09a85b2382a5bc0c83688130b14a0c8e9900e6572f25efe86c388b54139d9ca82d7053cfc415b39a618d1ec16
@@ -68,8 +68,9 @@ module Zeitwerk
68
68
  # @return [{String => String}]
69
69
  attr_reader :shadowed_files
70
70
 
71
- # Maps real absolute paths for which an autoload has been set to their
72
- # corresponding parent class or module and constant name.
71
+ # Maps real absolute paths for which an autoload has been set ---and not
72
+ # executed--- to their corresponding parent class or module and constant
73
+ # name.
73
74
  #
74
75
  # "/Users/fxn/blog/app/models/user.rb" => [Object, "User"],
75
76
  # "/Users/fxn/blog/app/models/hotel/pricing.rb" => [Hotel, "Pricing"]
@@ -84,13 +85,18 @@ module Zeitwerk
84
85
  #
85
86
  # Files are removed as they are autoloaded, but directories need to wait due
86
87
  # to concurrency (see why in Zeitwerk::Loader::Callbacks#on_dir_autoloaded).
88
+ #
89
+ # @private
90
+ # @return [<String>]
87
91
  attr_reader :autoloaded_dirs
88
92
 
89
- # Constant paths loaded so far.
93
+ # Constants paths that would be unloaded if you would reload. If reloading
94
+ # is enabled, this hash is filled as constants are autoloaded or eager
95
+ # loaded. Otherwise, the collection remains empty.
90
96
  #
91
97
  # @private
92
- # @return [Set<String>]
93
- attr_reader :loaded_cpaths
98
+ # @return [{String => (String, (Module, String))}]
99
+ attr_reader :to_unload
94
100
 
95
101
  # Maps constant paths of namespaces to arrays of corresponding directories.
96
102
  #
@@ -137,7 +143,7 @@ module Zeitwerk
137
143
  @ignored_paths = Set.new
138
144
  @autoloads = {}
139
145
  @autoloaded_dirs = []
140
- @loaded_cpaths = Set.new
146
+ @to_unload = {}
141
147
  @lazy_subdirs = {}
142
148
  @shadowed_files = {}
143
149
  @eager_load_exclusions = Set.new
@@ -270,19 +276,21 @@ module Zeitwerk
270
276
 
271
277
  autoloads.each do |realpath, (parent, cname)|
272
278
  if parent.autoload?(cname)
273
- parent.send(:remove_const, cname)
274
- log("autoload for #{cpath(parent, cname)} removed") if logger
279
+ unload_autoload(parent, cname)
275
280
  else
276
- if cdef?(parent, cname)
277
- parent.send(:remove_const, cname)
278
- log("#{cpath(parent, cname)} unloaded") if logger
279
- else
280
- # Already unloaded somehow, that is fine.
281
- end
281
+ # Could happen if loaded with require_relative. require_relative is
282
+ # not supported, and the cpath would escape `to_unload?`. This is
283
+ # just defensive code to clean things up as much as we are able to.
284
+ unload_cref(parent, cname) if cdef?(parent, cname)
282
285
  unloaded_files.add(realpath) if ruby?(realpath)
283
286
  end
284
287
  end
285
288
 
289
+ to_unload.each_value do |(realpath, (parent, cname))|
290
+ unload_cref(parent, cname) if cdef?(parent, cname)
291
+ unloaded_files.add(realpath) if ruby?(realpath)
292
+ end
293
+
286
294
  unless unloaded_files.empty?
287
295
  # Bootsnap decorates Kernel#require to speed it up using a cache and
288
296
  # this optimization does not check if $LOADED_FEATURES has the file.
@@ -300,7 +308,7 @@ module Zeitwerk
300
308
 
301
309
  autoloads.clear
302
310
  autoloaded_dirs.clear
303
- loaded_cpaths.clear
311
+ to_unload.clear
304
312
  lazy_subdirs.clear
305
313
  shadowed_files.clear
306
314
 
@@ -369,12 +377,13 @@ module Zeitwerk
369
377
  mutex.synchronize { eager_load_exclusions.merge(expand_paths(paths)) }
370
378
  end
371
379
 
372
- # Says if the given constant path has been loaded.
380
+ # Says if the given constant path would be unloaded on reload. This
381
+ # predicate returns `false` if reloading is disabled.
373
382
  #
374
383
  # @param cpath [String]
375
384
  # @return [Boolean]
376
- def loaded?(cpath)
377
- loaded_cpaths.member?(cpath)
385
+ def to_unload?(cpath)
386
+ to_unload.key?(cpath)
378
387
  end
379
388
 
380
389
  # --- Class methods ---------------------------------------------------------------------------
@@ -662,5 +671,21 @@ module Zeitwerk
662
671
  end
663
672
  end
664
673
  end
674
+
675
+ # @param parent [Module]
676
+ # @param cname [String]
677
+ # @return [void]
678
+ def unload_autoload(parent, cname)
679
+ parent.send(:remove_const, cname)
680
+ log("autoload for #{cpath(parent, cname)} removed") if logger
681
+ end
682
+
683
+ # @param parent [Module]
684
+ # @param cname [String]
685
+ # @return [void]
686
+ def unload_cref(parent, cname)
687
+ parent.send(:remove_const, cname)
688
+ log("#{cpath(parent, cname)} unloaded") if logger
689
+ end
665
690
  end
666
691
  end
@@ -5,10 +5,10 @@ module Zeitwerk::Loader::Callbacks
5
5
  # @param file [String]
6
6
  # @return [void]
7
7
  def on_file_autoloaded(file)
8
- parent, cname = cref_autoloaded_from(file)
9
- loaded_cpaths.add(cpath(parent, cname))
8
+ cref = autoloads.delete(file)
9
+ to_unload[cpath(*cref)] = [file, cref] if reloading_enabled?
10
10
  Zeitwerk::Registry.unregister_autoload(file)
11
- log("constant #{cpath(parent, cname)} loaded from file #{file}") if logger
11
+ log("constant #{cpath(*cref)} loaded from file #{file}") if logger
12
12
  end
13
13
 
14
14
  # Invoked from our decorated Kernel#require when a managed directory is
@@ -25,23 +25,25 @@ module Zeitwerk::Loader::Callbacks
25
25
  # autovivifies the module, and while autoloads for its children are being
26
26
  # set, thread t2 autoloads the same namespace.
27
27
  #
28
- # Without the mutex and short-circuiting break, t2 would reset the module.
28
+ # Without the mutex and subsequent delete call, t2 would reset the module.
29
29
  # That not only would reassign the constant (undesirable per se) but, worse,
30
30
  # the module object created by t2 wouldn't have any of the autoloads for its
31
31
  # children, since t1 would have correctly deleted its lazy_subdirs entry.
32
32
  mutex2.synchronize do
33
- parent, cname = cref_autoloaded_from(dir)
34
- # If reloading is disabled and there are several threads autoloading the
35
- # same namespace at the same time, the parent is going to bbe nil for all
36
- # except the first one.
37
- break if parent.nil? || loaded_cpaths.include?(cpath(parent, cname))
33
+ if cref = autoloads.delete(dir)
34
+ autovivified_module = cref[0].const_set(cref[1], Module.new)
35
+ log("module #{autovivified_module.name} autovivified from directory #{dir}") if logger
38
36
 
39
- autovivified_module = parent.const_set(cname, Module.new)
40
- log("module #{autovivified_module.name} autovivified from directory #{dir}") if logger
37
+ to_unload[autovivified_module.name] = [dir, cref] if reloading_enabled?
41
38
 
42
- loaded_cpaths.add(autovivified_module.name)
43
- autoloaded_dirs << dir
44
- on_namespace_loaded(autovivified_module)
39
+ # We don't unregister `dir` in the registry because concurrent threads
40
+ # wouldn't find a loader associated to it in Kernel#require and would
41
+ # try to require the directory. Instead, we are going to keep track of
42
+ # these to be able to unregister later if eager loading.
43
+ autoloaded_dirs << dir
44
+
45
+ on_namespace_loaded(autovivified_module)
46
+ end
45
47
  end
46
48
  end
47
49
 
@@ -59,10 +61,4 @@ module Zeitwerk::Loader::Callbacks
59
61
  end
60
62
  end
61
63
  end
62
-
63
- # @private
64
- # @return [(Module, String)]
65
- def cref_autoloaded_from(path)
66
- reloading_enabled? ? autoloads[path] : autoloads.delete(path)
67
- end
68
64
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
- VERSION = "2.0.0"
4
+ VERSION = "2.1.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zeitwerk
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Xavier Noria
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-04-07 00:00:00.000000000 Z
11
+ date: 2019-04-09 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |2
14
14
  Zeitwerk implements constant autoloading with Ruby semantics. Each gem