001/** 002The contents of this file are subject to the Mozilla Public License Version 1.1 003(the "License"); you may not use this file except in compliance with the License. 004You may obtain a copy of the License at http://www.mozilla.org/MPL/ 005Software distributed under the License is distributed on an "AS IS" basis, 006WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 007specific language governing rights and limitations under the License. 008 009The Original Code is "ManagedRunnable.java". Description: 010"Base class for a unified management of threads with a defined lifecycle." 011 012The Initial Developer of the Original Code is University Health Network. Copyright (C) 0132001. All Rights Reserved. 014 015Contributor(s): ______________________________________. 016 017Alternatively, the contents of this file may be used under the terms of the 018GNU General Public License (the "GPL"), in which case the provisions of the GPL are 019applicable instead of those above. If you wish to allow use of your version of this 020file only under the terms of the GPL and not to allow others to use your version 021of this file under the MPL, indicate your decision by deleting the provisions above 022and replace them with the notice and other provisions required by the GPL License. 023If you do not delete the provisions above, a recipient may use your version of 024this file under either the MPL or the GPL. 025 */ 026package ca.uhn.hl7v2.concurrent; 027 028import java.util.concurrent.ExecutionException; 029import java.util.concurrent.ExecutorService; 030import java.util.concurrent.Future; 031import java.util.concurrent.TimeUnit; 032import java.util.concurrent.TimeoutException; 033 034import org.slf4j.Logger; 035import org.slf4j.LoggerFactory; 036 037/** 038 * Base class for a unified management of threads with a defined lifecycle. It 039 * uses a {@link #keepRunning} flag to regularly terminate a thread. Classes 040 * implementing this class must implement {@link #handle()} to do the main 041 * processing. {@link #afterStartup()} and {@link #afterTermination()} can be 042 * overridden to acquire and release resources required for processing. 043 */ 044public abstract class Service implements Runnable { 045 046 private static final Logger log = LoggerFactory 047 .getLogger(Service.class); 048 private volatile boolean keepRunning; 049 private long shutdownTimeout = 3000L; 050 private final String name; 051 private final ExecutorService executorService; 052 private Future<?> thread; 053 private Throwable serviceExitedWithException; 054 055 public Service(String name, ExecutorService executorService) { 056 super(); 057 this.name = name; 058 this.executorService = executorService; 059 } 060 061 /** 062 * @return Returns <code>true</code> if the server has been started, and has 063 * not yet been stopped. 064 */ 065 public boolean isRunning() { 066 return keepRunning; 067 } 068 069 public ExecutorService getExecutorService() { 070 return executorService; 071 } 072 073 /** 074 * Sets the time in milliseconds how long {@link #stopAndWait()} should wait 075 * for the thread to terminate. Defaults to 3000ms. 076 * 077 * @param shutdownTimeout 078 */ 079 public void setShutdownTimeout(long shutdownTimeout) { 080 this.shutdownTimeout = shutdownTimeout; 081 } 082 083 /** 084 * Starts the server listening for connections in a new thread. This 085 * continues until <code>stop()</code> is called. 086 */ 087 public void start() { 088 log.debug("Starting service {}", name); 089 keepRunning = true; 090 thread = getExecutorService().submit(this); 091 } 092 093 /** 094 * Prepare any resources before entering the main thread. 095 * 096 * @throws RuntimeException 097 * if resources could not acquired. In this case, the thread 098 * will shutdown. Note that {@link #afterTermination()} is 099 * called before. 100 */ 101 protected void afterStartup() { 102 } 103 104 /** 105 * The main task of the thread, called in a loop as long as 106 * {@link #isRunning()} returns true. Overridden methods are responsible for 107 * yielding or pausing the thread when it's idle. The method must also not 108 * block indefinitely so that a call to {@link #stop()} is able to 109 * gracefully terminate the thread. 110 * 111 * @throws Throwable 112 * any exception thrown will terminate the thread. 113 */ 114 protected abstract void handle(); 115 116 /** 117 * Advises the thread to leave its main loop. {@link #beforeTermination()} is 118 * called before this method returns. {@link #afterTermination()} is 119 * called after the thread has left its main loop. 120 */ 121 public void stop() { 122 if (isRunning()) { 123 prepareTermination(); 124 } 125 } 126 127 public void waitForTermination() { 128 if (!thread.isDone()) 129 try { 130 thread.get(shutdownTimeout, TimeUnit.MILLISECONDS); 131 } catch (ExecutionException ee) { 132 } catch (TimeoutException te) { 133 log.warn( 134 "Thread did not stop after {} milliseconds. Now cancelling.", 135 shutdownTimeout); 136 thread.cancel(true); 137 } catch (InterruptedException e) { 138 } 139 } 140 141 /** 142 * Stops the thread by leaving its main loop. {@link #afterTermination()} is 143 * called before the thread is terminated. The method waits until the thread 144 * has stopped. 145 * 146 * @throws Exception 147 * if the thread has not finished within shutdownTimeout 148 */ 149 public final void stopAndWait() { 150 stop(); 151 waitForTermination(); 152 } 153 154 /** 155 * Clean up any resources initialized in {@link #afterStartup()}. 156 */ 157 protected void afterTermination() { 158 }; 159 160 /** 161 * Prepare thread to leave its main loop. By default sets {@link #keepRunning} 162 * to false, but some implementations may need to do additional stuff. 163 */ 164 protected void prepareTermination() { 165 log.debug("Prepare to stop thread {}", name); 166 keepRunning = false; 167 }; 168 169 /** 170 * Runs the thread. 171 * 172 * @see java.lang.Runnable#run() 173 */ 174 public final void run() { 175 try { 176 afterStartup(); 177 log.debug("Thread {} entering main loop", name); 178 while (isRunning()) { 179 handle(); 180 } 181 log.debug("Thread {} leaving main loop", name); 182 } catch (RuntimeException t) { 183 serviceExitedWithException = t.getCause(); 184 log.warn("Thread exiting main loop due to exception:", t.getCause()); 185 } catch (Throwable t) { 186 serviceExitedWithException = t; 187 log.warn("Thread exiting main loop due to exception:", t); 188 } finally { 189 afterTermination(); 190 } 191 192 } 193 194 /** 195 * Provide the exception which caused this service to fail 196 */ 197 protected void setServiceExitedWithException(Throwable theThreadExitedWithException) { 198 serviceExitedWithException = theThreadExitedWithException; 199 } 200 201 202 /** 203 * If this service exited with an exception, ths method returns that exception. This is useful for 204 * detecting if the service failed unexpectedly 205 */ 206 public Throwable getServiceExitedWithException() { 207 return serviceExitedWithException; 208 } 209 210}