/* * Copyright (c) 2009, Paul Merlin. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.swing.on.steroids.forking; import java.io.IOException; import java.io.OutputStream; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import org.codeartisans.java.toolbox.async.CallbackWithE; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public final class DefaultForkService implements ForkService { private static final Logger LOGGER = LoggerFactory.getLogger( DefaultForkService.class ); private CopyOnWriteArrayList<Process> processToKillOnShutdown; private boolean shutdownHookAdded = false; private final ThreadGroup threadGroup; public DefaultForkService() { threadGroup = new ThreadGroup( "ForkServiceThreads" ); } @Override public void fork( Forkable forkable, ShutdownAction shutdownAction ) { fork( forkable, null, shutdownAction, null, null ); } @Override public void fork( Forkable forkable, ShutdownAction shutdownAction, OutputStream output ) { fork( forkable, null, shutdownAction, output, output ); } @Override public void fork( Forkable forkable, ShutdownAction shutdownAction, OutputStream stdOut, OutputStream errOut ) { fork( forkable, null, shutdownAction, stdOut, errOut ); } @Override public void fork( Forkable forkable, CallbackWithE<Void, ForkFault> exitCallback, ShutdownAction shutdownAction ) { fork( forkable, exitCallback, shutdownAction, null, null ); } @Override public void fork( Forkable forkable, CallbackWithE<Void, ForkFault> exitCallback, ShutdownAction shutdownAction, OutputStream output ) { fork( forkable, exitCallback, shutdownAction, output, output ); } @Override public void fork( final Forkable forkable, final CallbackWithE<Void, ForkFault> exitCallback, final ShutdownAction shutdownAction, OutputStream stdOut, OutputStream errOut ) { try { final ProcessBuilder builder = new ProcessBuilder( forkable.command() ); builder.directory( forkable.workingDirectory() ); final Map<String, String> environment = forkable.environment(); if ( environment != null ) { builder.environment().putAll( environment ); } final Process proc = builder.start(); final StreamGobbler stderr = new StreamGobbler( proc.getErrorStream(), threadGroup, forkable.name() + "-STDERR", errOut ); final StreamGobbler stdout = new StreamGobbler( proc.getInputStream(), threadGroup, forkable.name() + "-STDOUT", stdOut ); stderr.setDaemon( true ); stdout.setDaemon( true ); stderr.start(); stdout.start(); final Thread watcher = new Thread( threadGroup, forkable.name() + "-Watcher" ) { @Override public void run() { try { beforeProcessWaitFor(); proc.waitFor(); afterProcessWaitFor(); callback(); interrupt(); } catch ( InterruptedException iex ) { ForkFault ex = new ForkFault( "Fork Watcher " + forkable.name() + "interrupted!", iex ); if ( exitCallback != null ) { exitCallback.onError( ex.getMessage(), ex ); } else { LOGGER.error( ex.getMessage(), ex ); } } } private void beforeProcessWaitFor() { if ( shutdownAction == ShutdownAction.KILL ) { ensureShutdownHook(); addKillOnShutdownProcess( proc ); } } private void afterProcessWaitFor() { stderr.interrupt(); stdout.interrupt(); if ( shutdownAction == ShutdownAction.KILL ) { removeKillOnShutdownProcess( proc ); } } private void callback() { int status = proc.exitValue(); if ( status == 0 ) { if ( exitCallback != null ) { exitCallback.onSuccess( null ); } } else { if ( exitCallback != null ) { exitCallback.onError( "Fork " + forkable.name() + " exited with error, status was: " + status, null ); } } } }; watcher.setDaemon( true ); watcher.start(); } catch ( IOException ex ) { throw new ForkFault( "Unable to fork: " + ex.getMessage(), ex ); } } private synchronized void ensureShutdownHook() { if ( !shutdownHookAdded ) { Runtime.getRuntime().addShutdownHook( new Thread( threadGroup, new Runnable() { @Override public void run() { if ( processToKillOnShutdown != null ) { for ( Process eachProcessToKill : processToKillOnShutdown ) { eachProcessToKill.destroy(); LOGGER.debug( "Forked process destroyed: " + eachProcessToKill.toString() ); } } } }, getClass().getSimpleName() + ".ShutdownHook" ) ); shutdownHookAdded = true; } } private void addKillOnShutdownProcess( Process proc ) { if ( processToKillOnShutdown == null ) { processToKillOnShutdown = new CopyOnWriteArrayList<Process>(); } processToKillOnShutdown.add( proc ); } private void removeKillOnShutdownProcess( Process proc ) { if ( processToKillOnShutdown != null ) { processToKillOnShutdown.remove( proc ); } } }