mirror of
https://github.com/apache/struts.git
synced 2026-08-07 07:37:20 +00:00
WW-5537 Resolve classloader/memory leaks during Tomcat hot deployment (#1632)
* WW-5537 Add InternalDestroyable and ContextAwareDestroyable interfaces Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * WW-5537 ContainerHolder: ThreadLocal with AtomicLong generation counter Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * WW-5537 FinalizableReferenceQueue: volatile instance, join, classloader null Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * WW-5537 ScopeInterceptor.clearLocks: add synchronized block Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * WW-5537 CompoundRootAccessor, DefaultFileManager: implement InternalDestroyable Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * WW-5537 Add InternalDestroyable adapter classes for static cache cleanup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * WW-5537 Register InternalDestroyable beans in struts-beans.xml Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * WW-5537 JSON plugin: add JSONCacheDestroyable for BeanInfo cache cleanup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * WW-5537 Dispatcher.cleanup: refactor into focused methods with InternalDestroyable discovery Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * WW-5537 Rewrite DispatcherCleanupTest for InternalDestroyable discovery Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * WW-5537 Add log4j-web for proper Log4j2 lifecycle in Servlet container Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * WW-5537 Dispatcher.destroyObjectFactory: add early return on null, use pattern matching Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * WW-5537 Fix @since annotations: 7.1.0 -> 7.2.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * WW-5537 Add Container.destroy() to clear internal caches on undeploy Container now exposes a destroy() method that clears factories, injectors, constructors, and ThreadLocals. This releases Class<?> keys and JDK DelegatingClassLoader instances that pin the webapp classloader. DefaultConfiguration.destroy() calls container.destroy() and reloadContainer() delegates to destroy() to avoid duplication. Also fixes JSONCacheDestroyable referencing non-existent DefaultJSONWriter (renamed to StrutsJSONWriter). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * WW-5537 Fix Container.destroy(): don't clear factories, don't call from reloadContainer factories must remain intact because existing code holds direct references to the Container after destroyConfiguration() and expects it to still resolve dependencies (e.g. during configuration reload). reloadContainer() reverted to clearing packageContexts/loadedFileNames directly — calling destroy() there nulled the container reference and cleared state needed during the bootstrap transition. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * WW-5537 Restore destroy() call in reloadContainer() The test failures were caused by factories.clear() in Container.destroy(), not by calling destroy() from reloadContainer(). Now that factories.clear() is removed, destroy() is safe to call here — it clears packageContexts, loadedFileNames, and the container's reflection caches in one place. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * WW-5537 Fix Sonar issues: thread-safe FinalizableReferenceQueue, empty method comments - Replace volatile field with AtomicReference in FinalizableReferenceQueue for proper thread safety using getAndSet() - Add comments to empty destroy() implementations in test mocks - Replace deprecated new URL() with URI.toURL() in DispatcherCleanupTest - Add comments to empty listener methods in DispatcherCleanupTest Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+4
@@ -1264,5 +1264,9 @@ public class PackageBasedActionConfigBuilderTest extends TestCase {
|
||||
public void setScopeStrategy(Strategy scopeStrategy) {
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
// no-op in test dummy
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.struts2.json;
|
||||
|
||||
import org.apache.struts2.dispatcher.InternalDestroyable;
|
||||
|
||||
/**
|
||||
* WW-5537: Clears JSON plugin's static BeanInfo caches when the Dispatcher is
|
||||
* destroyed, preventing classloader leaks during hot redeployment.
|
||||
*
|
||||
* @since 7.2.0
|
||||
*/
|
||||
public class JSONCacheDestroyable implements InternalDestroyable {
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
StrutsJSONWriter.clearBeanInfoCaches();
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,14 @@ public class StrutsJSONWriter implements JSONWriter {
|
||||
private static final ConcurrentMap<Class<?>, BeanInfo> BEAN_INFO_CACHE_IGNORE_HIERARCHY = new ConcurrentHashMap<>();
|
||||
private static final ConcurrentMap<Class<?>, BeanInfo> BEAN_INFO_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Clears both BeanInfo caches to prevent classloader leaks on hot redeploy.
|
||||
*/
|
||||
public static void clearBeanInfoCaches() {
|
||||
BEAN_INFO_CACHE_IGNORE_HIERARCHY.clear();
|
||||
BEAN_INFO_CACHE.clear();
|
||||
}
|
||||
|
||||
private final StringBuilder buf = new StringBuilder();
|
||||
private final Deque<Object> stack = new ArrayDeque<>();
|
||||
private boolean ignoreHierarchy = true;
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
<bean type="org.apache.struts2.json.JSONReader" name="struts" class="org.apache.struts2.json.StrutsJSONReader"
|
||||
scope="prototype"/>
|
||||
<bean class="org.apache.struts2.json.JSONUtil" scope="prototype"/>
|
||||
<bean type="org.apache.struts2.dispatcher.InternalDestroyable" name="jsonCache"
|
||||
class="org.apache.struts2.json.JSONCacheDestroyable"/>
|
||||
|
||||
<constant name="struts.json.writer" value="struts"/>
|
||||
<constant name="struts.json.reader" value="struts"/>
|
||||
|
||||
+5
@@ -184,4 +184,9 @@ class DummyContainer implements Container {
|
||||
public void removeScopeStrategy() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
// no-op in test mock
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user