Author: buildbot Date: Sat Jun 17 21:11:47 2023 New Revision: 1083433 Log: Production update by buildbot for tapestry
Modified: websites/production/tapestry/content/cache/main.pageCache websites/production/tapestry/content/class-reloading.html Modified: websites/production/tapestry/content/cache/main.pageCache ============================================================================== Binary files - no diff available. Modified: websites/production/tapestry/content/class-reloading.html ============================================================================== --- websites/production/tapestry/content/class-reloading.html (original) +++ websites/production/tapestry/content/class-reloading.html Sat Jun 17 21:11:47 2023 @@ -178,11 +178,11 @@ <p>One of the best features of Tapestry is automatic reloading of changed classes and templates. <em>Page and component</em> classes will automatically reload when changed. Likewise, changes to component templates and other related resources will also be picked up immediately. In addition, starting in version 5.2, your service classes will also be reloaded automatically after changes (if you're using <a href="ioc.html">Tapestry IoC</a>). Starting in version 5.8.3, you enable multiple classloader mode, which allows smarter page class invalidation.</p><div class="confluence-information-macro confluence-information-macro-information"><p class="title">Not necessarily throwing away all cached page instances</p><span class="aui-icon aui-icon-small aui-iconfont-info confluence-information-macro-icon"></span><div class="confluence-information-macro-body"><p>Since Tapestry 5.8.3, Tapestry can be run in multiple classloaders mode. When it's on, only the affected cached page instances are disc arded and rebuilt instead of all of them. </p></div></div><h2 id="ClassReloading-Contents">Contents</h2><p><style type="text/css">/*<![CDATA[*/ -div.rbtoc1686960702841 {padding: 0px;} -div.rbtoc1686960702841 ul {margin-left: 0px;} -div.rbtoc1686960702841 li {margin-left: 0px;padding-left: 0px;} +div.rbtoc1687036302863 {padding: 0px;} +div.rbtoc1687036302863 ul {margin-left: 0px;} +div.rbtoc1687036302863 li {margin-left: 0px;padding-left: 0px;} -/*]]>*/</style></p><div class="toc-macro rbtoc1686960702841"> +/*]]>*/</style></p><div class="toc-macro rbtoc1687036302863"> <ul class="toc-indentation"><li><a href="#ClassReloading-TemplateReloading">Template Reloading</a></li><li><a href="#ClassReloading-ClassReloading">Class Reloading</a></li><li><a href="#ClassReloading-PackagesScanned">Packages Scanned</a></li><li><a href="#ClassReloading-FileSystemOnly">File System Only</a></li><li><a href="#ClassReloading-ClassLoaderIssues">Class Loader Issues</a></li><li><a href="#ClassReloading-ClassCastExceptions">ClassCastExceptions</a></li><li><a href="#ClassReloading-HandlingReloadsinyourCode">Handling Reloads in your Code</a></li><li><a href="#ClassReloading-CheckingForUpdates">Checking For Updates</a></li><li><a href="#ClassReloading-TroubleshootingLiveClassReloading">Troubleshooting Live Class Reloading</a> <ul class="toc-indentation"><li><a href="#ClassReloading-QuickChecklist">Quick Checklist</a></li><li><a href="#ClassReloading-IfLiveClassReloadingdoesn'twork">If Live Class Reloading doesn't work</a> <ul class="toc-indentation"><li><a href="#ClassReloading-ProductionMode">Production Mode</a></li><li><a href="#ClassReloading-BuildPathIssues">Build Path Issues</a></li><li><a href="#ClassReloading-BuildingAutomatically">Building Automatically</a></li><li><a href="#ClassReloading-TurnoffJVMhotcodeswapping&automaticrestarts">Turn off JVM hot code swapping & automatic restarts</a></li></ul> @@ -211,7 +211,7 @@ div.rbtoc1686960702841 li {margin-left: </div></div><p>This is the intent of service builder methods; to do more than just injecting dependencies.</p><h2 id="ClassReloading-CheckingForUpdates">Checking For Updates</h2><p>The built in InvalidationEventHub services provide notifications of changes to component classes, to component templates, and to component message catalogs. If you wish to check some other resources (for example, files in a directory of the file system or rows in a database table), you should register as an <a class="external-link" href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/services/UpdateListener.html">UpdateListener</a> with the <a class="external-link" href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/services/UpdateListenerHub.html">UpdateListenerHub</a> service.</p><p>Periodically (the frequency is configurable), UpdateListeners are notified that they should check for updates. Typically, UpdateListeners are also InvalidationEventHubs (or provide Invalida tionEventHubs), so that other interested parties can be alerted when underlying data changes.</p><h2 id="ClassReloading-TroubleshootingLiveClassReloading">Troubleshooting Live Class Reloading</h2><h3 id="ClassReloading-QuickChecklist">Quick Checklist</h3><ul><li>"Production Mode" must be false (in Tapestry 5.3 and later)</li><li>The class must be one that Tapestry instantiates (a page, component, or mixin class, or a Tapestry IOC service implementation that implements an interface)</li><li>Turn on "Build Automatically" in your IDE, or remember to build manually.</li><li>Turn <em>off</em> JVM hot code swapping, if your servlet container supports it.</li><li>Eclipse: Uncheck the "derived" checkbox for the Target dir (in the Project Explorer view, right click on "target", select properties, uncheck "derived" on the Resource tab)</li></ul><p>Some of these issues are described in more detail below.</p><h3 id="ClassReloading-IfLiveClassReloadingdoesn'twork">If Live Class Reloading doesn't work</h3><h4 id="ClassReloading-ProductionMode">Production Mode</h4><p>Starting with Tapestry 5.3, Live Class Reloading only works when not in "Production Mode". Check your application module (usually AppModule.java) to be sure you have:</p><div class="code panel pdl" style="border-width: 1px;"><div class="codeContent panelContent pdl"> <pre><code class="language-java">configuration.add(SymbolConstants.PRODUCTION_MODE, "false"); </code></pre> -</div></div><p>and that this isn't being overridden to "true" on your application's startup command line.</p><h4 id="ClassReloading-BuildPathIssues">Build Path Issues</h4><p>Live Class Reloading can fail if your build path isn't set correctly, and the exact configuration may differ between Maven plugin versions and Eclipse versions. The build process must be set to create classes in a folder which is in the servlet container's classpath.</p><p>Live Class Reloading won't work correctly with vanilla Tomcat without some tweaks (see below).</p><p>Non-Tapestry filters can interfere with LCR. Try disabling other filters in your web.xml file to see if that helps.</p><h4 id="ClassReloading-BuildingAutomatically">Building Automatically</h4><p>Although LCR allows you to see changes without restarting your app, you still need to "build" your project (to compile the Java source into byte code). Your IDE can be set to do this automatically every time you save a file. (In Eclipse, this is done us ing <code>Project > Build Automatically</code>.) Alternatively, you can manually trigger a build after you save a file. (In Eclipse, this is done using <code>Project > Build</code>, or by pressing <code>Control-B</code>.)</p><h4 id="ClassReloading-TurnoffJVMhotcodeswapping&automaticrestarts">Turn off JVM hot code swapping & automatic restarts</h4><p>Many servlet containers, including Tomcat and Jetty, support various forms of hot code swapping and/or automatic restarts when file changes are detected. These are generally <strong>much slower</strong> than LCR and usually should be turned off with Tapestry applications. If you're using RunJettyRun plugin for Eclipse, for example, edit your Run Configuration, and on the Jetty tab, click Show Advanced Options and uncheck the Enable Scanner checkbox.</p><h3 id="ClassReloading-TomcatSpecifics">Tomcat Specifics</h3><p>See <a class="external-link" href="http://www.tynamo.org/Developing+with+Tomcat+and+Eclipse/" rel="nofollow">t hese Tomcat-specific hints</a></p><h3 id="ClassReloading-IfLiveClassReloadingworksbutisslow">If Live Class Reloading works but is slow</h3><p>If LCR works for you but is slow (more than a second or two), consider the following.</p><ul><li>Be sure your project source files (your workspace in Eclipse, for example), are on a local drive, NOT a network location. Network drives are always slower, and the file system scanning needed for LCR can add a noticable lag if I/O is slow. If you use Maven, be sure to put your local repository (e.g. ~/.m2/repository) on a local drive for similar reasons.</li><li><p>Java 7 and below: Since LCR adds classes to your PermGen space, you may be running low on PermGen memory (and may eventually get a "java.lang.OutOfMemoryError: PermGen space" error). Try increasing PermGen size with a JVM argument of something like -XX:MaxPermSize=400m. (PermGen settings are not relevant for Java 8 and above.)</p></li></ul><h2 id="ClassReloading-[5.8.3+]MultipleClas sloaderMode/SmarterPageClassInvalidation">[5.8.3+] Multiple Classloader Mode/Smarter Page Class Invalidation</h2><p>Since Tapestry 5.8.3, you can run Tapestry in multiple classloader mode, which implements smarter page class invalidation in live class reloading. In addition, other caches are also invalidated in smarter way when something changes, avoiding throwing away everything. Multiple classloader mode isn't available when production mode is on.</p><p>To enable multiple classloader mode, you need to <code>tapestry.multiple-classloaders</code> symbol to <code>true</code> and run the webapp with production mode disabled.</p><p>When multiple classloaders are enabled, when a class in a controlled component, template, message properties file or asset is changed, Tapestry tries to invalidate as few cached objects as possible. This include page instances, processed templates, asset information, property bindings, etc. This is possible by 2 new internal features:</p><ol><li>A component dependency registry, which gathers and stores information about how component, page, mixin and base classes depend on each other. There are 3 kinds of dependencies: usage (i.e. a component or mixin used in a class), superclass (i.e. one class extending the other) and @InjectPage (i.e. one class injecting a page class using @InjectPage.</li><li>The usage of multiple classloader instances, organized in a tree, instead of a single one. For example: if a component A is used in pages B and C, when B has its class changed, there's no need to invalidate a cached instance of C since Tapestry knows there's no dependency of B and C. On the other hand, if A is changed, both B and C instances need to be invalidated.</li></ol><p>You can find a graph of all known dependencies at a time by going to the /t5dashboard/pagedependencygraph URL of your webapp. You can view the dependencies for one specific page by going to /t5dashboard/ and clicking the page's Structure Info link. Here's one partial exa mple:</p><p><span class="confluence-embedded-file-wrapper confluence-embedded-manual-size"><img class="confluence-embedded-image" draggable="false" height="250" src="class-reloading.data/image-2023-6-16_19-53-38.png"></span></p><h3 id="ClassReloading-HowItWorks">How It Works</h3><p>First of all, when production mode is off and multiple classloaders mode is also off, Tapestry reacts to file (including classes in controlled packages, templates, message properties files and assets) changes in the same way as before and described above in this page: all cached information or objects about classes, templates, message properties files, assets, property bindings, etc is invalidated (i.e. thrown away). The internal services that track changes to these files notify all their listeners that they should invalidate their caches completely.</p><p>When multiple classloaders mode is on, these internal services notify their listeners what changed, so they can invalidate just the objects or informat ion that is actually affected by the changes. Sometimes this listeners notify the services that more stuff needs to be invalidated, For example, if one asset is changed and it's associated with a class, the asset service notifies the listeners that that class needs to be invalidated. For example, the component dependency registry, when notified that a given class was invalidated, besides invalidating information about that class, it informs the service that it also needs to invalidates the classes that depend on the changed one. This process repeats until there's no other class to be invalidated.</p><p>Additional logging was added so all files found as changed result in log entries. Same for the invalidation chain described in the paragraph above.</p><p>Now that it knows the class dependency graph, Tapestry needs to map that into a classloader tree, since each classloader instance can only have one parent. Internally, the framework has one class, PageClassLoaderContext (PCLC or cont ext, for short), that stores all information about one classloader in the tree: the classloader itself, a name, the set of names of the classes beloging to it, the parent PCLC and children PCLCs. When a class is invalidated, the classes in the context are invalidated, then the context itself is invalidated, including its classloader, then the child contexts are recursively invalidated too.</p><p>There are 2 special PCLC contexts: the root one, which is never invalidated, and the unknown one, where classes without dependency information go initially.</p><p>The PCLC tree is created class by class, starting from pages and then on its dependencies on them in a recursive manner. For each class, if there's no PCLC already containing it, Tapestry checks its dependency list. </p><h3 id="ClassReloading-Loading,StoringandPreloadingDependencyInformation">Loading, Storing and Preloading Dependency Information</h3><p>When a page instance is finished building, it's processed by the component dependency registry, which gathers the dependencies this page has, then the dependencies of its dependencies recursively. The registry doesn't process classes it already has information.</p><p>When starting up, Tapestry checks whether a file named <code>tapestryComponentDependencies.json</code> exists in the folder where the webapp is running. If it does, component dependency is loaded from it.</p><p>The T5Dashboard page has 2 new buttons related to dependencies, <code>Store dependency information</code>, writes all the dependency information as known at that moment.</p><p><code>Preload dependency information and page classloader contexts</code> goes through all known pages, gathers its dependencies, also doing the same for all the components used directly and indirectly by them, and the preloads page the page classloader contexts, thus avoiding context invalidations when page instances are created. Notice this process won't work if any of the pages or their templates are invalid f or any reason.</p><h3 id="ClassReloading-Caveats">Caveats</h3><p>Unfortunately, it wasn't possible to make the multiple classloader mode to work with all situations and code. Some known issues:</p><ol><li>On Java 9 and later, classes belonging to one classloader cannot call package-private methods of other classes even when both are in the same package. The solution is to change the method visibility to public.</li><li>If you don't have component dependency information already loaded for a given class, problems, usually in the form of a ClassCastException claiming an object of a given type cannot be assigned to a variable of the same type. The best way to deal with this is to use the T5Dashboard to write the component dependency information to a file and restart the webapp. In some cases, it helps to preload dependency information for all classes.</li><li>If you start your webapp without component dependency information, page instances may be created and invalidated a few times even when they didn't have any changes due to the page classloader context creation. The solution for this is the same one as the above.</li></ol><p></p></div> +</div></div><p>and that this isn't being overridden to "true" on your application's startup command line.</p><h4 id="ClassReloading-BuildPathIssues">Build Path Issues</h4><p>Live Class Reloading can fail if your build path isn't set correctly, and the exact configuration may differ between Maven plugin versions and Eclipse versions. The build process must be set to create classes in a folder which is in the servlet container's classpath.</p><p>Live Class Reloading won't work correctly with vanilla Tomcat without some tweaks (see below).</p><p>Non-Tapestry filters can interfere with LCR. Try disabling other filters in your web.xml file to see if that helps.</p><h4 id="ClassReloading-BuildingAutomatically">Building Automatically</h4><p>Although LCR allows you to see changes without restarting your app, you still need to "build" your project (to compile the Java source into byte code). Your IDE can be set to do this automatically every time you save a file. (In Eclipse, this is done us ing <code>Project > Build Automatically</code>.) Alternatively, you can manually trigger a build after you save a file. (In Eclipse, this is done using <code>Project > Build</code>, or by pressing <code>Control-B</code>.)</p><h4 id="ClassReloading-TurnoffJVMhotcodeswapping&automaticrestarts">Turn off JVM hot code swapping & automatic restarts</h4><p>Many servlet containers, including Tomcat and Jetty, support various forms of hot code swapping and/or automatic restarts when file changes are detected. These are generally <strong>much slower</strong> than LCR and usually should be turned off with Tapestry applications. If you're using RunJettyRun plugin for Eclipse, for example, edit your Run Configuration, and on the Jetty tab, click Show Advanced Options and uncheck the Enable Scanner checkbox.</p><h3 id="ClassReloading-TomcatSpecifics">Tomcat Specifics</h3><p>See <a class="external-link" href="http://www.tynamo.org/Developing+with+Tomcat+and+Eclipse/" rel="nofollow">t hese Tomcat-specific hints</a></p><h3 id="ClassReloading-IfLiveClassReloadingworksbutisslow">If Live Class Reloading works but is slow</h3><p>If LCR works for you but is slow (more than a second or two), consider the following.</p><ul><li>Be sure your project source files (your workspace in Eclipse, for example), are on a local drive, NOT a network location. Network drives are always slower, and the file system scanning needed for LCR can add a noticable lag if I/O is slow. If you use Maven, be sure to put your local repository (e.g. ~/.m2/repository) on a local drive for similar reasons.</li><li><p>Java 7 and below: Since LCR adds classes to your PermGen space, you may be running low on PermGen memory (and may eventually get a "java.lang.OutOfMemoryError: PermGen space" error). Try increasing PermGen size with a JVM argument of something like -XX:MaxPermSize=400m. (PermGen settings are not relevant for Java 8 and above.)</p></li></ul><h2 id="ClassReloading-[5.8.3+]MultipleClas sloaderMode/SmarterPageClassInvalidation">[5.8.3+] Multiple Classloader Mode/Smarter Page Class Invalidation</h2><p>Since Tapestry 5.8.3, you can run Tapestry in multiple classloader mode, which implements smarter page class invalidation in live class reloading. In addition, other caches are also invalidated in smarter way when something changes, avoiding throwing away everything. Multiple classloader mode isn't available when production mode is on.</p><p>To enable multiple classloader mode, you need to <code>tapestry.multiple-classloaders</code> symbol to <code>true</code> and run the webapp with production mode disabled. With production mode on, <code>tapestry.multiple-classloaders</code> is ignored.</p><p>When multiple classloaders are enabled, when a class in a controlled component, template, message properties file or asset is changed, Tapestry tries to invalidate as few cached objects as possible. This include page instances, processed templates, asset information, property bi ndings, etc. This is possible by 2 new internal features:</p><ol><li>A component dependency registry, which gathers and stores information about how component, page, mixin and base classes depend on each other. There are 3 kinds of dependencies: usage (i.e. a component or mixin used in a class), superclass (i.e. one class extending the other) and @InjectPage (i.e. one class injecting a page class using @InjectPage.</li><li>The usage of multiple classloader instances, organized in a tree, instead of a single one. For example: if a component A is used in pages B and C, when B has its class changed, there's no need to invalidate a cached instance of C since Tapestry knows there's no dependency of B and C. On the other hand, if A is changed, both B and C instances need to be invalidated.</li></ol><p>You can find a graph of all known dependencies at a time by going to the /t5dashboard/pagedependencygraph URL of your webapp. You can view the dependencies for one specific page by going to /t5dashboard/ and clicking the page's Structure Info link. Here's one partial example:</p><p><span class="confluence-embedded-file-wrapper confluence-embedded-manual-size"><img class="confluence-embedded-image" draggable="false" height="250" src="class-reloading.data/image-2023-6-16_19-53-38.png"></span></p><h3 id="ClassReloading-HowItWorks">How It Works</h3><p>First of all, when production mode is off and multiple classloaders mode is also off, Tapestry reacts to file (including classes in controlled packages, templates, message properties files and assets) changes in the same way as before and described above in this page: all cached information or objects about classes, templates, message properties files, assets, property bindings, etc is invalidated (i.e. thrown away). The internal services that track changes to these files notify all their listeners that they should invalidate their caches completely.</p><p>When multiple classloaders mode is on, these internal services notify their listeners what changed, so they can invalidate just the objects or information that is actually affected by the changes. Sometimes this listeners notify the services that more stuff needs to be invalidated, For example, if one asset is changed and it's associated with a class, the asset service notifies the listeners that that class needs to be invalidated. For example, the component dependency registry, when notified that a given class was invalidated, besides invalidating information about that class, it informs the service that it also needs to invalidates the classes that depend on the changed one. This process repeats until there's no other class to be invalidated.</p><p>Additional logging was added so all files found as changed result in log entries. Same for the invalidation chain described in the paragraph above.</p><p>Now that it knows the class dependency graph, Tapestry needs to map that into a classloader tree, since each classloader instance can only have one pare nt. Internally, the framework has one class, PageClassLoaderContext (PCLC or context, for short), that stores all information about one classloader in the tree: the classloader itself, a name, the set of names of the classes beloging to it, the parent PCLC and children PCLCs. When a class is invalidated, the classes in the context are invalidated, then the context itself is invalidated, including its classloader, then the child contexts are recursively invalidated too.</p><p>There are 2 special PCLC contexts: the root one, which is never invalidated, and the unknown one, where classes without dependency information go initially.</p><p>The PCLC tree is created class by class, starting from pages and then on its dependencies on them in a recursive manner. For each class, if there's no PCLC already containing it, Tapestry checks its dependency list. Its basic algorithm is this:</p><ol><li>If there are dependencies that belong to a PCLC yet, run the algorithm on it.</li><li>All the PCLC s of dependencies are gathered.<ol><li>If there are no PCLCs found, a new one is created with that class and the root context is its parent.</li><li>If there is exactly one PCLC found, a new one is created with that class and the found PCLC will be its parent.</li><li>If more than one PCLC is found, these PCLCs are merged by creating a new one containing all the classes in the PCLCs. The old PCLCs are invalidated recursively and an event about its invalidation is sent to the listeners. One of them is <code>PageSource</code>, the service that manages, creates and caches page instances, so the merging of PCLCs can cause page invalidation, as keeping these instances could potentially cause <code>ClassCastException</code>s later due to class instances belonging to different versions of the same class.</li></ol></li></ol><h3 id="ClassReloading-Loading,StoringandPreloadingDependencyInformation">Loading, Storing and Preloading Dependency Information</h3><p>When a page instance is finished building, it's processed by the component dependency registry, which gathers the dependencies this page has, then the dependencies of its dependencies recursively. The registry doesn't process classes it already has information.</p><p>When starting up, Tapestry checks whether a file named <code>tapestryComponentDependencies.json</code> exists in the folder where the webapp is running. If it does, component dependency is loaded from it.</p><p>The T5Dashboard page has 2 new buttons related to dependencies, <code>Store dependency information</code>, writes all the dependency information as known at that moment.</p><p><code>Preload dependency information and page classloader contexts</code> goes through all known pages, gathers its dependencies, also doing the same for all the components used directly and indirectly by them, and the preloads page the page classloader contexts, thus avoiding context invalidations when page instances are created. Notice this process won't work if any of t he pages or their templates are invalid for any reason.</p><h3 id="ClassReloading-Caveats">Caveats</h3><p>Unfortunately, it wasn't possible to make the multiple classloader mode to work with all situations and code. Some known issues:</p><ol><li>On Java 9 and later, classes belonging to one classloader cannot call package-private methods of other classes even when both are in the same package. The solution is to change the method visibility to public.</li><li>If you don't have component dependency information already loaded for a given class, problems, usually in the form of a ClassCastException claiming an object of a given type cannot be assigned to a variable of the same type. The best way to deal with this is to use the T5Dashboard to write the component dependency information to a file and restart the webapp. In some cases, it helps to preload dependency information for all classes.</li><li>If you start your webapp without component dependency information, page instances may be created and invalidated a few times even when they didn't have any changes due to the page classloader context creation. The solution for this is the same one as the above.</li><li>When one asset is imported into a class through @Import, the class and the asset are considered associated. When an asset is changed and it's associated with at least one class, the classes associated with it, including pages, are invalidated. If an asset is changed and it's not associated with any class, then multiple classloader mode works the same as single classloader one, invalidating everything.</li></ol><p></p></div> </div> <!-- /// Content End --> </div>