001: package org.tanukisoftware.wrapper;
002:
003: /*
004: * Copyright (c) 1999, 2006 Tanuki Software Inc.
005: *
006: * Permission is hereby granted, free of charge, to any person
007: * obtaining a copy of the Java Service Wrapper and associated
008: * documentation files (the "Software"), to deal in the Software
009: * without restriction, including without limitation the rights
010: * to use, copy, modify, merge, publish, distribute, sub-license,
011: * and/or sell copies of the Software, and to permit persons to
012: * whom the Software is furnished to do so, subject to the
013: * following conditions:
014: *
015: * The above copyright notice and this permission notice shall be
016: * included in all copies or substantial portions of the Software.
017: *
018: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
019: * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
020: * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
021: * NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
022: * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
023: * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
024: * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
025: * OTHER DEALINGS IN THE SOFTWARE.
026: */
027:
028: import java.io.IOException;
029: import java.io.InterruptedIOException;
030: import java.lang.reflect.InvocationTargetException;
031: import java.lang.reflect.Method;
032: import java.net.InetAddress;
033: import java.net.Socket;
034: import java.net.SocketException;
035: import java.net.ServerSocket;
036: import java.util.Hashtable;
037:
038: import org.tanukisoftware.wrapper.WrapperManager;
039:
040: /**
041: * If an application instantiates an instance of this class, the JVM will
042: * listen on the specified port for connections. When a connection is
043: * detected, the first byte of input will be read from the socket and
044: * then the connection will be immediately closed. An action will then
045: * be performed based on the byte read from the stream.
046: * <p>
047: * The easiest way to invoke an action manually is to telnet to the specified
048: * port and then type the single command key.
049: * <code>telnet localhost 9999</code>, for example.
050: * <p>
051: * Valid commands include:
052: * <ul>
053: * <li><b>S</b> : Shutdown cleanly.</li>
054: * <li><b>H</b> : Immediate forced shutdown.</li>
055: * <li><b>R</b> : Restart</li>
056: * <li><b>D</b> : Perform a Thread Dump</li>
057: * <li><b>U</b> : Unexpected shutdown. (Simulate a crash for testing)</li>
058: * <li><b>V</b> : Cause an access violation. (For testing)</li>
059: * <li><b>G</b> : Make the JVM appear to be hung. (For testing)</li>
060: * </ul>
061: * Additional user defined actions can be defined by calling the
062: * {@link #registerAction( byte command, Runnable action )} method.
063: * The Wrapper project reserves the right to define any upper case
064: * commands in the future. To avoid future conflicts, please use lower
065: * case for user defined commands.
066: * <p>
067: * This application will work even in most deadlock situations because the
068: * thread is in issolation from the rest of the application. If the JVM
069: * is truely hung, this class will fail to accept connections but the
070: * Wrapper itself will detect the hang and restart the JVM externally.
071: * <p>
072: * The following code can be used in your application to start up the
073: * WrapperActionServer with all default actions enabled:
074: * <pre>
075: * int port = 9999;
076: * WrapperActionServer server = new WrapperActionServer( port );
077: * server.enableShutdownAction( true );
078: * server.enableHaltExpectedAction( true );
079: * server.enableRestartAction( true );
080: * server.enableThreadDumpAction( true );
081: * server.enableHaltUnexpectedAction( true );
082: * server.enableAccessViolationAction( true );
083: * server.enableAppearHungAction( true );
084: * server.start();
085: * </pre>
086: * Then remember to stop the server when your application shuts down:
087: * <pre>
088: * server.stop();
089: * </pre>
090: *
091: * @author Leif Mortenson <leif@tanukisoftware.com>
092: */
093: public class WrapperActionServer implements Runnable {
094: /** Command to invoke a shutdown action. */
095: public final static byte COMMAND_SHUTDOWN = (byte) 'S';
096: /** Command to invoke an expected halt action. */
097: public final static byte COMMAND_HALT_EXPECTED = (byte) 'H';
098: /** Command to invoke a restart action. */
099: public final static byte COMMAND_RESTART = (byte) 'R';
100: /** Command to invoke a thread dump action. */
101: public final static byte COMMAND_DUMP = (byte) 'D';
102: /** Command to invoke an unexpected halt action. */
103: public final static byte COMMAND_HALT_UNEXPECTED = (byte) 'U';
104: /** Command to invoke an access violation. */
105: public final static byte COMMAND_ACCESS_VIOLATION = (byte) 'V';
106: /** Command to invoke an appear hung action. */
107: public final static byte COMMAND_APPEAR_HUNG = (byte) 'G';
108:
109: /** The address to bind the port server to. Null for any address. */
110: private InetAddress m_bindAddr;
111:
112: /** The port to listen on for connections. */
113: private int m_port;
114:
115: /** Reference to the worker thread. */
116: private Thread m_runner;
117:
118: /** Flag set when the m_runner thread has been asked to stop. */
119: private boolean m_runnerStop = false;
120:
121: /** Reference to the ServerSocket. */
122: private ServerSocket m_serverSocket;
123:
124: /** Table of all the registered actions. */
125: private Hashtable m_actions = new Hashtable();
126:
127: /*---------------------------------------------------------------
128: * Constructors
129: *-------------------------------------------------------------*/
130: /**
131: * Creates and starts WrapperActionServer instance bound to the
132: * specified port and address.
133: *
134: * @param port Port on which to listen for connections.
135: * @param bindAddress Address to bind to.
136: */
137: public WrapperActionServer(int port, InetAddress bindAddress) {
138: m_port = port;
139: m_bindAddr = bindAddress;
140: }
141:
142: /**
143: * Creates and starts WrapperActionServer instance bound to the
144: * specified port. The socket will bind to all addresses and
145: * should be concidered a security risk.
146: *
147: * @param port Port on which to listen for connections.
148: */
149: public WrapperActionServer(int port) {
150: this (port, null);
151: }
152:
153: /*---------------------------------------------------------------
154: * Runnable Methods
155: *-------------------------------------------------------------*/
156: /**
157: * Thread which will listen for connections on the socket.
158: */
159: public void run() {
160: if (Thread.currentThread() != m_runner) {
161: throw new IllegalStateException("Private method.");
162: }
163:
164: try {
165: while (!m_runnerStop) {
166: try {
167: int command;
168: Socket socket = m_serverSocket.accept();
169: try {
170: // Set a short timeout of 15 seconds,
171: // so connections will be promptly closed if left idle.
172: socket.setSoTimeout(15000);
173:
174: // Read a single byte.
175: command = socket.getInputStream().read();
176: } finally {
177: socket.close();
178: }
179:
180: if (command >= 0) {
181: Runnable action;
182: synchronized (m_actions) {
183: action = (Runnable) m_actions
184: .get(new Integer(command));
185: }
186:
187: if (action != null) {
188: try {
189: action.run();
190: } catch (Throwable t) {
191: System.out
192: .println("WrapperActionServer: Error processing action.");
193: t.printStackTrace();
194: }
195: }
196: }
197: } catch (Throwable t) {
198: // Check for throwable type this way rather than with seperate catches
199: // to work around a problem where InterruptedException can be thrown
200: // when the compiler gives an error saying that it can't.
201: if (m_runnerStop
202: && ((t instanceof InterruptedException)
203: || (t instanceof SocketException) || (t instanceof InterruptedIOException))) {
204: // This is expected, the service is being stopped.
205: } else {
206: System.out
207: .println("WrapperActionServer: Unexpeced error.");
208: t.printStackTrace();
209:
210: // Avoid tight thrashing
211: try {
212: Thread.sleep(5000);
213: } catch (InterruptedException e) {
214: // Ignore
215: }
216: }
217: }
218: }
219: } finally {
220: synchronized (this ) {
221: m_runner = null;
222:
223: // Wake up the stop method if it is waiting for the runner to stop.
224: this .notify();
225: }
226: }
227: }
228:
229: /*---------------------------------------------------------------
230: * Methods
231: *-------------------------------------------------------------*/
232: /**
233: * Starts the runner thread.
234: *
235: * @throws IOException If the server socket is unable to bind to the
236: * specified port or there are any other problems
237: * opening a socket.
238: */
239: public void start() throws IOException {
240: // Create the server socket.
241: m_serverSocket = new ServerSocket(m_port, 5, m_bindAddr);
242:
243: m_runner = new Thread(this , "WrapperActionServer_runner");
244: m_runner.setDaemon(true);
245: m_runner.start();
246: }
247:
248: /**
249: * Stops the runner thread, blocking until it has stopped.
250: */
251: public void stop() throws Exception {
252: Thread runner = m_runner;
253: m_runnerStop = true;
254: runner.interrupt();
255:
256: // Close the server socket so it stops blocking for new connections.
257: ServerSocket serverSocket = m_serverSocket;
258: if (serverSocket != null) {
259: try {
260: serverSocket.close();
261: } catch (IOException e) {
262: // Ignore.
263: }
264: }
265:
266: synchronized (this ) {
267: while (m_runner != null) {
268: try {
269: // Wait to be notified that the thread has exited.
270: this .wait();
271: } catch (InterruptedException e) {
272: // Ignore
273: }
274: }
275: }
276: }
277:
278: /**
279: * Registers an action with the action server. The server will not accept
280: * any new connections until an action has returned, so keep that in mind
281: * when writing them. Also be aware than any uncaught exceptions will be
282: * dumped to the console if uncaught by the action. To avoid this, wrap
283: * the code in a <code>try { ... } catch (Throwable t) { ... }</code>
284: * block.
285: *
286: * @param command Command to be registered. Will override any exiting
287: * action already registered with the same command.
288: * @param action Action to be registered.
289: */
290: public void registerAction(byte command, Runnable action) {
291: synchronized (m_actions) {
292: m_actions.put(new Integer(command), action);
293: }
294: }
295:
296: /**
297: * Unregisters an action with the given command. If no action exists with
298: * the specified command, the method will quietly ignore the call.
299: */
300: public void unregisterAction(byte command) {
301: synchronized (m_actions) {
302: m_actions.remove(new Integer(command));
303: }
304: }
305:
306: /**
307: * Enable or disable the shutdown command. Disabled by default.
308: *
309: * @param enable True to enable to action, false to disable it.
310: */
311: public void enableShutdownAction(boolean enable) {
312: if (enable) {
313: registerAction(COMMAND_SHUTDOWN, new Runnable() {
314: public void run() {
315: WrapperManager.stop(0);
316: }
317: });
318: } else {
319: unregisterAction(COMMAND_SHUTDOWN);
320: }
321: }
322:
323: /**
324: * Enable or disable the expected halt command. Disabled by default.
325: * This will shutdown the JVM, but will do so immediately without going
326: * through the clean shutdown process.
327: *
328: * @param enable True to enable to action, false to disable it.
329: */
330: public void enableHaltExpectedAction(boolean enable) {
331: if (enable) {
332: registerAction(COMMAND_HALT_EXPECTED, new Runnable() {
333: public void run() {
334: WrapperManager.stopImmediate(0);
335: }
336: });
337: } else {
338: unregisterAction(COMMAND_HALT_EXPECTED);
339: }
340: }
341:
342: /**
343: * Enable or disable the restart command. Disabled by default.
344: *
345: * @param enable True to enable to action, false to disable it.
346: */
347: public void enableRestartAction(boolean enable) {
348: if (enable) {
349: registerAction(COMMAND_RESTART, new Runnable() {
350: public void run() {
351: WrapperManager.restart();
352: }
353: });
354: } else {
355: unregisterAction(COMMAND_RESTART);
356: }
357: }
358:
359: /**
360: * Enable or disable the thread dump command. Disabled by default.
361: *
362: * @param enable True to enable to action, false to disable it.
363: */
364: public void enableThreadDumpAction(boolean enable) {
365: if (enable) {
366: registerAction(COMMAND_DUMP, new Runnable() {
367: public void run() {
368: WrapperManager.requestThreadDump();
369: }
370: });
371: } else {
372: unregisterAction(COMMAND_DUMP);
373: }
374: }
375:
376: /**
377: * Enable or disable the unexpected halt command. Disabled by default.
378: * If this command is executed, the Wrapper will think the JVM crashed
379: * and restart it.
380: *
381: * @param enable True to enable to action, false to disable it.
382: */
383: public void enableHaltUnexpectedAction(boolean enable) {
384: if (enable) {
385: registerAction(COMMAND_HALT_UNEXPECTED, new Runnable() {
386: public void run() {
387: // Execute runtime.halt(0) using reflection so this class will
388: // compile on 1.2.x versions of Java.
389: Method haltMethod;
390: try {
391: haltMethod = Runtime.class.getMethod("halt",
392: new Class[] { Integer.TYPE });
393: } catch (NoSuchMethodException e) {
394: System.out
395: .println("halt not supported by current JVM.");
396: haltMethod = null;
397: }
398:
399: if (haltMethod != null) {
400: Runtime runtime = Runtime.getRuntime();
401: try {
402: haltMethod.invoke(runtime,
403: new Object[] { new Integer(0) });
404: } catch (IllegalAccessException e) {
405: System.out
406: .println("Unable to call runitme.halt: "
407: + e.getMessage());
408: } catch (InvocationTargetException e) {
409: System.out
410: .println("Unable to call runitme.halt: "
411: + e.getMessage());
412: }
413: }
414: }
415: });
416: } else {
417: unregisterAction(COMMAND_HALT_UNEXPECTED);
418: }
419: }
420:
421: /**
422: * Enable or disable the access violation command. Disabled by default.
423: * This command is useful for testing how an application handles the worst
424: * case situation where the JVM suddenly crashed. When this happens, the
425: * the JVM will simply die and there will be absolutely no chance for any
426: * shutdown or cleanup work to be done by the JVM.
427: *
428: * @param enable True to enable to action, false to disable it.
429: */
430: public void enableAccessViolationAction(boolean enable) {
431: if (enable) {
432: registerAction(COMMAND_ACCESS_VIOLATION, new Runnable() {
433: public void run() {
434: WrapperManager.accessViolationNative();
435: }
436: });
437: } else {
438: unregisterAction(COMMAND_ACCESS_VIOLATION);
439: }
440: }
441:
442: /**
443: * Enable or disable the appear hung command. Disabled by default.
444: * This command is useful for testing how an application handles the
445: * situation where the JVM stops responding to the Wrapper's ping
446: * requests. This can happen if the JVM hangs or some piece of code
447: * deadlocks. When this happens, the Wrapper will give up after the
448: * ping timeout has expired and kill the JVM process. The JVM will
449: * not have a chance to clean up and shudown gracefully.
450: *
451: * @param enable True to enable to action, false to disable it.
452: */
453: public void enableAppearHungAction(boolean enable) {
454: if (enable) {
455: registerAction(COMMAND_APPEAR_HUNG, new Runnable() {
456: public void run() {
457: WrapperManager.appearHung();
458: }
459: });
460: } else {
461: unregisterAction(COMMAND_APPEAR_HUNG);
462: }
463: }
464: }
|