/* * Copyright (C) 2011 Laurent Caillette * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.novelang.outfit.shell.insider; import java.lang.management.ManagementFactory; import java.util.concurrent.atomic.AtomicLong; import static java.lang.System.currentTimeMillis; /** * Runs inside the JVM launched by {@link org.novelang.outfit.shell.JavaShell}. * * @author Laurent Caillette */ @SuppressWarnings( { "JavadocReference", "UseOfSystemOutOrSystemErr" } ) public class LocalInsider implements Insider { private final AtomicLong keepaliveCounter ; private final String virtualMachineName ; private final Thread heartbeatReceiver ; public LocalInsider() { this( HEARTBEAT_FATAL_DELAY_MILLISECONDS ) ; } @SuppressWarnings( { "CallToThreadStartDuringObjectConstruction" } ) public LocalInsider( final long delay ) { virtualMachineName = ManagementFactory.getRuntimeMXBean().getName() ; printStandard( "Initializing " + getClass().getSimpleName() + " " + "with: " + "virtualMachineName=" + virtualMachineName + ", " + "fatalHeartbeatDelay=" + delay + " milliseconds..." ) ; keepaliveCounter = new AtomicLong( currentTimeMillis() ) ; heartbeatReceiver = new Thread( new Runnable() { @Override public void run() { printStandard( "Started keepalive watcher from thread " + Thread.currentThread() + " inside " + virtualMachineName + "." ) ; // Reset the counter for synchronization with thread start time. keepAlive() ; while( true ) { try { Thread.sleep( delay ) ; final long lag = currentTimeMillis() - keepaliveCounter.get() ; if( lag > delay ) { printError( "No heartbeat for more than " + delay + " milliseconds, " + "halting " + virtualMachineName + " with status of " + STATUS_HEARTBEAT_PERIOD_EXPIRED + "." ) ; Runtime.getRuntime().halt( STATUS_HEARTBEAT_PERIOD_EXPIRED ) ; break ; // Avoid a compilation warning because of infinite loop. } } catch( InterruptedException ignore ) { } } } } ,getClass().getSimpleName() + "-HeartbeatReceiver" ); heartbeatReceiver.setDaemon( true ) ; } /** * Call this method only after JMX registration happened. Otherwise there can be timing problems, * especially when running tests in parallel. */ @Override public void startWatchingKeepalive() { heartbeatReceiver.start() ; } /** * Shortcut method for getting value returned by * {@link java.lang.management.RuntimeMXBean#getName()}. */ protected final String getVirtualMachineName() { return virtualMachineName ; } /** * Performs shutdown in a separate thread to let the {@link #shutdown()} method return. */ @Override public void shutdown() { heartbeatReceiver.interrupt() ; // Might avoid messy error messages in extreme cases. new Thread( new Runnable() { @Override public void run() { System.exit( 0 ) ; } }, LocalInsider.class.getSimpleName() ).start() ; } @Override public void keepAlive() { // printOut( "Received keepalive call at " + currentTimeMillis() + "." ) ; keepaliveCounter.set( currentTimeMillis() ) ; } @Override public boolean isAlive() { return true ; } @Override public void printStandard( final String message ) { System.out.println( createTimestamp() + message ) ; System.out.flush() ; } @Override public void printError( final String message ) { System.out.println( createTimestamp() + message ) ; System.err.flush() ; } private static String createTimestamp() { // return new SimpleDateFormat( "HH:mm:ss,SSS " ).format( new Date() ); return "" ; } }