package com.mcqueeney.demo; /** * Demonstrate code to shutdown after ^C only after processing * a complete record, not in the middle. * @author Tom McQueeney */ public class CleanShutdownDemo { /** Flags so we can stop cleanly when the VM exits. */ private boolean isVMShuttingDown = false; private boolean readyToExit = false; // Shutdown thread as an instance var so we can remove it later. private Thread shutdownThread; // For demonstration purposes: private final boolean runningInCleanMode; private StringBuffer[] dataToProcess; /** * Main to start processing with some string data. * @param args processes "cleanly" if args[0] is "clean" */ public static void main(String[] args) { StringBuffer[] data = new StringBuffer[] { new StringBuffer("show"), new StringBuffer("cod"), new StringBuffer("thread"), new StringBuffer("blogg"), new StringBuffer("vacation"), }; boolean runClean = args.length > 0 && "clean".equals(args[0]); new CleanShutdownDemo(runClean).startProcessing(data); } /** * Constructor to set what demo "mode" we should run in * and to register the shutdown hook. */ public CleanShutdownDemo(boolean shouldRunClean) { this.runningInCleanMode = shouldRunClean; registerShutdownHook(); // For cleaner unexpected shutdowns. } /** * Begins processing the records, cognizant of Control-C interrupts. * Param "records" is the data we want to process in a way that * an interrupt occurs only after fully processing each record. * The records probably would be data from a ResultSet or some * other data we need to process without being interrupted * in the middle. */ public void startProcessing(StringBuffer[] records) { this.dataToProcess = records; // Store for demo purposes. System.out.println("Starting processing"); int recordsProcessed = 0; try { for (int i = 0, j = records.length; i < j; i++) { // Process this next record but don't let ^C interrupt // unless it takes more than 1.5 seconds. addIngToString(records[i]); ++recordsProcessed; // Don't continue if VM is trying to shut down. if (this.isVMShuttingDown) { System.out.println( "Process interrupted. Shutting down after processing " + recordsProcessed + " records." ); signalReadyToExit(); break; } } // end while. // Tell shutdown hook we're done. signalReadyToExit(); // In case shutdown thread running. unregisterShutdownHook(); System.out.println( "Finished comparison. Processed " + recordsProcessed + " records." ); } catch (RuntimeException rte) { System.err.println( "Got unexpected runtime exception: " + rte.getMessage() ); throw rte; } finally { // Cleanup, assuming we had resources to close. } } /** Set instance flag to tell everyone we're ready to exit. */ private synchronized void signalReadyToExit() { this.readyToExit = true; this.notify(); } /** * This is our demo method that allegedly is doing the * processing we want to protect. * Takes 1 second to append "ing" onto the given stringbuffer. * @param buffer the stringbuffer onto which to append "ing" */ private void addIngToString(StringBuffer buffer) { char[] toAppend = { 'i', 'n', 'g' }; for (int i = 0, j = toAppend.length; i < j; i++) { System.out.print( "About to add '" + toAppend[i] + "' to " + buffer ); sleep(333); // Sleep to simulate long processing buffer.append(toAppend[i]); System.out.println("...added"); } } /** * Registers a shutdown hook to pause shutdown after Control-C long enough * to finish processing the current record. */ private void registerShutdownHook() { System.out.println("Registering shutdown hook"); this.shutdownThread = new Thread("myhook") { public void run() { // For demonstration purposes: Don't give chance to // shutdown unless flag is set. Just show data. if (!runningInCleanMode) { showData(); return; } synchronized(this) { if (!readyToExit) { isVMShuttingDown = true; System.out.println("Shutdown hook: Waiting to exit"); try { // Wait up to 1.5 secs for a record to be processed. wait(1500); } catch (InterruptedException ignore) { } if (!readyToExit) { System.out.println( "Main processing interrupted." + " Data corruption possible." ); } } } showData(); // To demo current state of data. } /** * For demo purposes: Show data to see whether it is "corrupted" */ private void showData() { for (int i = 0, j = dataToProcess.length; i < j; i++) { System.err.println( "Record " + i + ": " + dataToProcess[i] ); } } }; Runtime.getRuntime().addShutdownHook(this.shutdownThread); } /** Unregister the shutdown hook. */ private void unregisterShutdownHook() { System.out.println("Unregistering shutdown hook"); try { Runtime.getRuntime().removeShutdownHook(this.shutdownThread); } catch (IllegalStateException ignore) { // VM already was shutting down } } /** * Sleeps for the given amount of milliseconds. * @param millisToSleep time to sleep in millis. */ private void sleep(int millisToSleep) { try { Thread.sleep(millisToSleep); } catch (InterruptedException ignore) { } } }