* initial load attempts are processed (as before),
    * recursive load attempts return immediately (as before),
    * but subsequent concurrent load attempts from different threads
      wait on a semaphore (condition variable) and then retry (e.g.
      use the now-valid results of the first thread).

This is a slight modification of the solution I'd originally proposed, to 
prevent a deadlock
that could have arisen if three or more threads simultaneously attempted to 
load the same item.
Though it solves the bug as reported, it has room for improvement:

    * Failures aren't cached, so repeated attempts will be made to
      import invalid items each time they are encountered
    * It doesn't address any of the underlying referential ambiguity
      (module vs. filename)
    * The threading logic should probably be refactored into a separate
      class (as a start I encapsulated it in an ad hoc singleton class,
      so at least it isn't cluttering up the load method)

Signed-off-by: MarkusQ <[email protected]>
---
 lib/puppet/parser/parser_support.rb |  116 +++++++++++++++++++----------------
 1 files changed, 64 insertions(+), 52 deletions(-)

diff --git a/lib/puppet/parser/parser_support.rb 
b/lib/puppet/parser/parser_support.rb
index e1af2fe..224c2db 100644
--- a/lib/puppet/parser/parser_support.rb
+++ b/lib/puppet/parser/parser_support.rb
@@ -112,36 +112,22 @@ class Puppet::Parser::Parser
     end
 
     def find_or_load(namespace, name, type)
-        method = "find_" + type.to_s
-        if result = @loaded_code.send(method, namespace, name)
-            return result
-        end
-
+        method = "find_#{type}"
         fullname = (namespace + "::" + name).sub(/^::/, '')
-        self.load(fullname)
-
-        if result = @loaded_code.send(method, namespace, name)
-            return result
-        end
-
-        # Try to load the module init file if we're a qualified
-        # name
-        if fullname.include?("::")
-            module_name = fullname.split("::")[0]
+        things_to_try = [fullname]
 
-            self.load(module_name)
+        # Try to load the module init file if we're a qualified name
+        things_to_try << fullname.split("::")[0] if fullname.include?("::")
 
-            if result = @loaded_code.send(method, namespace, name)
-                return result
-            end
-        end
-
-        # Now try to load the bare name on its own.  This is
-        # appropriate if the class we're looking for is in a
+        # Otherwise try to load the bare name on its own.  This
+        # is appropriate if the class we're looking for is in a
         # module that's different from our namespace.
-        self.load(name)
+        things_to_try << name
 
-        @loaded_code.send(method, namespace, name)
+        until (result = @loaded_code.send(method, namespace, name)) or 
things_to_try.empty? do
+            self.load(things_to_try.shift)
+        end
+        return result
     end
 
     # Import our files.
@@ -207,42 +193,68 @@ class Puppet::Parser::Parser
         @lexer = Puppet::Parser::Lexer.new()
         @files = {}
         @loaded = []
-    end
-
-    # Try to load a class, since we could not find it.
-    def load(classname)
-        return false if classname == ""
-        filename = classname.gsub("::", File::SEPARATOR)
-
-        # First try to load the top-level module
-        mod = filename.scan(/^[\w-]+/).shift
-        unless @loaded.include?(mod)
-            @loaded << mod
-            begin
-                import(mod)
-                Puppet.info "Autoloaded module %s" % mod
-            rescue Puppet::ImportError => detail
-                # We couldn't load the module
+        @loading = {}
+        @loading.extend(MonitorMixin)
+        class << @loading
+            def done_with(item)
+                synchronize do 
+                    delete(item)[:busy].signal if self.has_key?(item) and 
self[item][:loader] == Thread.current
+                end
+            end
+            def owner_of(item)
+                synchronize do
+                    if !self.has_key? item
+                        self[item] = { :loader => Thread.current, :busy => 
self.new_cond}
+                        :nobody
+                      elsif self[item][:loader] == Thread.current
+                        :this_thread
+                      else
+                        flag = self[item][:busy]
+                        flag.wait
+                        flag.signal
+                        :another_thread
+                    end
+                end
             end
         end
+    end
 
-        # We don't know whether we're looking for a class or definition, so we 
have
-        # to test for both.
-        return true if @loaded_code.hostclass(classname) || 
@loaded_code.definition(classname)
-
-        unless @loaded.include?(filename)
-            @loaded << filename
-            # Then the individual file
+    # Utility method factored out of load
+    def able_to_import?(classname,item,msg)
+        unless @loaded.include?(item)
             begin
-                import(filename)
-                Puppet.info "Autoloaded file %s from module %s" % [filename, 
mod]
+              case @loading.owner_of(item)
+              when :this_thread
+                  return
+              when :another_thread
+                  return able_to_import?(classname,item,msg)
+              when :nobody
+                  import(item)
+                  Puppet.info "Autoloaded #{msg}"
+                  @loaded << item
+              end
             rescue Puppet::ImportError => detail
-                # We couldn't load the file
+                # We couldn't load the item
+            ensure
+                @loading.done_with(item)
             end
         end
         # We don't know whether we're looking for a class or definition, so we 
have
         # to test for both.
-        return true if @loaded_code.hostclass(classname) || 
@loaded_code.definition(classname)
+        return @loaded_code.hostclass(classname) || 
@loaded_code.definition(classname)
+    end
+
+    # Try to load a class, since we could not find it.
+    def load(classname)
+        return false if classname == ""
+        filename = classname.gsub("::", File::SEPARATOR)
+        mod = filename.scan(/^[\w-]+/).shift
+
+        # First try to load the top-level module then the individual file
+        # First try to load the top-level module then the individual file
+        [[mod,     "module %s"              %            mod ],
+         [filename,"file %s from module %s" % [filename, mod]]
+        ].any? { |item,description| 
able_to_import?(classname,item,description) }
     end
 
     # Split an fq name into a namespace and name
-- 
1.6.0.4


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Puppet Developers" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to 
[email protected]
For more options, visit this group at 
http://groups.google.com/group/puppet-dev?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to