You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2012/05/12 08:53:17 UTC

svn commit: r1337454 - in /tapestry/tapestry5/branches/5.3/tapestry-core/src: main/java/org/apache/tapestry5/ main/java/org/apache/tapestry5/internal/structure/ main/java/org/apache/tapestry5/internal/transform/ main/java/org/apache/tapestry5/internal/...

Author: hlship
Date: Sat May 12 06:53:16 2012
New Revision: 1337454

URL: http://svn.apache.org/viewvc?rev=1337454&view=rev
Log:
TAP5-1929: Performance improvements
Rework page loading and lazily initialization inside InternalComponentResourcesImpl to avoid some synchronization hotspots



Added:
    tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/runtime/PageLifecycleCallbackHub.java
Modified:
    tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/ComponentResources.java
    tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElementImpl.java
    tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/InternalComponentResourcesImpl.java
    tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/Page.java
    tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/PageImpl.java
    tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/PageResetListener.java
    tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/StructureMessages.java
    tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/InjectComponentWorker.java
    tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ParameterConduit.java
    tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ParameterWorker.java
    tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/util/NamedSet.java
    tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/runtime/PageLifecycleAdapter.java
    tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/runtime/PageLifecycleListener.java
    tapestry/tapestry5/branches/5.3/tapestry-core/src/main/resources/org/apache/tapestry5/internal/structure/StructureStrings.properties
    tapestry/tapestry5/branches/5.3/tapestry-core/src/test/java/org/apache/tapestry5/internal/structure/PageImplTest.java

Modified: tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/ComponentResources.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/ComponentResources.java?rev=1337454&r1=1337453&r2=1337454&view=diff
==============================================================================
--- tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/ComponentResources.java (original)
+++ tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/ComponentResources.java Sat May 12 06:53:16 2012
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2012 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ import org.apache.tapestry5.ioc.Messages
 import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.model.ComponentModel;
 import org.apache.tapestry5.runtime.Component;
+import org.apache.tapestry5.runtime.PageLifecycleCallbackHub;
 import org.apache.tapestry5.runtime.PageLifecycleListener;
 
 import java.lang.annotation.Annotation;
@@ -76,11 +77,11 @@ public interface ComponentResources exte
 
     /**
      * Returns an embedded component, given the component's id.
-     * 
+     *
      * @param embeddedId
-     *            selects the embedded component (case is ignored)
+     *         selects the embedded component (case is ignored)
      * @throws org.apache.tapestry5.ioc.util.UnknownValueException
-     *             if this component does not contain a component with the given id
+     *         if this component does not contain a component with the given id
      */
 
     Component getEmbeddedComponent(String embeddedId);
@@ -92,11 +93,11 @@ public interface ComponentResources exte
 
     /**
      * Obtains an annotation provided by a parameter.
-     * 
+     *
      * @param parameterName
-     *            name of parameter to search for the annotation
+     *         name of parameter to search for the annotation
      * @param annotationType
-     *            the type of annotation
+     *         the type of annotation
      * @return the annotation if found or null otherwise
      */
     <T extends Annotation> T getParameterAnnotation(String parameterName, Class<T> annotationType);
@@ -104,9 +105,9 @@ public interface ComponentResources exte
     /**
      * Indentifies all parameters that are not formal parameters and writes each as a attribute/value pair into the
      * current element of the markup writer.
-     * 
+     *
      * @param writer
-     *            to which {@link MarkupWriter#attributes(Object[]) attributes} will be written
+     *         to which {@link MarkupWriter#attributes(Object[]) attributes} will be written
      */
     void renderInformalParameters(MarkupWriter writer);
 
@@ -120,9 +121,9 @@ public interface ComponentResources exte
      * with property bindings, and is used to determine the actual type of the property, rather than the type of
      * parameter (remember that type coercion automatically occurs, which can mask significant differences between the
      * parameter type and the bound property type).
-     * 
+     *
      * @param parameterName
-     *            used to select the parameter (case is ignored)
+     *         used to select the parameter (case is ignored)
      * @return the type of the bound parameter, or null if the parameter is not bound
      * @see Binding#getBindingType()
      */
@@ -130,54 +131,65 @@ public interface ComponentResources exte
 
     /**
      * Returns an annotation provider, used to obtain annotations related to the parameter.
-     * 
+     *
      * @param parameterName
-     *            used to select the parameter (case is ignored)
+     *         used to select the parameter (case is ignored)
      * @return the annotation provider, or null if the parameter is not bound
      */
     AnnotationProvider getAnnotationProvider(String parameterName);
 
     /**
      * Used to access an informal parameter that's a Block.
-     * 
+     *
      * @param parameterName
-     *            the name of the informal parameter (case is ignored)
+     *         the name of the informal parameter (case is ignored)
      * @return the informal Block parameter, or null if not bound
      */
     Block getBlockParameter(String parameterName);
 
     /**
      * Returns a previously stored render variable.
-     * 
+     *
      * @param name
-     *            of the variable (case will be ignored)
+     *         of the variable (case will be ignored)
      * @return the variable's value
      * @throws IllegalArgumentException
-     *             if the name doesn't correspond to a stored value
+     *         if the name doesn't correspond to a stored value
      */
     Object getRenderVariable(String name);
 
     /**
      * Stores a render variable, accessible with the provided name.
-     * 
+     *
      * @param name
-     *            of value to store
+     *         of value to store
      * @param value
-     *            value to store (may not be null)
+     *         value to store (may not be null)
      * @throws IllegalStateException
-     *             if the component is not currently rendering
+     *         if the component is not currently rendering
      */
     void storeRenderVariable(String name, Object value);
 
     /**
      * Adds a listener object that will be notified about page lifecycle events.
+     *
+     * @deprecated In 5.3.3, use {@link #getPageLifecycleCallbackHub()} instead
      */
     void addPageLifecycleListener(PageLifecycleListener listener);
 
     /**
+     * Provides access to an object that can be used to register callbacks for page lifecycle events.
+     *
+     * @return the hub
+     * @since 5.3.3
+     */
+    PageLifecycleCallbackHub getPageLifecycleCallbackHub();
+
+    /**
      * Removes a previously added listener.
-     * 
+     *
      * @since 5.2.0
+     * @deprecated in 5.3.3, not necessary with {@link PageLifecycleCallbackHub#addPageLoadedCallback(Runnable)}.
      */
     void removePageLifecycleListener(PageLifecycleListener listener);
 
@@ -190,25 +202,27 @@ public interface ComponentResources exte
 
     /**
      * Returns the name of element that represents the component in its template, or null if not known.
-     * 
+     *
      * @return the element name or null
      */
     String getElementName();
 
     /**
      * Returns a list of the names of any informal parameters bound to this component.
-     * 
+     *
      * @return the name sorted alphabetically
      * @see org.apache.tapestry5.annotations.SupportsInformalParameters
      */
     List<String> getInformalParameterNames();
 
-/**
+    /**
      * Reads an informal parameter and {@linkplain org.apache.tapestry5.ioc.services.TypeCoercer coercers} the bound
      * value to the indicated type.
      *
-     * @param name name of informal parameter
-     * @param type output value type
+     * @param name
+     *         name of informal parameter
+     * @param type
+     *         output value type
      * @return instance of type
      */
     <T> T getInformalParameter(String name, Class<T> type);
@@ -216,7 +230,7 @@ public interface ComponentResources exte
     /**
      * Returns true if these resources represent a mixin to another component. The component is the
      * {@linkplain #getContainerResources() container} of this resources.
-     * 
+     *
      * @since 5.2.0
      */
     boolean isMixin();

Modified: tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElementImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElementImpl.java?rev=1337454&r1=1337453&r2=1337454&view=diff
==============================================================================
--- tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElementImpl.java (original)
+++ tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElementImpl.java Sat May 12 06:53:16 2012
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008, 2009, 2010, 2011 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2009, 2010, 2011, 2012 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -519,16 +519,23 @@ public class ComponentPageElementImpl ex
      * Constructor for other components embedded within the root component or at deeper levels of
      * the hierarchy.
      *
-     * @param page             ultimately containing this component
-     * @param container        component immediately containing this component (may be null for a root component)
-     * @param id               unique (within the container) id for this component (may be null for a root
-     *                         component)
-     * @param elementName      the name of the element which represents this component in the template, or null
-     *                         for
-     *                         &lt;comp&gt; element or a page component
-     * @param instantiator     used to create the new component instance and access the component's model
-     * @param location         location of the element (within a template), used as part of exception reporting
-     * @param elementResources Provides access to common methods of various services
+     * @param page
+     *         ultimately containing this component
+     * @param container
+     *         component immediately containing this component (may be null for a root component)
+     * @param id
+     *         unique (within the container) id for this component (may be null for a root
+     *         component)
+     * @param elementName
+     *         the name of the element which represents this component in the template, or null
+     *         for
+     *         &lt;comp&gt; element or a page component
+     * @param instantiator
+     *         used to create the new component instance and access the component's model
+     * @param location
+     *         location of the element (within a template), used as part of exception reporting
+     * @param elementResources
+     *         Provides access to common methods of various services
      */
     ComponentPageElementImpl(Page page, ComponentPageElement container, String id, String nestedId, String completeId,
                              String elementName, Instantiator instantiator, Location location,
@@ -562,14 +569,11 @@ public class ComponentPageElementImpl ex
 
         renderingValue = elementResources.createPerThreadValue();
 
-        page.addLifecycleListener(new PageLifecycleAdapter()
+        page.addPageLoadedCallback(new Runnable()
         {
-            @Override
-            public void containingPageDidLoad()
+            public void run()
             {
                 pageLoaded();
-
-                ComponentPageElementImpl.this.page.removeLifecycleListener(this);
             }
         });
     }
@@ -783,7 +787,7 @@ public class ComponentPageElementImpl ex
 
         initializeRenderPhases();
 
-        page.addVerifyListener(new Runnable()
+        page.addVerifyCallback(new Runnable()
         {
             public void run()
             {
@@ -940,10 +944,12 @@ public class ComponentPageElementImpl ex
     /**
      * Invokes a callback on the component instances (the core component plus any mixins).
      *
-     * @param reverse  if true, the callbacks are in the reverse of the normal order (this is associated
-     *                 with AfterXXX
-     *                 phases)
-     * @param callback the object to receive each component instance
+     * @param reverse
+     *         if true, the callbacks are in the reverse of the normal order (this is associated
+     *         with AfterXXX
+     *         phases)
+     * @param callback
+     *         the object to receive each component instance
      */
     private void invoke(boolean reverse, ComponentCallback callback)
     {

Modified: tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/InternalComponentResourcesImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/InternalComponentResourcesImpl.java?rev=1337454&r1=1337453&r2=1337454&view=diff
==============================================================================
--- tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/InternalComponentResourcesImpl.java (original)
+++ tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/InternalComponentResourcesImpl.java Sat May 12 06:53:16 2012
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008, 2009, 2010, 2011 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2009, 2010, 2011, 2012 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@ import org.apache.tapestry5.ioc.internal
 import org.apache.tapestry5.ioc.services.PerThreadValue;
 import org.apache.tapestry5.model.ComponentModel;
 import org.apache.tapestry5.runtime.Component;
-import org.apache.tapestry5.runtime.PageLifecycleAdapter;
+import org.apache.tapestry5.runtime.PageLifecycleCallbackHub;
 import org.apache.tapestry5.runtime.PageLifecycleListener;
 import org.apache.tapestry5.runtime.RenderQueue;
 import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
@@ -42,6 +42,8 @@ import java.lang.annotation.Annotation;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 /**
  * The bridge between a component and its {@link ComponentPageElement}, that supplies all kinds of
@@ -73,21 +75,35 @@ public class InternalComponentResourcesI
 
     private static final AnnotationProvider NULL_ANNOTATION_PROVIDER = new NullAnnotationProvider();
 
-    // Case insensitive map from parameter name to binding
+    // Map from parameter name to binding. This is mutable but not guarded by the lazy creation lock, as it is only
+    // written to during page load, not at runtime.
     private NamedSet<Binding> bindings;
 
-    // Case insensitive map from parameter name to ParameterConduit, used to support mixins
+    /**
+     * Lock, shared by all instances, on certain rare, runtime (not construction time) lazy creation, such
+     * as the creation of the {@link PerThreadValue}, to store render variables. It is possible that this lock could be converted
+     * into a static variable without affecting throughput since across all instances, reads vastly dominate writes.
+     */
+    private final ReadWriteLock lazyCreationLock = new ReentrantReadWriteLock();
+
+    // Maps from parameter name to ParameterConduit, used to support mixins
     // which need access to the containing component's PC's
+    // Guarded by this
     private NamedSet<ParameterConduit> conduits;
 
+    // Guarded by: lazyCreationLock
     private Messages messages;
 
+    // Guarded by: lazyCreationLock
     private boolean informalsComputed;
 
+    // Guarded by: lazyCreationLock
     private PerThreadValue<Map<String, Object>> renderVariables;
 
+    // Guarded by: lazyCreationLock
     private Informal firstInformal;
 
+
     /**
      * We keep a linked list of informal parameters, which saves us the expense of determining which
      * bindings are formal
@@ -137,14 +153,6 @@ public class InternalComponentResourcesI
         }
     };
 
-    private static Worker<ParameterConduit> LOAD_PARAMETER_CONDUIT = new Worker<ParameterConduit>()
-    {
-        public void work(ParameterConduit value)
-        {
-            value.load();
-        }
-    };
-
     public InternalComponentResourcesImpl(Page page, ComponentPageElement element,
                                           ComponentResources containerResources, ComponentPageElementResources elementResources, String completeId,
                                           String nestedId, Instantiator componentInstantiator, boolean mixin)
@@ -375,19 +383,48 @@ public class InternalComponentResourcesI
             i.write(writer);
     }
 
-    private synchronized Informal firstInformal()
+    private Informal firstInformal()
     {
-        if (!informalsComputed)
+        try
         {
-            for (Map.Entry<String, Binding> e : getInformalParameterBindings().entrySet())
+            lazyCreationLock.readLock().lock();
+
+            if (!informalsComputed)
             {
-                firstInformal = new Informal(e.getKey(), e.getValue(), firstInformal);
+                computeInformals();
             }
 
-            informalsComputed = true;
+            return firstInformal;
+        } finally
+        {
+            lazyCreationLock.readLock().unlock();
         }
+    }
+
+    private void computeInformals()
+    {
+        try
+        {
+            lazyCreationLock.readLock().unlock();
+            // Tiny window here where some other thread may compute informals!
+            lazyCreationLock.writeLock().lock();
+
+            if (!informalsComputed)
+            {
+                for (Map.Entry<String, Binding> e : getInformalParameterBindings().entrySet())
+                {
+                    firstInformal = new Informal(e.getKey(), e.getValue(), firstInformal);
+                }
 
-        return firstInformal;
+                informalsComputed = true;
+            }
+        } finally
+        {
+            // Downgrade back to read:
+            lazyCreationLock.readLock().lock();
+            lazyCreationLock.writeLock().unlock();
+
+        }
     }
 
     public Component getContainer()
@@ -418,12 +455,51 @@ public class InternalComponentResourcesI
         return element.getResourceSelector();
     }
 
-    public synchronized Messages getMessages()
+    public Messages getMessages()
     {
-        if (messages == null)
-            messages = elementResources.getMessages(componentModel);
+        try
+        {
+            lazyCreationLock.readLock().lock();
+
+            if (messages == null)
+            {
+                obtainComponentMessages();
+            }
+
+            return messages;
+        } finally
+        {
+            lazyCreationLock.readLock().unlock();
+        }
+    }
+
+    private void obtainComponentMessages()
+    {
+
+        boolean haveWriteLock = false;
+
+        try
+        {
+            lazyCreationLock.readLock().unlock();
+
+            // Do the expensive part here, with no locks!
+
+            Messages componentMessages = elementResources.getMessages(componentModel);
 
-        return messages;
+            lazyCreationLock.writeLock().lock();
+
+            haveWriteLock = true;
+
+            messages = componentMessages;
+        } finally
+        {
+            lazyCreationLock.readLock().lock();
+            if (haveWriteLock)
+            {
+                lazyCreationLock.writeLock().unlock();
+            }
+
+        }
     }
 
     public String getElementName(String defaultElementName)
@@ -471,22 +547,53 @@ public class InternalComponentResourcesI
         return result;
     }
 
-    private synchronized Map<String, Object> getRenderVariables(boolean create)
+    private Map<String, Object> getRenderVariables(boolean create)
     {
-        if (renderVariables == null)
+        try
         {
-            if (!create)
-                return null;
+            lazyCreationLock.readLock().lock();
+
+            if (renderVariables == null)
+            {
+                if (!create)
+                {
+                    return null;
+                }
+
+                createRenderVariablesPerThreadValue();
+            }
 
-            renderVariables = elementResources.createPerThreadValue();
+            Map<String, Object> result = renderVariables.get();
+
+            if (result == null && create)
+                result = renderVariables.set(CollectionFactory.newCaseInsensitiveMap());
+
+            return result;
+        } finally
+        {
+            lazyCreationLock.readLock().unlock();
         }
+    }
 
-        Map<String, Object> result = renderVariables.get();
+    private void createRenderVariablesPerThreadValue()
+    {
+        try
+        {
+            lazyCreationLock.readLock().unlock();
+            // There's a window right here where another thread may acquire the write lock and create the PTV
+            lazyCreationLock.writeLock().lock();
 
-        if (result == null && create)
-            result = renderVariables.set(CollectionFactory.newCaseInsensitiveMap());
+            if (renderVariables == null)
+            {
+                renderVariables = elementResources.createPerThreadValue();
+            }
 
-        return result;
+        } finally
+        {
+            // The thread with the write lock is allowed to downgrade to a read lock as so:
+            lazyCreationLock.readLock().lock();
+            lazyCreationLock.writeLock().unlock();
+        }
     }
 
     public Object getRenderVariable(String name)
@@ -537,8 +644,6 @@ public class InternalComponentResourcesI
         page.addResetListener(listener);
     }
 
-
-
     private synchronized void resetParameterConduits()
     {
         if (conduits != null)
@@ -547,15 +652,6 @@ public class InternalComponentResourcesI
         }
     }
 
-    private synchronized void loadParameterConduits()
-    {
-        // Don't need the conduits != null, because this method will only be invoked
-        // when conduits is non-null.
-
-        conduits.eachValue(LOAD_PARAMETER_CONDUIT);
-    }
-
-
     public synchronized ParameterConduit getParameterConduit(String parameterName)
     {
         return NamedSet.get(conduits, parameterName);
@@ -566,16 +662,6 @@ public class InternalComponentResourcesI
         if (conduits == null)
         {
             conduits = NamedSet.create();
-
-            page.addLifecycleListener(new PageLifecycleAdapter()
-            {
-                @Override
-                public void containingPageDidLoad()
-                {
-                    loadParameterConduits();
-                }
-            });
-
         }
 
         conduits.put(parameterName, conduit);
@@ -599,9 +685,17 @@ public class InternalComponentResourcesI
         return null;
     }
 
-    /** @since 5.3 */
+    /**
+     * @since 5.3
+     */
     public void render(MarkupWriter writer, RenderQueue queue)
     {
         queue.push(element);
     }
+
+    @Override
+    public PageLifecycleCallbackHub getPageLifecycleCallbackHub()
+    {
+        return page;
+    }
 }

Modified: tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/Page.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/Page.java?rev=1337454&r1=1337453&r2=1337454&view=diff
==============================================================================
--- tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/Page.java (original)
+++ tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/Page.java Sat May 12 06:53:16 2012
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008, 2009, 2010, 2011 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2009, 2010, 2011, 2012 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@ package org.apache.tapestry5.internal.st
 import org.apache.tapestry5.ComponentResources;
 import org.apache.tapestry5.ioc.services.PerthreadManager;
 import org.apache.tapestry5.runtime.Component;
+import org.apache.tapestry5.runtime.PageLifecycleCallbackHub;
 import org.apache.tapestry5.runtime.PageLifecycleListener;
 import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
 import org.slf4j.Logger;
@@ -29,16 +30,16 @@ import org.slf4j.Logger;
  * Starting in release 5.2, the nature of pages changed considerably. Pages are no longer pooled instances. Instead,
  * pages are shared instances (per locale) but all internal <em>mutable</em> state is stored inside
  * {@link PerthreadManager}. Page construction time is considered to extend past the
- * {@link PageLifecycleListener#containingPageDidLoad()} lifecycle notification. This is not quite perfect from a
+ * {@linkplain  PageLifecycleCallbackHub#addPageLoadedCallback(Runnable) page loaded callback}. This is not quite perfect from a
  * threading point-of-view (arguably, even write-once-read-many fields should be protected by synchronized blocks or
  * other mechanisms). At best, we can be assured that the entire page construction phase is protected by a single
  * synchronized block (but not on the page itself). An ideal system would build the page bottom to top so that all
  * assignments could take place in constructors, assigning to final fields. Maybe some day.
  * <p/>
- * The Page object is never visible to end-user code. The page also exists to provide a kind of service to components
- * embedded (directly or indirectly) within the page.
+ * The Page object is never visible to end-user code, though it exposes an interface ({@link PageLifecycleCallbackHub} that
+ * {@linkplain org.apache.tapestry5.ComponentResources#getPageLifecycleCallbackHub() is}).
  */
-public interface Page
+public interface Page extends PageLifecycleCallbackHub
 {
     /**
      * Page construction statistics for the page.
@@ -141,6 +142,9 @@ public interface Page
 
     /**
      * Adds a listener that is notified of large scale page events.
+     *
+     * @deprecated in 5.3.3; use {@link #addPageLoadedCallback(Runnable)}, {@link #addPageAttachedCallback(Runnable)}, or
+     *             {@link #addPageDetachedCallback(Runnable)}  instead
      */
     void addLifecycleListener(PageLifecycleListener listener);
 
@@ -148,6 +152,7 @@ public interface Page
      * Removes a listener that was previously added.
      *
      * @since 5.2.0
+     * @deprecated in 5.3.3, due to introduction of {@link #addPageLoadedCallback(Runnable)}
      */
     void removeLifecycleListener(PageLifecycleListener listener);
 
@@ -165,25 +170,31 @@ public interface Page
      * string) returns the root
      * element of the page.
      *
-     * @throws IllegalArgumentException if the nestedId does not correspond to a component
+     * @throws IllegalArgumentException
+     *         if the nestedId does not correspond to a component
      */
     ComponentPageElement getComponentElementByNestedId(String nestedId);
 
     /**
      * Posts a change to a persistent field.
      *
-     * @param resources the component resources for the component or mixin containing the field whose
-     *                  value changed
-     * @param fieldName the name of the field
-     * @param newValue  the new value for the field
+     * @param resources
+     *         the component resources for the component or mixin containing the field whose
+     *         value changed
+     * @param fieldName
+     *         the name of the field
+     * @param newValue
+     *         the new value for the field
      */
     void persistFieldChange(ComponentResources resources, String fieldName, Object newValue);
 
     /**
      * Gets a change for a field within the component.
      *
-     * @param nestedId  the nested component id of the component containing the field
-     * @param fieldName the name of the persistent field
+     * @param nestedId
+     *         the nested component id of the component containing the field
+     * @param fieldName
+     *         the name of the persistent field
      * @return the value, or null if no value is stored
      */
     Object getFieldChange(String nestedId, String fieldName);
@@ -199,23 +210,14 @@ public interface Page
     /**
      * Adds a new listener for page reset events.
      *
-     * @param listener will receive notifications when the page is accessed from a different page
+     * @param listener
+     *         will receive notifications when the page is accessed from a different page
      * @since 5.2.0
+     * @deprecated in 5.3.3,
      */
     void addResetListener(PageResetListener listener);
 
     /**
-     * Adds a verify callback, which is allowed while the page is loading. Such callbacks are invoked once,
-     * after the page has been loaded succesfully. This was added specifically to ensure that components
-     * only verify that required parameters are bound after all components and mixins of the page have had a chance
-     * to initialize.
-     *
-     * @param callback to be invoked after page loaded
-     * @since 5.3
-     */
-    void addVerifyListener(Runnable callback);
-
-    /**
      * Returns true if there are any {@link PageResetListener} listeners.
      *
      * @since 5.2.0
@@ -227,7 +229,6 @@ public interface Page
      */
     void pageReset();
 
-
     /**
      * Invoked once at the end of page construction, to provide page construction statistics.
      *

Modified: tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/PageImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/PageImpl.java?rev=1337454&r1=1337453&r2=1337454&view=diff
==============================================================================
--- tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/PageImpl.java (original)
+++ tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/PageImpl.java Sat May 12 06:53:16 2012
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008, 2009, 2010, 2011 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2009, 2010, 2011, 2012 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@ package org.apache.tapestry5.internal.st
 import org.apache.tapestry5.ComponentResources;
 import org.apache.tapestry5.internal.services.PersistentFieldManager;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.ioc.internal.util.OneShotLock;
 import org.apache.tapestry5.ioc.services.PerThreadValue;
 import org.apache.tapestry5.ioc.services.PerthreadManager;
@@ -41,13 +42,19 @@ public class PageImpl implements Page
 
     private ComponentPageElement rootElement;
 
-    private final List<PageLifecycleListener> lifecycleListeners = CollectionFactory.newThreadSafeList();
+    private List<Runnable> loadedCallbacks = CollectionFactory.newList();
 
-    private final List<PageResetListener> resetListeners = CollectionFactory.newList();
+    private final List<Runnable> attachCallbacks = CollectionFactory.newList();
+
+    private final List<Runnable> detachCallbacks = CollectionFactory.newList();
+
+    private final List<Runnable> resetCallbacks = CollectionFactory.newList();
 
     private boolean loadComplete;
 
-    private final OneShotLock lock = new OneShotLock();
+    private final OneShotLock lifecycleListenersLock = new OneShotLock();
+
+    private final OneShotLock verifyListenerLocks = new OneShotLock();
 
     private final Map<String, ComponentPageElement> idToComponent = CollectionFactory.newCaseInsensitiveMap();
 
@@ -67,10 +74,14 @@ public class PageImpl implements Page
     private static final Pattern SPLIT_ON_DOT = Pattern.compile("\\.");
 
     /**
-     * @param name                   canonicalized page name
-     * @param selector               used to locate resources
-     * @param persistentFieldManager for access to cross-request persistent values
-     * @param perThreadManager       for managing per-request mutable state
+     * @param name
+     *         canonicalized page name
+     * @param selector
+     *         used to locate resources
+     * @param persistentFieldManager
+     *         for access to cross-request persistent values
+     * @param perThreadManager
+     *         for managing per-request mutable state
      */
     public PageImpl(String name, ComponentResourceSelector selector, PersistentFieldManager persistentFieldManager,
                     PerthreadManager perThreadManager)
@@ -129,7 +140,7 @@ public class PageImpl implements Page
 
     public void setRootElement(ComponentPageElement component)
     {
-        lock.check();
+        lifecycleListenersLock.check();
 
         rootElement = component;
     }
@@ -144,33 +155,59 @@ public class PageImpl implements Page
         return rootElement.getComponent();
     }
 
-    public void addLifecycleListener(PageLifecycleListener listener)
+    public void addLifecycleListener(final PageLifecycleListener listener)
     {
-        lock.check();
+        assert listener != null;
 
-        lifecycleListeners.add(listener);
+        addPageLoadedCallback(new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                listener.containingPageDidLoad();
+            }
+        });
+
+        addPageAttachedCallback(new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                listener.containingPageDidAttach();
+            }
+        });
+
+        addPageDetachedCallback(new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                listener.containingPageDidDetach();
+            }
+        });
     }
 
     public void removeLifecycleListener(PageLifecycleListener listener)
     {
-        lock.check();
+        lifecycleListenersLock.check();
 
-        lifecycleListeners.remove(listener);
+        throw new UnsupportedOperationException("It is not longer possible to remove a page lifecycle listener; please convert your code to use the addPageLoadedCallback() method instead.");
     }
 
     public boolean detached()
     {
         boolean result = false;
 
-        for (PageLifecycleListener listener : lifecycleListeners)
+        for (Runnable callback : detachCallbacks)
         {
             try
             {
-                listener.containingPageDidDetach();
+                callback.run();
             } catch (RuntimeException ex)
             {
-                getLogger().error(StructureMessages.detachFailure(listener, ex), ex);
                 result = true;
+
+                getLogger().error(String.format("Callback %s failed during page detach: %s", callback, InternalUtils.toMessage(ex)), ex);
             }
         }
 
@@ -179,25 +216,15 @@ public class PageImpl implements Page
 
     public void loaded()
     {
-        lock.check();
-
-        for (PageLifecycleListener listener : lifecycleListeners)
-        {
-            listener.containingPageDidLoad();
-        }
+        lifecycleListenersLock.lock();
 
-        lock.lock();
+        invokeCallbacks(loadedCallbacks);
 
+        loadedCallbacks = null;
 
-        for (Runnable callback : pageVerifyCallbacks)
-        {
-            callback.run();
-        }
+        verifyListenerLocks.lock();
 
-        // These are never needed again, so we can get rid of them. The PageLifecycleListener interface is too complicated,
-        // we don't know what's needed when, and rely on the listeners themselves to unregister when no longer needed. A better design
-        // would be more like these pageVerifyCallbacks: just use Runnable and know when it is safe to throw them away. Something
-        // to refactor to over time.
+        invokeCallbacks(pageVerifyCallbacks);
 
         pageVerifyCallbacks = null;
 
@@ -208,11 +235,7 @@ public class PageImpl implements Page
     {
         attachCount.incrementAndGet();
 
-        for (PageLifecycleListener listener : lifecycleListeners)
-            listener.restoreStateBeforePageAttach();
-
-        for (PageLifecycleListener listener : lifecycleListeners)
-            listener.containingPageDidAttach();
+        invokeCallbacks(attachCallbacks);
     }
 
     public Logger getLogger()
@@ -246,38 +269,89 @@ public class PageImpl implements Page
         return name;
     }
 
-    public void addResetListener(PageResetListener listener)
+    @Override
+    public void addResetCallback(Runnable callback)
+    {
+        assert callback != null;
+
+        lifecycleListenersLock.check();
+
+        resetCallbacks.add(callback);
+    }
+
+    public void addResetListener(final PageResetListener listener)
     {
         assert listener != null;
-        lock.check();
 
-        resetListeners.add(listener);
+        addResetCallback(new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                listener.containingPageDidReset();
+            }
+        });
     }
 
-    public void addVerifyListener(Runnable callback)
+    public void addVerifyCallback(Runnable callback)
     {
-        assert callback != null;
+        verifyListenerLocks.check();
 
-        lock.check();
+        assert callback != null;
 
         pageVerifyCallbacks.add(callback);
     }
 
     public void pageReset()
     {
-        for (PageResetListener l : resetListeners)
-        {
-            l.containingPageDidReset();
-        }
+        invokeCallbacks(resetCallbacks);
     }
 
     public boolean hasResetListeners()
     {
-        return !resetListeners.isEmpty();
+        return !resetCallbacks.isEmpty();
     }
 
     public int getAttachCount()
     {
         return attachCount.get();
     }
+
+    @Override
+    public void addPageLoadedCallback(Runnable callback)
+    {
+        lifecycleListenersLock.check();
+
+        assert callback != null;
+
+        loadedCallbacks.add(callback);
+    }
+
+    @Override
+    public void addPageAttachedCallback(Runnable callback)
+    {
+        lifecycleListenersLock.check();
+
+        assert callback != null;
+
+        attachCallbacks.add(callback);
+    }
+
+    @Override
+    public void addPageDetachedCallback(Runnable callback)
+    {
+        lifecycleListenersLock.check();
+
+        assert callback != null;
+
+        detachCallbacks.add(callback);
+    }
+
+    private void invokeCallbacks(List<Runnable> callbacks)
+    {
+        for (Runnable callback : callbacks)
+        {
+            callback.run();
+        }
+    }
 }

Modified: tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/PageResetListener.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/PageResetListener.java?rev=1337454&r1=1337453&r2=1337454&view=diff
==============================================================================
--- tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/PageResetListener.java (original)
+++ tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/PageResetListener.java Sat May 12 06:53:16 2012
@@ -1,4 +1,4 @@
-// Copyright 2010 The Apache Software Foundation
+// Copyright 2010, 2012 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -21,6 +21,8 @@ import org.apache.tapestry5.annotations.
  * 
  * @since 5.2.0
  * @see PageReset
+ * @deprecated in 5.3.3
+ * @see org.apache.tapestry5.runtime.PageLifecycleCallbackHub#addResetCallback(Runnable)
  */
 public interface PageResetListener
 {

Modified: tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/StructureMessages.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/StructureMessages.java?rev=1337454&r1=1337453&r2=1337454&view=diff
==============================================================================
--- tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/StructureMessages.java (original)
+++ tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/StructureMessages.java Sat May 12 06:53:16 2012
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008, 2009, 2011 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2009, 2011, 2012 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -14,14 +14,14 @@
 
 package org.apache.tapestry5.internal.structure;
 
-import java.util.Collection;
-import java.util.List;
-
 import org.apache.tapestry5.ioc.Location;
 import org.apache.tapestry5.ioc.Messages;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.ioc.internal.util.MessagesImpl;
 
+import java.util.Collection;
+import java.util.List;
+
 final class StructureMessages
 {
     private static final Messages MESSAGES = MessagesImpl.forClass(StructureMessages.class);
@@ -41,11 +41,6 @@ final class StructureMessages
         return MESSAGES.format("unknown-mixin", componentId, mixinClassName);
     }
 
-    static String detachFailure(Object listener, Throwable cause)
-    {
-        return MESSAGES.format("detach-failure", listener, cause);
-    }
-
     static String wrongPhaseResultType(List<String> expectedTypes)
     {
         return MESSAGES.format("wrong-phase-result-type", InternalUtils.join(expectedTypes));

Modified: tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/InjectComponentWorker.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/InjectComponentWorker.java?rev=1337454&r1=1337453&r2=1337454&view=diff
==============================================================================
--- tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/InjectComponentWorker.java (original)
+++ tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/InjectComponentWorker.java Sat May 12 06:53:16 2012
@@ -1,4 +1,4 @@
-// Copyright 2008, 2010, 2011 The Apache Software Foundation
+// Copyright 2008, 2010, 2011, 2012 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -22,7 +22,6 @@ import org.apache.tapestry5.ioc.util.Unk
 import org.apache.tapestry5.model.MutableComponentModel;
 import org.apache.tapestry5.plastic.*;
 import org.apache.tapestry5.runtime.Component;
-import org.apache.tapestry5.runtime.PageLifecycleAdapter;
 import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
 import org.apache.tapestry5.services.transform.TransformationSupport;
 
@@ -36,11 +35,12 @@ public class InjectComponentWorker imple
     private final class InjectedComponentFieldValueConduit extends ReadOnlyComponentFieldConduit
     {
         private final ComponentResources resources;
+
         private final String fieldName, componentId, type;
 
         private Component embedded;
 
-        private InjectedComponentFieldValueConduit(final ComponentResources resources, String fieldName, String type,
+        private InjectedComponentFieldValueConduit(ComponentResources resources, String fieldName, String type,
                                                    String componentId)
         {
             super(resources, fieldName);
@@ -50,13 +50,12 @@ public class InjectComponentWorker imple
             this.componentId = componentId;
             this.type = type;
 
-            resources.addPageLifecycleListener(new PageLifecycleAdapter()
+            resources.getPageLifecycleCallbackHub().addPageAttachedCallback(new Runnable()
             {
-                public void containingPageDidLoad()
+                @Override
+                public void run()
                 {
                     load();
-
-                    resources.removePageLifecycleListener(this);
                 }
             });
         }

Modified: tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ParameterConduit.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ParameterConduit.java?rev=1337454&r1=1337453&r2=1337454&view=diff
==============================================================================
--- tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ParameterConduit.java (original)
+++ tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ParameterConduit.java Sat May 12 06:53:16 2012
@@ -1,4 +1,4 @@
-// Copyright 2010, 2011 The Apache Software Foundation
+// Copyright 2010, 2011, 2012 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -45,11 +45,4 @@ public interface ParameterConduit extend
      * Resets the conduit, clearing any <em>temporarily</em> cached data (from a non-invariant {@link Binding}).
      */
     void reset();
-
-    /**
-     * Invoked from the component's {@link org.apache.tapestry5.runtime.PageLifecycleListener#containingPageDidLoad()} lifecycle method, to
-     * finishing initializing
-     * the conduit prior to real use.
-     */
-    void load();
 }

Modified: tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ParameterWorker.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ParameterWorker.java?rev=1337454&r1=1337453&r2=1337454&view=diff
==============================================================================
--- tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ParameterWorker.java (original)
+++ tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ParameterWorker.java Sat May 12 06:53:16 2012
@@ -114,6 +114,7 @@ public class ParameterWorker implements 
     private void convertFieldIntoParameter(PlasticClass plasticClass, MutableComponentModel model,
                                            PlasticField field)
     {
+
         Parameter annotation = field.getAnnotation(Parameter.class);
 
         String fieldType = field.getTypeName();
@@ -195,6 +196,14 @@ public class ParameterWorker implements 
                         // shared with mixins.
 
                         icr.setParameterConduit(parameterName, this);
+                        icr.getPageLifecycleCallbackHub().addPageLoadedCallback(new Runnable()
+                        {
+                            @Override
+                            public void run()
+                            {
+                                load();
+                            }
+                        });
                     }
 
                     private ParameterState getState()

Modified: tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/util/NamedSet.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/util/NamedSet.java?rev=1337454&r1=1337453&r2=1337454&view=diff
==============================================================================
--- tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/util/NamedSet.java (original)
+++ tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/internal/util/NamedSet.java Sat May 12 06:53:16 2012
@@ -1,4 +1,4 @@
-// Copyright 2011 The Apache Software Foundation
+// Copyright 2011, 2012 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -15,12 +15,15 @@
 package org.apache.tapestry5.internal.util;
 
 import org.apache.commons.collections.map.CaseInsensitiveMap;
+import org.apache.tapestry5.func.F;
 import org.apache.tapestry5.func.Worker;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 
 import java.util.Collections;
 import java.util.Set;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 /**
  * Simple, thread-safe associative array that relates a name to a value. Names are case-insensitive.
@@ -28,13 +31,15 @@ import java.util.Set;
  * though the cost of a lookup is more expensive. However, this is a good match against many of the structures inside
  * a page instance, where most lookups occur only during page constructions, and the number of values is often small.
  * <p/>
- * We use simple synchronization, as uncontested synchronized locks are very, very cheap.
+ * Each NameSet has its own {@link ReadWriteLock}.
  *
  * @param <T>
  *         the type of value stored
  */
 public class NamedSet<T>
 {
+    private final ReadWriteLock lock = new ReentrantReadWriteLock();
+
     private NamedRef<T> first;
 
     private static class NamedRef<T>
@@ -55,37 +60,53 @@ public class NamedSet<T>
     /**
      * Returns a set of the names of all stored values.
      */
-    public synchronized Set<String> getNames()
+    public Set<String> getNames()
     {
-        Set<String> result = CollectionFactory.newSet();
+        try
+        {
+            lock.readLock().lock();
+
+            Set<String> result = CollectionFactory.newSet();
 
-        NamedRef<T> cursor = first;
+            NamedRef<T> cursor = first;
+
+            while (cursor != null)
+            {
+                result.add(cursor.name);
+                cursor = cursor.next;
+            }
 
-        while (cursor != null)
+            return result;
+        } finally
         {
-            result.add(cursor.name);
-            cursor = cursor.next;
+            lock.readLock().unlock();
         }
-
-        return result;
     }
 
     /**
      * Returns a set of all the values in the set.
      */
-    public synchronized Set<T> getValues()
+    public Set<T> getValues()
     {
         Set<T> result = CollectionFactory.newSet();
 
-        NamedRef<T> cursor = first;
+        try
+        {
+            lock.readLock().lock();
+
+            NamedRef<T> cursor = first;
+
+            while (cursor != null)
+            {
+                result.add(cursor.value);
+                cursor = cursor.next;
+            }
 
-        while (cursor != null)
+            return result;
+        } finally
         {
-            result.add(cursor.value);
-            cursor = cursor.next;
+            lock.readLock().unlock();
         }
-
-        return result;
     }
 
     /**
@@ -95,21 +116,29 @@ public class NamedSet<T>
      *         used to locate the value
      * @return the value, or null if not found
      */
-    public synchronized T get(String name)
+    public T get(String name)
     {
-        NamedRef<T> cursor = first;
-
-        while (cursor != null)
+        try
         {
-            if (cursor.name.equalsIgnoreCase(name))
+            lock.readLock().lock();
+
+            NamedRef<T> cursor = first;
+
+            while (cursor != null)
             {
-                return cursor.value;
+                if (cursor.name.equalsIgnoreCase(name))
+                {
+                    return cursor.value;
+                }
+
+                cursor = cursor.next;
             }
 
-            cursor = cursor.next;
+            return null;
+        } finally
+        {
+            lock.readLock().unlock();
         }
-
-        return null;
     }
 
     /**
@@ -121,37 +150,45 @@ public class NamedSet<T>
      * @param newValue
      *         non-null value to store
      */
-    public synchronized void put(String name, T newValue)
+    public void put(String name, T newValue)
     {
         assert InternalUtils.isNonBlank(name);
         assert newValue != null;
 
-        NamedRef<T> prev = null;
-        NamedRef<T> cursor = first;
-
-        while (cursor != null)
+        try
         {
-            if (cursor.name.equalsIgnoreCase(name))
+            lock.writeLock().lock();
+
+            NamedRef<T> prev = null;
+            NamedRef<T> cursor = first;
+
+            while (cursor != null)
             {
-                // Retain the case of the name as put(), even if it doesn't match
-                // the existing case
+                if (cursor.name.equalsIgnoreCase(name))
+                {
+                    // Retain the case of the name as put(), even if it doesn't match
+                    // the existing case
 
-                cursor.name = name;
-                cursor.value = newValue;
+                    cursor.name = name;
+                    cursor.value = newValue;
 
-                return;
-            }
+                    return;
+                }
 
-            prev = cursor;
-            cursor = cursor.next;
-        }
+                prev = cursor;
+                cursor = cursor.next;
+            }
 
-        NamedRef<T> newRef = new NamedRef<T>(name, newValue);
+            NamedRef<T> newRef = new NamedRef<T>(name, newValue);
 
-        if (prev == null)
-            first = newRef;
-        else
-            prev.next = newRef;
+            if (prev == null)
+                first = newRef;
+            else
+                prev.next = newRef;
+        } finally
+        {
+            lock.writeLock().unlock();
+        }
     }
 
     /**
@@ -160,17 +197,9 @@ public class NamedSet<T>
      * @param worker
      *         performs an operation on, or using, the value
      */
-    public synchronized void eachValue(Worker<T> worker)
+    public void eachValue(Worker<T> worker)
     {
-        assert worker != null;
-
-        NamedRef<T> cursor = first;
-
-        while (cursor != null)
-        {
-            worker.work(cursor.value);
-            cursor = cursor.next;
-        }
+        F.flow(getValues()).each(worker);
     }
 
 
@@ -183,37 +212,46 @@ public class NamedSet<T>
      *         non-null value to store
      * @return true if value stored, false if name already exists
      */
-    public synchronized boolean putIfNew(String name, T newValue)
+    public boolean putIfNew(String name, T newValue)
     {
         assert InternalUtils.isNonBlank(name);
         assert newValue != null;
 
-        NamedRef<T> prev = null;
-        NamedRef<T> cursor = first;
-
-        while (cursor != null)
+        try
         {
-            if (cursor.name.equalsIgnoreCase(name))
+
+            lock.writeLock().lock();
+
+            NamedRef<T> prev = null;
+            NamedRef<T> cursor = first;
+
+            while (cursor != null)
             {
-                return false;
-            }
+                if (cursor.name.equalsIgnoreCase(name))
+                {
+                    return false;
+                }
 
-            prev = cursor;
-            cursor = cursor.next;
-        }
+                prev = cursor;
+                cursor = cursor.next;
+            }
 
-        NamedRef<T> newRef = new NamedRef<T>(name, newValue);
+            NamedRef<T> newRef = new NamedRef<T>(name, newValue);
 
-        if (prev == null)
-            first = newRef;
-        else
-            prev.next = newRef;
+            if (prev == null)
+                first = newRef;
+            else
+                prev.next = newRef;
 
-        return true;
+            return true;
+        } finally
+        {
+            lock.writeLock().unlock();
+        }
     }
 
     /**
-     * Convienience method for creating a new, empty set.
+     * Convenience method for creating a new, empty set.
      */
     public static <T> NamedSet<T> create()
     {
@@ -221,7 +259,7 @@ public class NamedSet<T>
     }
 
     /**
-     * Convienience method for getting a value from a set that may be null.
+     * Convenience method for getting a value from a set that may be null.
      *
      * @param <T>
      * @param set
@@ -241,7 +279,9 @@ public class NamedSet<T>
     public static Set<String> getNames(NamedSet<?> set)
     {
         if (set == null)
+        {
             return Collections.emptySet();
+        }
 
         return set.getNames();
     }
@@ -252,7 +292,9 @@ public class NamedSet<T>
     public static <T> Set<T> getValues(NamedSet<T> set)
     {
         if (set == null)
+        {
             return Collections.emptySet();
+        }
 
         return set.getValues();
     }

Modified: tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/runtime/PageLifecycleAdapter.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/runtime/PageLifecycleAdapter.java?rev=1337454&r1=1337453&r2=1337454&view=diff
==============================================================================
--- tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/runtime/PageLifecycleAdapter.java (original)
+++ tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/runtime/PageLifecycleAdapter.java Sat May 12 06:53:16 2012
@@ -1,4 +1,4 @@
-// Copyright 2010 The Apache Software Foundation
+// Copyright 2010, 2012 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@ package org.apache.tapestry5.runtime;
 
 /**
  * Empty implementation of the {@link PageLifecycleListener} interface.
+ *
+ * @deprecated in 5.3.3, as {@link PageLifecycleListener} has been deprecated
  */
 public class PageLifecycleAdapter implements PageLifecycleListener
 {

Added: tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/runtime/PageLifecycleCallbackHub.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/runtime/PageLifecycleCallbackHub.java?rev=1337454&view=auto
==============================================================================
--- tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/runtime/PageLifecycleCallbackHub.java (added)
+++ tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/runtime/PageLifecycleCallbackHub.java Sat May 12 06:53:16 2012
@@ -0,0 +1,70 @@
+// Copyright 2012 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.runtime;
+
+/**
+ * Defines a way for different aspects of a page to add callbacks for important lifecycle events.
+ *
+ * @see org.apache.tapestry5.ComponentResources#getPageLifecycleCallbackHub()
+ * @since 5.3.3
+ */
+public interface PageLifecycleCallbackHub
+{
+    /**
+     * Adds a callback for when the page is first loaded.  Callbacks are invoked in the order they
+     * are added to the page. They are invoked once and then discarded.
+     *
+     * @param callback
+     *         invoked once, when page is first loaded
+     * @since 5.3.3
+     */
+    void addPageLoadedCallback(Runnable callback);
+
+    /**
+     * Adds a callback for when the page is attached to the request.
+     *
+     * @param callback
+     * @since 5.3.3
+     */
+    void addPageAttachedCallback(Runnable callback);
+
+    /**
+     * Adds a callback for when the page is detached from the request.
+     *
+     * @param callback
+     * @since 5.3.3
+     */
+    void addPageDetachedCallback(Runnable callback);
+
+    /**
+     * Adds a verify callback, which is allowed while the page is loading. Such callbacks are invoked once,
+     * after the page has been loaded successfully, and are then discarded. This was added specifically to ensure that components
+     * only verify that required parameters are bound after all components and mixins of the page have had a chance
+     * to initialize.
+     *
+     * @param callback
+     *         to be invoked after page loaded
+     * @since 5.3
+     */
+    void addVerifyCallback(Runnable callback);
+
+    /**
+     * A reset occurs when a request for a page arrives that did not originate on the same page. This gives the application a chance to reset the state of the page.
+     *
+     * @param callback
+     *         invoked when a page is activated by link from some other page.
+     */
+    void addResetCallback(Runnable callback);
+}

Modified: tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/runtime/PageLifecycleListener.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/runtime/PageLifecycleListener.java?rev=1337454&r1=1337453&r2=1337454&view=diff
==============================================================================
--- tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/runtime/PageLifecycleListener.java (original)
+++ tapestry/tapestry5/branches/5.3/tapestry-core/src/main/java/org/apache/tapestry5/runtime/PageLifecycleListener.java Sat May 12 06:53:16 2012
@@ -1,4 +1,4 @@
-// Copyright 2006, 2009, 2011 The Apache Software Foundation
+// Copyright 2006, 2009, 2011, 2012 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -18,12 +18,16 @@ package org.apache.tapestry5.runtime;
  * A set of methods that allow components to know about page-level operations. When this interface is
  * {@linkplain org.apache.tapestry5.plastic.PlasticClass#introduceInterface(Class)} introduced}, the component will
  * automatically register itself as a listener with the page.
+ *
+ * @deprecated in 5.3.3, replaced with {@link PageLifecycleCallbackHub}
  */
 public interface PageLifecycleListener
 {
     /**
      * Invoked when the page finishes loading. This occurs once all components are loaded and all parameters have been
      * set.
+     *
+     * @deprecated in 5.3.3,  use {@link org.apache.tapestry5.ComponentResources#addPageLoadedCallback(Runnable)} instead
      */
     void containingPageDidLoad();
 

Modified: tapestry/tapestry5/branches/5.3/tapestry-core/src/main/resources/org/apache/tapestry5/internal/structure/StructureStrings.properties
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/branches/5.3/tapestry-core/src/main/resources/org/apache/tapestry5/internal/structure/StructureStrings.properties?rev=1337454&r1=1337453&r2=1337454&view=diff
==============================================================================
--- tapestry/tapestry5/branches/5.3/tapestry-core/src/main/resources/org/apache/tapestry5/internal/structure/StructureStrings.properties (original)
+++ tapestry/tapestry5/branches/5.3/tapestry-core/src/main/resources/org/apache/tapestry5/internal/structure/StructureStrings.properties Sat May 12 06:53:16 2012
@@ -1,4 +1,4 @@
-# Copyright 2006, 2007, 2008, 2009, 2010, 2011 The Apache Software Foundation
+# Copyright 2006, 2007, 2008, 2009, 2010, 2011, 2012 The Apache Software Foundation
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -14,7 +14,6 @@
 
 missing-parameters=Parameter(s) '%s' are required for %s, but have not been bound.
 unknown-mixin=Component %s does not contain a mixin of type %s.
-detach-failure=Listener %s failed during page detach: %s
 wrong-phase-result-type=The return value from a render phase event method was not compatible with the expected return type. Expected is a component, a block or an instance of %s. \
   You should change the method to return the correct type. 
 block-not-found=Template for component %s does not contain a block with identifier '%s'.

Modified: tapestry/tapestry5/branches/5.3/tapestry-core/src/test/java/org/apache/tapestry5/internal/structure/PageImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/branches/5.3/tapestry-core/src/test/java/org/apache/tapestry5/internal/structure/PageImplTest.java?rev=1337454&r1=1337453&r2=1337454&view=diff
==============================================================================
--- tapestry/tapestry5/branches/5.3/tapestry-core/src/test/java/org/apache/tapestry5/internal/structure/PageImplTest.java (original)
+++ tapestry/tapestry5/branches/5.3/tapestry-core/src/test/java/org/apache/tapestry5/internal/structure/PageImplTest.java Sat May 12 06:53:16 2012
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008, 2009, 2010, 2011 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2009, 2010, 2011, 2012 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -18,9 +18,6 @@ import org.apache.tapestry5.internal.tes
 import org.apache.tapestry5.ioc.services.PerthreadManager;
 import org.apache.tapestry5.runtime.PageLifecycleListener;
 import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
-
-import static org.easymock.EasyMock.contains;
-import static org.easymock.EasyMock.same;
 import org.slf4j.Logger;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
@@ -28,6 +25,9 @@ import org.testng.annotations.Test;
 
 import java.util.Locale;
 
+import static org.easymock.EasyMock.contains;
+import static org.easymock.EasyMock.same;
+
 public class PageImplTest extends InternalBaseTestCase
 {
     private final ComponentResourceSelector selector = new ComponentResourceSelector(Locale.ENGLISH);
@@ -135,9 +135,6 @@ public class PageImplTest extends Intern
         PageLifecycleListener listener1 = newPageLifecycle();
         PageLifecycleListener listener2 = newPageLifecycle();
 
-        listener1.restoreStateBeforePageAttach();
-        listener2.restoreStateBeforePageAttach();
-
         listener1.containingPageDidAttach();
         listener2.containingPageDidAttach();