Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
Service |
|
| 1.8125;1.812 |
1 | /** | |
2 | The contents of this file are subject to the Mozilla Public License Version 1.1 | |
3 | (the "License"); you may not use this file except in compliance with the License. | |
4 | You may obtain a copy of the License at http://www.mozilla.org/MPL/ | |
5 | Software distributed under the License is distributed on an "AS IS" basis, | |
6 | WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the | |
7 | specific language governing rights and limitations under the License. | |
8 | ||
9 | The Original Code is "ManagedRunnable.java". Description: | |
10 | "Base class for a unified management of threads with a defined lifecycle." | |
11 | ||
12 | The Initial Developer of the Original Code is University Health Network. Copyright (C) | |
13 | 2001. All Rights Reserved. | |
14 | ||
15 | Contributor(s): ______________________________________. | |
16 | ||
17 | Alternatively, the contents of this file may be used under the terms of the | |
18 | GNU General Public License (the "GPL"), in which case the provisions of the GPL are | |
19 | applicable instead of those above. If you wish to allow use of your version of this | |
20 | file only under the terms of the GPL and not to allow others to use your version | |
21 | of this file under the MPL, indicate your decision by deleting the provisions above | |
22 | and replace them with the notice and other provisions required by the GPL License. | |
23 | If you do not delete the provisions above, a recipient may use your version of | |
24 | this file under either the MPL or the GPL. | |
25 | */ | |
26 | package ca.uhn.hl7v2.concurrent; | |
27 | ||
28 | import java.util.concurrent.CountDownLatch; | |
29 | import java.util.concurrent.ExecutionException; | |
30 | import java.util.concurrent.ExecutorService; | |
31 | import java.util.concurrent.Future; | |
32 | import java.util.concurrent.TimeUnit; | |
33 | import java.util.concurrent.TimeoutException; | |
34 | ||
35 | import org.slf4j.Logger; | |
36 | import org.slf4j.LoggerFactory; | |
37 | ||
38 | /** | |
39 | * Base class for a unified management of threads with a defined lifecycle. It | |
40 | * uses a {@link #keepRunning} flag to regularly terminate a thread. Classes | |
41 | * implementing this class must implement {@link #handle()} to do the main | |
42 | * processing. {@link #afterStartup()} and {@link #afterTermination()} can be | |
43 | * overridden to acquire and release resources required for processing. | |
44 | */ | |
45 | public abstract class Service implements Runnable { | |
46 | ||
47 | 10 | private static final Logger log = LoggerFactory |
48 | 5 | .getLogger(Service.class); |
49 | private volatile boolean keepRunning; | |
50 | 993 | private long shutdownTimeout = 3000L; |
51 | private final String name; | |
52 | private final ExecutorService executorService; | |
53 | private Future<?> thread; | |
54 | private Throwable serviceExitedWithException; | |
55 | 993 | private CountDownLatch startupLatch = new CountDownLatch(1); |
56 | ||
57 | public Service(String name, ExecutorService executorService) { | |
58 | 993 | super(); |
59 | 993 | this.name = name; |
60 | 993 | this.executorService = executorService; |
61 | 993 | } |
62 | ||
63 | /** | |
64 | * @return Returns <code>true</code> if the server has been started, and has | |
65 | * not yet been stopped. | |
66 | */ | |
67 | public boolean isRunning() { | |
68 | 15299 | return keepRunning; |
69 | } | |
70 | ||
71 | public ExecutorService getExecutorService() { | |
72 | 2145 | return executorService; |
73 | } | |
74 | ||
75 | /** | |
76 | * Sets the time in milliseconds how long {@link #stopAndWait()} should wait | |
77 | * for the thread to terminate. Defaults to 3000ms. | |
78 | * | |
79 | * @param shutdownTimeout timout in milliseconds | |
80 | */ | |
81 | public void setShutdownTimeout(long shutdownTimeout) { | |
82 | 0 | this.shutdownTimeout = shutdownTimeout; |
83 | 0 | } |
84 | ||
85 | /** | |
86 | * Starts the server listening for connections in a new thread. This | |
87 | * continues until <code>stop()</code> is called. | |
88 | * | |
89 | * @throws IllegalStateException If the service is already running (i.e. | |
90 | * start() has already been called. | |
91 | * | |
92 | */ | |
93 | public void start() { | |
94 | 978 | if (keepRunning) { |
95 | 5 | throw new IllegalStateException("Service is already running"); |
96 | } | |
97 | 973 | log.debug("Starting service {}", name); |
98 | 973 | keepRunning = true; |
99 | 973 | ExecutorService service = getExecutorService(); |
100 | 973 | if (service.isShutdown()) { |
101 | 0 | throw new IllegalStateException("ExecutorService is shut down"); |
102 | } | |
103 | 973 | thread = service.submit(this); |
104 | 973 | } |
105 | ||
106 | /** | |
107 | * <p> | |
108 | * Starts the server listening for connections in a new thread. This | |
109 | * continues until <code>stop()</code> is called. | |
110 | * </p> | |
111 | * <p> | |
112 | * Unlike {@link #start()}, this method will not return until the processing | |
113 | * loop has completed at least once. This does not imply any kind of successful | |
114 | * processing, but should at least provide a guarantee that the service | |
115 | * has finished initializing itself. | |
116 | * </p> | |
117 | */ | |
118 | public void startAndWait() throws InterruptedException { | |
119 | 115 | start(); |
120 | 110 | startupLatch.await(); |
121 | 110 | } |
122 | ||
123 | /** | |
124 | * Prepare any resources before entering the main thread. | |
125 | * | |
126 | * @throws RuntimeException | |
127 | * if resources could not acquired. In this case, the thread | |
128 | * will shutdown. Note that {@link #afterTermination()} is | |
129 | * called before. | |
130 | */ | |
131 | protected void afterStartup() { | |
132 | 838 | } |
133 | ||
134 | /** | |
135 | * The main task of the thread, called in a loop as long as | |
136 | * {@link #isRunning()} returns true. Overridden methods are responsible for | |
137 | * yielding or pausing the thread when it's idle. The method must also not | |
138 | * block indefinitely so that a call to {@link #stop()} is able to | |
139 | * gracefully terminate the thread. | |
140 | */ | |
141 | protected abstract void handle(); | |
142 | ||
143 | /** | |
144 | * Advises the thread to leave its main loop. {@link #prepareTermination()} is | |
145 | * called before this method returns. {@link #afterTermination()} is | |
146 | * called after the thread has left its main loop. | |
147 | */ | |
148 | public void stop() { | |
149 | 878 | if (isRunning()) { |
150 | 878 | prepareTermination(); |
151 | } | |
152 | 878 | } |
153 | ||
154 | public void waitForTermination() { | |
155 | 205 | if (!thread.isDone()) |
156 | try { | |
157 | 205 | thread.get(shutdownTimeout, TimeUnit.MILLISECONDS); |
158 | 0 | } catch (ExecutionException ee) { |
159 | // empty | |
160 | 0 | } catch (TimeoutException te) { |
161 | 0 | log.warn( |
162 | "Thread did not stop after {} milliseconds. Now cancelling.", | |
163 | 0 | shutdownTimeout); |
164 | 0 | thread.cancel(true); |
165 | 0 | } catch (InterruptedException e) { |
166 | // empty | |
167 | 205 | } |
168 | 205 | } |
169 | ||
170 | /** | |
171 | * Stops the thread by leaving its main loop. {@link #afterTermination()} is | |
172 | * called before the thread is terminated. The method waits until the thread | |
173 | * has stopped. | |
174 | */ | |
175 | public final void stopAndWait() { | |
176 | 205 | stop(); |
177 | 205 | waitForTermination(); |
178 | 205 | } |
179 | ||
180 | /** | |
181 | * Clean up any resources initialized in {@link #afterStartup()}. | |
182 | */ | |
183 | protected void afterTermination() { | |
184 | 878 | } |
185 | ||
186 | /** | |
187 | * Prepare thread to leave its main loop. By default sets {@link #keepRunning} | |
188 | * to false, but some implementations may need to do additional stuff. | |
189 | */ | |
190 | protected void prepareTermination() { | |
191 | 878 | log.debug("Prepare to stop thread {}", name); |
192 | 878 | keepRunning = false; |
193 | 878 | } |
194 | ||
195 | /** | |
196 | * Runs the thread. | |
197 | * | |
198 | * @see java.lang.Runnable#run() | |
199 | */ | |
200 | public final void run() { | |
201 | try { | |
202 | 973 | afterStartup(); |
203 | 968 | log.debug("Thread {} entering main loop", name); |
204 | 13267 | while (isRunning()) { |
205 | 12394 | handle(); |
206 | 12299 | startupLatch.countDown(); |
207 | } | |
208 | 873 | log.debug("Thread {} leaving main loop", name); |
209 | 5 | } catch (RuntimeException t) { |
210 | 5 | if (t.getCause() != null) { |
211 | 5 | serviceExitedWithException = t.getCause(); |
212 | } else { | |
213 | 0 | serviceExitedWithException = t; |
214 | } | |
215 | 5 | log.warn("Thread exiting main loop due to exception:", t); |
216 | 0 | } catch (Throwable t) { |
217 | 0 | serviceExitedWithException = t; |
218 | 0 | log.warn("Thread exiting main loop due to exception:", t); |
219 | } finally { | |
220 | 878 | startupLatch.countDown(); |
221 | 878 | afterTermination(); |
222 | 878 | } |
223 | ||
224 | 878 | } |
225 | ||
226 | /** | |
227 | * Provide the exception which caused this service to fail | |
228 | */ | |
229 | protected void setServiceExitedWithException(Throwable theThreadExitedWithException) { | |
230 | 5 | serviceExitedWithException = theThreadExitedWithException; |
231 | 5 | } |
232 | ||
233 | ||
234 | /** | |
235 | * If this service exited with an exception, ths method returns that exception. This is useful for | |
236 | * detecting if the service failed unexpectedly | |
237 | */ | |
238 | public Throwable getServiceExitedWithException() { | |
239 | 3594 | return serviceExitedWithException; |
240 | } | |
241 | ||
242 | } |