private int executeInternal(final CommandLine command, final Map<String, String> environment,
final File dir, final ExecuteStreamHandler streams) throws IOException {
setExceptionCaught(null);
final Process process = this.launch(command, environment, dir);
try {
streams.setProcessInputStream(process.getOutputStream());
streams.setProcessOutputStream(process.getInputStream());
streams.setProcessErrorStream(process.getErrorStream());
} catch (final IOException e) {
process.destroy();
throw e;
}
streams.start();
try {
// add the process to the list of those to destroy if the VM exits
if (this.getProcessDestroyer() != null) {
this.getProcessDestroyer().add(process);
}
// associate the watchdog with the newly created process
if (watchdog != null) {
watchdog.start(process);
}
int exitValue = Executor.INVALID_EXITVALUE;
try {
exitValue = process.waitFor();
/**
这里的process是在VmsCommandLancher启动的。
这里的process.waitFor()有两个作用:
1. 接受线程的interrupted,在这里抛出异常;
2. 执行的命令缓冲区满,在这里抛出异常;
所以xxljob停止任务这里会抛出异常
*/
} catch (final InterruptedException e) {
/**
xxljob终止任务会进入这里,调用process方法。
destroy()方法将终止命令的执行。
*/
process.destroy();
}
finally {
// see http://bugs.sun.com/view_bug.do?bug_id=6420270
// see https://issues.apache.org/jira/browse/EXEC-46
// Process.waitFor should clear interrupt status when throwing InterruptedException
// but we have to do that manually
Thread.interrupted();
}
if (watchdog != null) {
watchdog.stop();
}
try {
streams.stop();
}
catch (final IOException e) {
setExceptionCaught(e);
}
closeProcessStreams(process);
if (getExceptionCaught() != null) {
throw getExceptionCaught();
}
if (watchdog != null) {
try {
watchdog.checkException();
} catch (final IOException e) {
throw e;
} catch (final Exception e) {
throw new IOException(e.getMessage());
}
}
if (this.isFailure(exitValue)) {
throw new ExecuteException("Process exited with an error: " + exitValue, exitValue);
}
return exitValue;
} finally {
// remove the process to the list of those to destroy if the VM exits
if (this.getProcessDestroyer() != null) {
this.getProcessDestroyer().remove(process);
}
}
}
问题描述
发布日,发现xxljob上点击终止任务无效。
问题分析
首先查看xxljob上被终止的任务类型发现是GLUE(SHELL),且shell代码为:
那么分析下xxljob调度器和执行器的这一类型任务的源码即可。
终止任务原理
执行器调用kill方法:
return new ReturnT(ReturnT.SUCCESS_CODE, "job thread aleady killed.");
}
下面分析GLUE(SHELL)类型的任务是如何启动的。 执行任务步骤:
下面来分析下ScriptJobHandler,为什么无法被终止掉。ScriptJobHandler将具体执行的逻辑委托给了ScriptUtil执行,看下关键代码:
再分析下DefaultExecutor的exec方法:
而Process.destroy()的具体实现类在不同平台是不一致的,但最终都是调用native方法: terminateProcess(long handle)。
linux:
windows:
这两个API按理说是能正常终止掉程序的,那为什么失败呢。要么是kill、TerminateProcess被hook了,不能正常用,要么就是没权限(可以排除,子进程都是从该Java进程类生的)。
那就只能怀疑PID不正确了,看下启动kettle启动脚本:
上面27行,调了其他脚本,最终会走到kettle的spoon.sh上,该脚本又会启动一个Java进程。
真相大白,xxljob kill掉的是shell进程,shell启动的Java进程还跑的好好的呢。
为何 Jenkins 可以终止子进程
Jenkins 中大家一定熟悉 DONTKILLME 这个环境变量,默认情况下,Jenkins 会杀死所有派生的进程。那么 Jenkins 是如何实现的呢? Jenkins 在内部维护了一套进程数(ProcessTree.java),杀死父进程的时候,会遍历父进程的所有子进程一一 kill,遍历的方法则是扫描进程的环境变量。
如何获取进程环境变量? Windows -> 通过 winp 封装好的接口,底层是 cpp; Linux -> cat /proc/pid/environ;
总结