/*
* Copyright 2008 the original author or authors.
*
* 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.rioproject.impl.exec.posix;
import org.rioproject.impl.exec.ProcessManager;
import org.rioproject.impl.exec.Util;
import org.rioproject.impl.util.FileUtils;
import org.rioproject.impl.util.StreamRedirector;
import org.rioproject.impl.system.OperatingSystemType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.URL;
/**
* A ProcessManager implementation for posix compliant systems
*
* @author Dennis Reedy
*/
public class PosixProcessManager extends ProcessManager {
private ProcessListener processListener;
private boolean terminated = false;
private File commandFile;
private String commandLine;
private static final String COMPONENT = PosixProcessManager.class.getPackage().getName();
private static final String KILL_SCRIPT="ps-kill-template.sh";
private static final String PROC_STATUS_SCRIPT="proc-status-template.sh";
static final Logger logger = LoggerFactory.getLogger(COMPONENT);
/**
* Create a PosixProcessManager
*
* @param process The {@link Process} the ProcessManager will manage
* @param pid The process ID of the started Process
* @param stdOutFileName The file name to redirect standard output to
* @param stdErrFileName The file name to redirect standard error to
*/
public PosixProcessManager(Process process,
int pid,
String stdOutFileName,
String stdErrFileName) {
super(process, pid);
handleRedirects(stdOutFileName, stdErrFileName);
}
/**
* Set the generated command file to delete
*
* @param commandFile The script that started the process this
* process manager is managing. Upon exit, delete this file
*/
public void setCommandFile(File commandFile) {
this.commandFile = commandFile;
}
/**
* Set the command line that was executed
*
* @param commandLine The command line that was executed, and as a result
* of it's execution this manager created. If set, this will be useful for
* logging purposes.
*/
public void setCommandLine(String commandLine) {
this.commandLine = commandLine;
}
/**
* Manage the Process
*
* @throws java.io.IOException if the process management utility cannot be created
*/
public void manage() throws IOException {
processListener = new ProcessListener();
processListener.start();
logger.info("Managing process ["+getPid()+"], command ["+commandLine+"]");
}
/**
* Destroy the managed process
*
* @param includeChildren If true, destroy all child processes as well.
* This method will look for all child processes that have a parent process
* ID of the managed process and forcibly terminate them.
*/
public void destroy(boolean includeChildren) {
if(terminated)
return;
try {
if(includeChildren) {
File killFile = genKillFile();
Util.runShellScript(killFile);
} else {
getProcess().destroy();
}
if(processListener!=null)
processListener.interrupt();
} catch (IOException e) {
logger.warn("Could not completely terminate process and process children, will attempt to close stdout and stderr",
e);
}
try {
getProcess().waitFor();
if(processListener!=null)
processListener.cleanFiles();
} catch (InterruptedException e) {
logger.warn("process.waitFor() was interrupted, continuing");
}
if (outputStream != null)
outputStream.interrupt();
if (errorStream != null)
errorStream.interrupt();
Util.close(getProcess().getOutputStream());
Util.close(getProcess().getInputStream());
Util.close(getProcess().getErrorStream());
getProcess().destroy();
if(commandFile!=null) {
if(commandFile.delete()) {
if(logger.isTraceEnabled()) {
logger.trace("Command file [{}] for [{}], command [{}]", commandFile.getName(), getPid(), commandLine);
}
}
}
terminated = true;
}
private File genProcStatusScript(File procStatusFile) throws IOException {
int pid = getPid();
File procStatusScript = new File(System.getProperty("java.io.tmpdir"),
"proc-status-"+pid+".sh");
procStatusScript.deleteOnExit();
URL url = Util.getResource(PROC_STATUS_SCRIPT);
String sPid = Integer.toString(pid);
String processStatusFile = FileUtils.getFilePath(procStatusFile);
StringBuilder sb = new StringBuilder();
String str;
BufferedReader in =
new BufferedReader(new InputStreamReader(url.openStream()));
while ((str = in.readLine()) != null) {
str = Util.replace(str, "${pid}", sPid);
str = Util.replace(str, "${process_status_file}", processStatusFile);
sb.append(str).append("\n");
}
in.close();
Util.writeFile(sb.toString(), procStatusScript);
Util.chmodX(procStatusScript);
return procStatusScript;
}
private File genKillFile() throws IOException {
int pid = getPid();
File killFile = new File(System.getProperty("java.io.tmpdir"),
"ps-kill-"+pid+".sh");
killFile.deleteOnExit();
URL url = Util.getResource(KILL_SCRIPT);
String sPid = Integer.toString(pid);
StringBuilder sb = new StringBuilder();
String str;
BufferedReader in =
new BufferedReader(new InputStreamReader(url.openStream()));
String psOptions = "xo";
if(OperatingSystemType.isSolaris())
psOptions = "-e -o";
while ((str = in.readLine()) != null) {
str = Util.replace(str, "${psOptions}", psOptions);
str = Util.replace(str, "${pid}", sPid);
sb.append(str).append("\n");
}
in.close();
Util.writeFile(sb.toString(), killFile);
Util.chmodX(killFile);
return killFile;
}
/**
* Waits for a process to exit
*/
class ProcessListener extends Thread {
File procStatusFile;
File procStatusScript;
ProcessListener() throws IOException {
procStatusFile = new File(System.getProperty("java.io.tmpdir"),
"proc-stat-"+getPid());
procStatusScript = genProcStatusScript(procStatusFile);
procStatusScript.deleteOnExit();
Util.runShellScript(procStatusScript, false);
}
void cleanFiles() {
if(procStatusScript!=null) {
if(procStatusScript.delete()) {
if(logger.isDebugEnabled())
logger.debug("Process status script for pid [{}], command [{}] deleted", getPid(), commandLine);
}
}
if(procStatusFile!=null)
if(procStatusFile.delete()) {
if(logger.isDebugEnabled())
logger.debug("Process stats file for pid [{}], command [{}] deleted", getPid(), commandLine);
}
}
public void run() {
while(!isInterrupted()) {
BufferedReader in = null;
try {
if(!procStatusFile.exists()) {
continue;
}
in = new BufferedReader(new FileReader(procStatusFile));
String s = in.readLine();
if(s==null)
continue;
//System.out.println("process ["+pid+"] status="+s);
if(s!=null) {
if(Integer.parseInt(s)!=0) {
if(logger.isDebugEnabled()) {
logger.debug("Process status for pid [{}], command [{}] is: {}",
getPid(), commandLine, Integer.parseInt(s));
}
break;
}
}
} catch (IOException e) {
logger.warn("Non fatal exception trying to read " +
"process status file "+
e.getClass().getName()+": "+e.getMessage());
} finally {
if(in!=null)
try {
in.close();
} catch (IOException e) {
if(logger.isTraceEnabled()) {
logger.trace("Problem closing "+procStatusFile.getName(), e);
}
}
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
if(logger.isTraceEnabled()) {
logger.trace("Interrupted", e);
}
}
}
if(logger.isDebugEnabled())
logger.info("Process ["+getPid()+"] terminated for command ["+commandLine+"]");
notifyOnTermination();
if(procStatusFile.delete() && logger.isDebugEnabled())
logger.debug("Process stats file [{}] removed for command [{}]", getPid(), commandLine);
}
}
}