简单示例,在我的平台上无效(Ruby 2.2
,Cygwin
):
#!/usr/bin/ruby
backtt = fork { exec('mintty','/usr/bin/zsh','-i') }
Process.detach(backtt)
exit
这个小程序(从shell启动时)应该跨越一个终端窗口(mintty),然后让我回到shell提示符。
然而,虽然它创建了薄荷窗口,但之后我没有shell提示符,并且我无法在调用shell中键入任何内容。
但是当我在分离之前引入一个小延迟时,要么使用' sleep',要么在stdout上打印一些内容,它会按预期工作:
#!/usr/bin/ruby
backtt = fork { exec('mintty','/usr/bin/zsh','-i') }
sleep 1
Process.detach(backtt)
exit
为什么这有必要?
顺便说一句,我很清楚我可以(来自shell)做一个mintty /usr/bin/zsh -i &
直接,或者我可以使用Ruby内部的系统(...... &
),但这不是重点。我特别感兴趣的是Ruby中的fork/exec/detach
行为。任何见解?
答案 0 :(得分:6)
发帖作为答案,因为评论时间过长
虽然我不是Ruby的专家,根本不了解Cygwin,但这种情况听起来非常我来自C / C ++。
此脚本太短,因此父级的父级完成,而孙子尝试启动。
如果你在分离后和退出前放置睡眠会怎样?
如果我的理论是正确的,它也应该有效。您的程序在任何(或足够的)线程切换发生之前退出。
我称这些问题为#34;中断了握手"。虽然这是心理学术语,但它描述了会发生什么。
睡眠"放弃时间片",导致线程切换,
控制台输出(任何文件I / O)运行到信号量,也导致线程切换。
如果我的想法是正确的,它也应该有用,如果你不睡觉",只计算到1e9(取决于计算的速度),因为那时抢占式多任务使得甚至线程切换本身也没有放弃CPU。
所以这是编程中的错误(恕我直言:竞争条件在这种情况下是哲学的),但很难找到"谁"负责。涉及的内容很多。
答案 1 :(得分:0)
Process::detach
通过设置单独的Ruby线程来阻止此,其唯一的工作是在进程终止时获取进程pid的状态。
注意:我无法在任何可用的操作系统上重现此行为,我只是为了格式化而将其作为答案发布。
由于Process.detach(backtt)
透明地创建一个线程,我建议你尝试:
#!/usr/bin/ruby
backtt = fork { exec('mintty','/usr/bin/zsh','-i') }
# ⇓⇓⇓⇓⇓
Process.detach(backtt).join
exit
这不是任何意思(与愚蠢的sleep
相反),因为您可能意识到底层命令应该立即返回或多或少。我不是cygwin中的大师,但它可能有一些线程的特定问题,所以,让这个线程得到处理。
答案 2 :(得分:0)
我既不是Ruby也不是Cygwin,所以我在这里提出的建议可能根本不起作用。无论如何:我猜,你甚至没有在这里遇到Ruby或Cygwin特定的bug。在一个名为" start"我多年前用C语言写过,我遇到了同样的问题。这是函数void daemonize_now()函数开头的注释:
/*
* This is a little bit trickier than I expected: If we simply call
* setsid(), it may fail! We have to fork() and exit(), and let our
* child call setsid().
*
* Now the problem: If we fork() and exit() immediatelly, our child
* will be killed before it ever had been run. So we need to sleep a
* little bit. Now the question: How long? I don't know an answer. So
* let us being killed by our child :-)
*/
所以,他的策略是:让父母等待它的孩子(这可以在孩子真正有机会做任何事之前立即完成),然后让孩子做分离部分。怎么样?让它创建一个新的进程组(它将被重新设置为init进程)。这就是我在评论中谈论的setsid()调用。它会像这样工作(C语法,你应该能够查找Ruby的正确用法并自己应用所需的更改):
parentspid = getpid();
Fork = fork();
if (Fork) {
if (Fork == -1) { // fork() failed
handle error
} else { // parent, Fork is the pid of the child
int tmp; waitpid(0, &tmp, 0);
}
} else { // child
if (setsid() == -1) {
handle error - possibly by doing nothing
and just let the parent wait ...
} else {
kill(parentspid, SIGUSR1);
}
exec(...);
}
您可以使用任何终止该过程的信号(即SIGKILL)。我使用了SIGUSR1并安装了一个退出(0)父进程的信号处理程序,因此调用者获得了成功消息。唯一需要注意的是:即使exec失败,你也会获得成功。然而,这是一个无法解决的问题,因为在成功的执行官之后,你不能再向父母发出任何信号。由于你不知道执行官什么时候会失败(如果失败了),你就会回到比赛条件部分。