1 /****************************************************************
2 * Licensed to the Apache Software Foundation (ASF) under one *
3 * or more contributor license agreements. See the NOTICE file *
4 * distributed with this work for additional information *
5 * regarding copyright ownership. The ASF licenses this file *
6 * to you under the Apache License, Version 2.0 (the *
7 * "License"); you may not use this file except in compliance *
8 * with the License. You may obtain a copy of the License at *
9 * *
10 * http://www.apache.org/licenses/LICENSE-2.0 *
11 * *
12 * Unless required by applicable law or agreed to in writing, *
13 * software distributed under the License is distributed on an *
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
15 * KIND, either express or implied. See the License for the *
16 * specific language governing permissions and limitations *
17 * under the License. *
18 ****************************************************************/
19
20
21 package org.apache.james.util.watchdog;
22
23 import org.apache.excalibur.thread.ThreadPool;
24 import org.apache.avalon.framework.activity.Disposable;
25 import org.apache.avalon.framework.container.ContainerUtil;
26 import org.apache.avalon.framework.logger.AbstractLogEnabled;
27
28 /**
29 * This class represents an watchdog process that serves to
30 * monitor a situation and triggers an action after a certain time has
31 * passed. This implementation is deliberately inaccurate, trading
32 * accuracy for minimal impact on reset. This should be used when
33 * the time of the Watchdog trigger is not critical, and a high number
34 * of resets are expected.
35 *
36 */
37 public class InaccurateTimeoutWatchdog
38 extends AbstractLogEnabled
39 implements Watchdog, Runnable, Disposable {
40
41 /**
42 * Whether the watchdog is currently checking the trigger condition
43 */
44 private volatile boolean isChecking = false;
45
46 /**
47 * Whether the watchdog has been reset since the thread slept.
48 */
49 private volatile boolean isReset = false;
50
51
52 /**
53 * The number of milliseconds until the watchdog times out.
54 */
55 private final long timeout;
56
57 /**
58 * The last time the internal timer was reset, as measured in milliseconds since
59 * January 1, 1970 00:00:00.000 GMT.
60 */
61 private volatile long lastReset;
62
63 /**
64 * The WatchdogTarget whose execute() method will be called upon triggering
65 * of the condition.
66 */
67 private WatchdogTarget triggerTarget;
68
69 /**
70 * The thread that runs the watchdog.
71 */
72 private Thread watchdogThread;
73
74 /**
75 * The thread pool used to generate InaccurateTimeoutWatchdogs
76 */
77 private ThreadPool myThreadPool;
78
79 /**
80 * The sole constructor for the InaccurateTimeoutWatchdog
81 *
82 * @param timeout the time (in msec) that it will take the Watchdog to timeout
83 * @param target the WatchdogTarget to be executed when this Watchdog expires
84 * @param threadPool the thread pool used to generate threads for this implementation.
85 */
86 public InaccurateTimeoutWatchdog(long timeout, WatchdogTarget target, ThreadPool threadPool) {
87 if (target == null) {
88 throw new IllegalArgumentException("The WatchdogTarget for this TimeoutWatchdog cannot be null.");
89 }
90 if (threadPool == null) {
91 throw new IllegalArgumentException("The thread pool for this TimeoutWatchdog cannot be null.");
92 }
93 this.timeout = timeout;
94 triggerTarget = target;
95 myThreadPool = threadPool;
96 }
97
98 /**
99 * Start this Watchdog, causing it to begin checking.
100 */
101 public void start() {
102 if (getLogger().isDebugEnabled()) {
103 getLogger().debug("[" + triggerTarget + "] Calling start()" );
104 }
105 lastReset = System.currentTimeMillis();
106 isChecking = true;
107 synchronized(this) {
108 if ( watchdogThread == null) {
109 myThreadPool.execute(this);
110 }
111 }
112 }
113
114 /**
115 * Reset this Watchdog. Tells the Watchdog thread to reset
116 * the timer when it next awakens.
117 */
118 public void reset() {
119 if (watchdogThread != null) {
120 getLogger().debug("[" + triggerTarget + "] Calling reset() on thread '" + watchdogThread.getName() + "'");
121 } else {
122 getLogger().debug("[" + triggerTarget + "] Calling reset() for inactive watchdog");
123 }
124 isReset = true;
125 }
126
127 /**
128 * Stop this Watchdog, causing the Watchdog to stop checking the trigger
129 * condition. The monitor can be restarted with a call to startWatchdog.
130 */
131 public void stop() {
132 if (watchdogThread != null) {
133 getLogger().debug("[" + triggerTarget + "] Calling stop() on thread '" + watchdogThread.getName() + "'");
134 } else {
135 getLogger().debug("[" + triggerTarget + "] Calling stop() for inactive watchdog");
136 }
137 isChecking = false;
138 }
139
140 /**
141 * Execute the body of the Watchdog, triggering as appropriate.
142 */
143 public void run() {
144
145 try {
146 watchdogThread = Thread.currentThread();
147
148 while ((!(Thread.interrupted())) && (watchdogThread != null)) {
149 try {
150 if (!isChecking) {
151 if (getLogger().isDebugEnabled()) {
152 getLogger().debug("[" + triggerTarget + "] Watchdog on thread '" + Thread.currentThread().getName() + "' is not active - going to exit.");
153 }
154 synchronized (this) {
155 if (!isChecking) {
156 watchdogThread = null;
157 }
158 continue;
159 }
160 } else {
161 long currentTime = System.currentTimeMillis();
162 if (isReset) {
163 isReset = false;
164 lastReset = currentTime;
165 }
166 long timeToSleep = lastReset + timeout - currentTime;
167 if (watchdogThread != null) {
168 getLogger().debug("[" + triggerTarget + "] Watchdog on thread '" + watchdogThread.getName() + "' has time to sleep " + timeToSleep);
169 } else {
170 getLogger().debug("[" + triggerTarget + "] Watchdog has time to sleep " + timeToSleep);
171 }
172 if (timeToSleep <= 0) {
173 try {
174 synchronized (this) {
175 if ((isChecking) && (triggerTarget != null)) {
176 triggerTarget.execute();
177 }
178 watchdogThread = null;
179 }
180 } catch (Throwable t) {
181 getLogger().error("[" + triggerTarget + "] Encountered error while executing Watchdog target.", t);
182 }
183 isChecking = false;
184 continue;
185 } else {
186 synchronized(this) {
187 wait(timeToSleep);
188 }
189 }
190 }
191 } catch (InterruptedException ie) {
192 }
193 }
194
195 synchronized( this ) {
196 watchdogThread = null;
197 }
198 } finally {
199 // Ensure that the thread is in a non-interrupted state when it gets returned
200 // to the pool.
201 Thread.interrupted();
202 }
203 if (getLogger().isDebugEnabled()) {
204 getLogger().debug("[" + triggerTarget + "] Watchdog on thread '" + Thread.currentThread().getName() + "' is exiting run().");
205 }
206 }
207
208 /**
209 * @see org.apache.avalon.framework.activity.Disposable#dispose()
210 */
211 public void dispose() {
212 synchronized(this) {
213 isChecking = false;
214 if (getLogger().isDebugEnabled()) {
215 if (watchdogThread != null) {
216 getLogger().debug("[" + triggerTarget + "] Calling disposeWatchdog() on thread '" + watchdogThread.getName() + "'");
217 } else {
218 getLogger().debug("[" + triggerTarget + "] Calling disposeWatchdog() for inactive watchdog");
219 }
220 }
221 if (watchdogThread != null) {
222 watchdogThread = null;
223 notifyAll();
224 }
225 ContainerUtil.dispose(triggerTarget);
226 triggerTarget = null;
227 }
228 }
229 }