【问题标题】:Killing a bash script does not kill child processes杀死 bash 脚本不会杀死子进程
【发布时间】:2016-01-24 08:17:23
【问题描述】:

我编写了一个测试脚本,它运行另一个脚本来启动服务器进行测试。当测试完成时,SIGKILL 消息被发送到服务器进程,但是当再次运行测试脚本时,服务器抛出 EADDRINUSE 错误(我在 node.js 环境中),这意味着服务器的端口尝试挂载到当前正在使用中。我们试图用SIGKILL 杀死的进程仍在运行。我不认为这是一个特定于节点的问题,而是我缺乏对 bash 进程如何工作的教育。

这里有一些细节,这是我的启动脚本scripts/start-node.sh:

#!/bin/bash

node_modules/.bin/babel-node --stage 0 index.js

这是我的节点服务器 index.js(我还没有编写任何 process 事件监听器):

Http.createServer(…).listen(PORT, () => console.log(`Server listening on ${PORT}`))

并且启动脚本由节点child_process模块控制:

var child = child_process.spawn('scripts/start-node.sh')
// Later…
child.kill('SIGKILL')

【问题讨论】:

  • 你试过child.kill('SIGINT')可能节点不支持SIGKILL信号,然后会假设默认为:SIGTERM :nodejs.org/api/…
  • 我试过SIGINT、SIGKILL、SIGHUP、SIGQUIT和SIGTERM。根据我的阅读,SIGKILL 似乎是最符合语义的方式。
  • 用.close()定期关闭服务器而不是杀死进程不是更好吗?
  • 我没有看到 .close() 记录的 here
  • 不,我的意思是从你的服务器代码nodejs.org/api/net.html#net_server_close_callback关闭服务器

标签: node.js bash process child-process sigkill


【解决方案1】:

简单地说:

#!/bin/bash
if pgrep -x "node" > /dev/null
then
mv -f  /usr/local/bin/node /usr/local/bin/node.1
killall  node
mv -f /usr/local/bin/node.1 /usr/local/bin/node
which node
else
echo "process node not exists"
fi

每次我们杀死它时,节点都会创建子进程。所以不是 可以通过 kill、pkill 或 killall 命令杀死进程。所以 我们正在删除节点命令以使分叉过程失败,然后我们 杀死进程。最后我们恢复节点命令。

【讨论】:

    【解决方案2】:

    要杀死一个子进程及其所有子进程,您可以使用process.kill 和一个否定的pid(杀死一个进程组)

    var child = child_process.spawn('scripts/start-node.sh', {detached: true})
    // Later…
    process.kill(-child.pid, 'SIGKILL');
    

    查看child_processoptions.detached 文档的详细信息

    在非 Windows 上,如果设置了 detached 选项,子进程将成为新进程组和会话的领导者。

    这里引用man 2 kill 的一部分了解一些细节:

    如果 pid 小于 -1,则向进程组中 ID 为 -pid 的每个进程发送 sig。


    另一个选项可能是在您的 shell 脚本中使用 trap 来拦截信号并杀死所有子级,并使用来自 node 的 child.kill('SIGTERM')(因为 SIGKILL 不会被 trap 拦截)

    #!/bin/bash
    
    trap 'kill $(jobs -p)' EXIT
    node_modules/.bin/babel-node --stage 0 index.js
    

    【讨论】:

    • 好的,成功了,谢谢。虽然我更喜欢 bash 级别的解决方案,因为在使用像 pm2 这样的外部软件时会发生同样的错误。像 pm2 这样的软件应该使用负 pid 吗?如果是这样,我可以看看是否可以收到拉取请求。
    • @Calebmer,我添加了您可以在 shell 脚本级别使用的替代方案,您可能还想阅读How to propagate a signal through an arborescence of scripts ? Bash的第一个答案
    【解决方案3】:

    在您的服务器中有一个处理信号的处理程序

    server.js

     var http = require('http');
    
    function handleRequest(request, response){
        response.end("Hello");
    }
    var server = http.createServer(handleRequest);
    
    server.listen(3000, function(){console.log("listening...")});
    process.title="testserver"
    
    process.on('SIGQUIT', function() {
      server.close()
    });
    

    startup.sh

     #!/bin/bash
     node server.js & echo "started"
    

    然后有一个“shutdown.sh”。

    shutdown.sh

    #!/bin/bash
    pkill -QUIT testserver & echo "stoped"
    

    如果您在“start-node.sh”中生成多个进程,它将杀死所有具有该名称的进程。这样,您可以在关闭时进行一些清理代码,例如关闭所有连接等。

    在你的测试运行器中你可以做

    var exec = require('child_process').exec;
    exec("./start.sh")
    // later...
    exec("./stop.sh")
    

    【讨论】:

    • 这种方法的问题是我想使用一个 bash 脚本,以便它可以互操作。在开发中不断调用启动/停止脚本更加困难,我使用process manager,我认为它没有关闭脚本选项。
    • 那你为什么不直接require('pm2') 并从那里启动和停止服务器呢?
    • 顺便说一句,当它由 pm2 管理时,你不能杀死服务器,因为 pm2 在它被杀死时总是会为你重新启动服务器。最后这就是 pm2 的用途 ;-)
    • 因为require('pm2') 意味着我失去了一些我希望从 bash 脚本中获得的互操作性。此外,要使用 pm2 重新启动进程,它首先必须停止该进程然后重新启动它。
    • 我不清楚您是否在测试运行程序中使用 pm2。如果是这样,您不能从 pm2 之外终止该进程!如果没有,我上面给出的解决方案的问题在哪里? “在开发中不断调用启动/停止脚本更难”您不必调用启动和停止脚本,这只能由您的测试运行者完成
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-08
    • 1970-01-01
    • 2016-07-08
    • 2013-07-24
    相关资源
    最近更新 更多