Igo*_*nko 13 javascript windows process node.js gulp
这个问题与我最近以一个可怕的hack™ 关闭的另一个问题有关.
我正在尝试编写一个脚本,可以在CI /构建管道的上下文中使用一个步骤.
该脚本应该为我们的Angular单页面应用程序(SPA)运行基于Protractor的端到端测试.
该脚本需要执行以下操作(按顺序):
- 运行名为"App"的.NET Core微服务
- 运行名为"Web"的.NET Core微服务
- 运行SPA
- 运行一个执行Protractor测试的命令
- 在步骤4完成后(成功或出错),终止在步骤1-3中创建的进程.这绝对是必要的,否则构建将永远不会在CI中完成和/或将会出现僵尸Web/App/SPA进程,这将破坏未来的构建管道执行.
我还没有开始处理第4步("e2e测试"),因为我真的想确保第5步("清理")按预期工作.
正如你猜的那样(右),清理步骤不起作用.具体来说,进程"App"和"Web"不会因某种原因而被杀死并继续运行.
顺便说一句,我确保我的gulp脚本以提升(管理员)权限执行.
我刚刚发现问题的直接原因(我认为),我不知道究竟是什么原因.按照我的预期,有5个进程而不是1个进程.例如,对于App流程,在流程管理器中观察到以下流程:
{
"id": 14840,
"binary": "cmd.exe",
"title": "Console"
},
{
"id": 12600,
"binary": "dotnet.exe",
"title": "Console"
},
{
"id": 12976,
"binary": "cmd.exe",
"title": "Console"
},
{
"id": 5492,
"binary": "cmd.exe",
"title": "Console"
},
{
"id": 2636,
"binary": "App.exe",
"title": "Console"
}
Run Code Online (Sandbox Code Playgroud)
同样,为Web服务创建了五个进程而不是一个进程:
{
"id": 13264,
"binary": "cmd.exe",
"title": "Console"
},
{
"id": 1900,
"binary": "dotnet.exe",
"title": "Console"
},
{
"id": 4668,
"binary": "cmd.exe",
"title": "Console"
},
{
"id": 15520,
"binary": "Web.exe",
"title": "Console"
},
{
"id": 7516,
"binary": "cmd.exe",
"title": "Console"
}
Run Code Online (Sandbox Code Playgroud)
基本上,这里的工作马是runCmdAndListen()通过运行cmd提供的参数来旋转进程的函数.当函数启动一个进程是Node.js的手段时exec(),它会被推送到createdProcesses数组进行跟踪.
被调用的Gulp步骤CLEANUP = "cleanup"负责迭代createdProcesses和调用.kill('SIGTERM')它们中的每一个,这应该杀死之前创建的所有进程.
gulpfile.js (Gulp任务脚本)const gulp = require('gulp');
const exec = require('child_process').exec;
const path = require('path');
const RUN_APP = `run-app`;
const RUN_WEB = `run-web`;
const RUN_SPA = `run-spa`;
const CLEANUP = `cleanup`;
const appDirectory = path.join(`..`, `App`);
const webDirectory = path.join(`..`, `Web`);
const spaDirectory = path.join(`.`);
const createdProcesses = [];
Run Code Online (Sandbox Code Playgroud)
runCmdAndListen()/**
* Runs a command and taps on `stdout` waiting for a `resolvePhrase` if provided.
* @param {*} name Title of the process to use in console output.
* @param {*} command Command to execute.
* @param {*} cwd Command working directory.
* @param {*} env Command environment parameters.
* @param {*} resolvePhrase Phrase to wait for in `stdout` and resolve on.
* @param {*} rejectOnError Flag showing whether to reject on a message in `stderr` or not.
*/
function runCmdAndListen(name, command, cwd, env, resolvePhrase, rejectOnError) {
const options = { cwd };
if (env) options.env = env;
return new Promise((resolve, reject) => {
const newProcess = exec(command, options);
console.info(`Adding a running process with id ${newProcess.pid}`);
createdProcesses.push({ childProcess: newProcess, isRunning: true });
newProcess.on('exit', () => {
createdProcesses
.find(({ childProcess, _ }) => childProcess.pid === newProcess.pid)
.isRunning = false;
});
newProcess.stdout
.on(`data`, chunk => {
if (resolvePhrase && chunk.toString().indexOf(resolvePhrase) >= 0) {
console.info(`RESOLVED ${name}/${resolvePhrase}`);
resolve();
}
});
newProcess.stderr
.on(`data`, chunk => {
if (rejectOnError) reject(chunk);
});
if (!resolvePhrase) {
console.info(`RESOLVED ${name}`);
resolve();
}
});
}
Run Code Online (Sandbox Code Playgroud)
gulp.task(RUN_APP, () => runCmdAndListen(
`[App]`,
`dotnet run --no-build --no-dependencies`,
appDirectory,
{ 'ASPNETCORE_ENVIRONMENT': `Development` },
`Now listening on:`,
true)
);
gulp.task(RUN_WEB, () => runCmdAndListen(
`[Web]`,
`dotnet run --no-build --no-dependencies`,
webDirectory,
{ 'ASPNETCORE_ENVIRONMENT': `Development` },
`Now listening on:`,
true)
);
gulp.task(RUN_SPA, () => runCmdAndListen(
`[SPA]`,
`npm run start-prodish-for-e2e`,
spaDirectory,
null,
`webpack: Compiled successfully
`,
false)
);
gulp.task(CLEANUP, () => {
createdProcesses
.forEach(({ childProcess, isRunning }) => {
console.warn(`Killing child process ${childProcess.pid}`);
// if (isRunning) {
childProcess.kill('SIGTERM');
// }
});
});
Run Code Online (Sandbox Code Playgroud)
gulp.task(
'e2e',
gulp.series(
gulp.series(
RUN_APP,
RUN_WEB,
),
RUN_SPA,
CLEANUP,
),
() => console.info(`All tasks complete`),
);
gulp.task('default', gulp.series('e2e'));
Run Code Online (Sandbox Code Playgroud)
dotnet run不会将终止信号传播给 Windows 中的子进程(这是 Windows 中的行为,与 POSIX 操作系统不同),您正在做正确的事情,即管理子进程SIGTERM不适用于 Windows:nodejs doc process_signal_events;那是你的问题。你可能想尝试一下SIGKILL。process.kill(process.pid, SIGKILL)可能需要测试:nodejs issues 12378。对于可靠的经 MSFT 测试的解决方案(但不是跨平台),请考虑使用 powershell 来管理树,这要归功于以下 powershell 功能:
function startproc($mydotnetcommand)
{
$parentprocid = Start-Process $mydotnetcommand -passthru
}
function stopproctree($parentprocid)
{
$childpidlist= Get-WmiObject win32_process |`
where {$_.ParentProcessId -eq $parentprocid}
Get-Process -Id $childpidlist -ErrorAction SilentlyContinue |`
Stop-Process -Force
}
Run Code Online (Sandbox Code Playgroud)
您可以通过将父 PID 作为参数传递给函数,从 ps 脚本外部使用第二个函数stopproctree:
param([Int32]$parentprocid)
stopproctree $parentprocid
Run Code Online (Sandbox Code Playgroud)
(在脚本中,例如treecleaner.ps1),然后powershell.exe -file treecleaner.ps1 -parentprocid XXX
| 归档时间: |
|
| 查看次数: |
268 次 |
| 最近记录: |