1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.catalina.core;
19
20 import java.util.concurrent.Executor;
21
22 import org.apache.catalina.ContainerEvent;
23 import org.apache.catalina.Context;
24 import org.apache.catalina.Engine;
25 import org.apache.catalina.Lifecycle;
26 import org.apache.catalina.LifecycleEvent;
27 import org.apache.catalina.LifecycleListener;
28 import org.apache.catalina.Server;
29 import org.apache.catalina.Service;
30 import org.apache.catalina.connector.Connector;
31 import org.apache.coyote.ProtocolHandler;
32 import org.apache.juli.logging.Log;
33 import org.apache.juli.logging.LogFactory;
34 import org.apache.tomcat.util.res.StringManager;
35 import org.apache.tomcat.util.threads.ThreadPoolExecutor;
36
37 /**
38 * <p>
39 * A {@link LifecycleListener} that triggers the renewal of threads in Executor
40 * pools when a {@link Context} is being stopped to avoid thread-local related
41 * memory leaks.
42 * </p>
43 * <p>
44 * Note : active threads will be renewed one by one when they come back to the
45 * pool after executing their task, see
46 * {@link org.apache.tomcat.util.threads.ThreadPoolExecutor}.afterExecute().
47 * </p>
48 *
49 * This listener must be declared in server.xml to be active.
50 *
51 */
52 public class ThreadLocalLeakPreventionListener extends FrameworkListener {
53
54 private static final Log log =
55 LogFactory.getLog(ThreadLocalLeakPreventionListener.class);
56
57 private volatile boolean serverStopping = false;
58
59 /**
60 * The string manager for this package.
61 */
62 protected static final StringManager sm =
63 StringManager.getManager(Constants.Package);
64
65 /**
66 * Listens for {@link LifecycleEvent} for the start of the {@link Server} to
67 * initialize itself and then for after_stop events of each {@link Context}.
68 */
69 @Override
70 public void lifecycleEvent(LifecycleEvent event) {
71 try {
72 super.lifecycleEvent(event);
73
74 Lifecycle lifecycle = event.getLifecycle();
75 if (Lifecycle.BEFORE_STOP_EVENT.equals(event.getType()) &&
76 lifecycle instanceof Server) {
77 // Server is shutting down, so thread pools will be shut down so
78 // there is no need to clean the threads
79 serverStopping = true;
80 }
81
82 if (Lifecycle.AFTER_STOP_EVENT.equals(event.getType()) &&
83 lifecycle instanceof Context) {
84 stopIdleThreads((Context) lifecycle);
85 }
86 } catch (Exception e) {
87 String msg =
88 sm.getString(
89 "threadLocalLeakPreventionListener.lifecycleEvent.error",
90 event);
91 log.error(msg, e);
92 }
93 }
94
95 @Override
96 public void containerEvent(ContainerEvent event) {
97 try {
98 super.containerEvent(event);
99 } catch (Exception e) {
100 String msg =
101 sm.getString(
102 "threadLocalLeakPreventionListener.containerEvent.error",
103 event);
104 log.error(msg, e);
105 }
106
107 }
108
109 /**
110 * Updates each ThreadPoolExecutor with the current time, which is the time
111 * when a context is being stopped.
112 *
113 * @param context
114 * the context being stopped, used to discover all the Connectors
115 * of its parent Service.
116 */
117 private void stopIdleThreads(Context context) {
118 if (serverStopping) return;
119
120 if (!(context instanceof StandardContext) ||
121 !((StandardContext) context).getRenewThreadsWhenStoppingContext()) {
122 log.debug("Not renewing threads when the context is stopping. "
123 + "It is not configured to do it.");
124 return;
125 }
126
127 Engine engine = (Engine) context.getParent().getParent();
128 Service service = engine.getService();
129 Connector[] connectors = service.findConnectors();
130 if (connectors != null) {
131 for (Connector connector : connectors) {
132 ProtocolHandler handler = connector.getProtocolHandler();
133 Executor executor = null;
134 if (handler != null) {
135 executor = handler.getExecutor();
136 }
137
138 if (executor instanceof ThreadPoolExecutor) {
139 ThreadPoolExecutor threadPoolExecutor =
140 (ThreadPoolExecutor) executor;
141 threadPoolExecutor.contextStopping();
142 } else if (executor instanceof StandardThreadExecutor) {
143 StandardThreadExecutor stdThreadExecutor =
144 (StandardThreadExecutor) executor;
145 stdThreadExecutor.contextStopping();
146 }
147
148 }
149 }
150 }
151
152 @Override
153 protected LifecycleListener createLifecycleListener(Context context) {
154 return this;
155 }
156 }
157