【问题标题】:Perl send ajax response then exec()Perl 发送 ajax 响应然后 exec()
【发布时间】:2012-04-01 19:35:58
【问题描述】:

Perl 脚本需要接收 ajax 请求,将“成功”消息发送回浏览器,然后“生成”一个进程在后台运行。 (这个生成的进程可能需要 10 分钟才能运行,所以我不希望浏览器等待 - 或者发生 apache 超时)

我的印象是 Perl 的 exec() 函数可以做到这一点。比使用 fork() 或 system() 更好,因为它们应该等待响应。

但是,在将成功消息发送到浏览器之前,脚本似乎正在等待 exec() 程序完成。这是我的 Perl 脚本的最后部分:

print "Content-type: text/html\n\n";
print "success"; # this gets returned via ajax

exec $script_filename, $var1, $var2, $var3; 

一切正常,但浏览器在 $script_filename 完成运行之前没有收到“成功”消息。

我想做的似乎类似于this previous post,但那里没有足够的细节。

有什么想法吗?谢谢。

【问题讨论】:

  • fork() 不会“等待响应”。

标签: ajax perl exec


【解决方案1】:

Apache 将在完成对浏览器的响应之前等待您的脚本完成。您应该安排一个作业在后台运行,然后让您的脚本终止。

为了安排后台作业,您可以将后台任务处理器实现为守护进程,该守护进程在执行任务之前从您的 perl 脚本中通过 IPC(例如,通过 UNIX 域套接字)接受任务。或者,您可以使用现有的 UNIX 工具进行后台作业调度,例如 atbatch。详情请见this manpage

如果您需要非常高的可靠性,您可能还希望在向客户端确认任务已安排之前将任务持久化到外部存储。

【讨论】:

  • +1 表示“Apache 将等待”(但我不太确定其余部分)。使用fork() 并让子进程运行后台进程并让父进程退出(因此 Apache 停止等待)将解决问题。您不必使用复杂的调度(batchat); fork() 已经足够了。它可能有优势;分叉的孩子具有可以简化后台处理设置的上下文。
  • 你是否建议这样的事情:$SIG{CHLD} = 'IGNORE';已定义 (my $kid = fork) 或死 "Cannot fork: $!\n"; if ($kid) { print "父母已经完成\n"; } else { # 做一些耗时的事情 }
  • @JonathanLeffler 从 OP 的代码看来,其意图是运行外部命令/脚本。这可以通过 at/batch 轻松完成,并确保更大的隔离(想想信号、进程组、文件描述符等)。当需要来自父脚本的上下文时,fork() 可能会有优势。
  • @egret 好吧,你至少也应该关闭 STDIN、STDOUT 和 STDERR。
  • 文件流/描述符很重要(应该重定向到 /dev/null - 而不是仅仅关闭)。如果孩子需要执行另一个进程,则没有大问题。它(在做exec 之前的孩子)可以根据需要创建自己的进程组。如果愿意,它可以设置自己的信号处理程序;我不相信有任何无法克服的问题。同样,如果该过程需要 10 分钟才能运行,那么使用 batchat 不会有任何真正的效率问题。
【解决方案2】:

我找到了另一个 StackOverflow 问题的答案,似乎可以解决我的问题 - 请参阅 Paul Tomblin's response here

似乎如果 perl 脚本执行 exec(),那么 apache 会等待。但是如果 perl 脚本先执行 fork,然后 fork 执行 exec(),那么一切都会按预期进行。

这是我的代码中的样子,替换了我原来帖子中的内容:

use POSIX 'setsid';

$SIG{CHLD} = 'IGNORE';

# This should flush stdout.
my $ofh = select(STDOUT);$| = 1;select $ofh;

my $kpid = fork;
if ($kpid)
{
    # Parent process
    print "Content-type: text/html\n\n";
    print "success"; # this gets returned via ajax
}
else
{
    close STDIN;
    close STDOUT;
    close STDERR;
    setsid();
    my $gpid = fork;
    if (!$gpid)
    {
        open(STDIN, "</dev/null") ;
        open(STDOUT, ">/dev/null") ;
        open(STDERR, ">/dev/null") ;
        # Child process
        exec($script_filename, $var1, $var2, $var3) ;
    }
    exit 0;
}

感谢 Adam 立即确认我最初尝试做的事情有缺陷。

感谢乔纳森为我指明了正确的方向。

【讨论】:

  • 恭喜修复!如果有能力,请确保将您的答案标记为“已接受”,以便其他人可以从您的成功中学习。干杯~
  • 我会的。这让我等了 8 个小时。
猜你喜欢
  • 1970-01-01
  • 2021-10-13
  • 2015-08-24
  • 1970-01-01
  • 1970-01-01
  • 2020-07-25
  • 2012-04-24
  • 2018-09-18
  • 1970-01-01
相关资源
最近更新 更多