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