【问题标题】:How can I run a long background process from a Perl CGI program?如何从 Perl CGI 程序运行较长的后台进程?
【发布时间】:2011-04-25 12:24:40
【问题描述】:

我遇到了长时间运行的 CGI 和超时错误的问题:

等待 CGI 脚本输出超时

客户端是一个用 jQuery 编写的表单。用户输入一些数据并接收到分析已启动的消息。分析数据后,除了带有链接的电子邮件外,用户不希望收到更多消息。 那么,此时,与客户端的连接就关闭了,对吧?

在服务器端,一个 Perl CGI 脚本获取数据并执行一些 C 程序(使用 Perl 的系统)来分析它们。此过程可能需要几秒钟到几小时不等,具体取决于输入的数据。

然后同一个 CGI 程序解析结果并向用户发送一封电子邮件,其中包含指向结果网页的链接。

由于对于某些数据,CGI 可以运行数小时我收到错误消息。

我假设增加 ScriptTimeout 是个坏主意。 我什至不确定是否安装了 mod_cgi。

我可以做些什么来避免这个错误?

服务器:在 Mac OS X 中运行的 Apache2。

【问题讨论】:

标签: perl timeout apache2 cgi


【解决方案1】:

我禁用了 STDOUT 缓冲,我尝试使用 system() 生成后台进程,我尝试了 fork() 和 exec() 的组合,我尝试使用后台子 shell 进程调用后台 bash shell,你可以命名它,但没有什么会允许父 CGI 进程打印输出,直到后台进程完成。

这最终对我有用:

print "Content-type: text/plain\n\n";
print "Executing background process...\n";

# fork this process
my $pid = fork();
die "Fork failed: $!" if !defined $pid;

if ($pid == 0) {
 # do this in the child
 open STDIN, "</dev/null";
 open STDOUT, ">/dev/null";
 open STDERR, ">/dev/null";
 system('bash -c \'(sleep 10; touch ./test_file)\'&');
 exit;
}

print "The background task will be finished shortly.\n";

诀窍是在子进程中关闭 STDERR(通过将其重新打开到 /dev/null)。如果不这样做,我们的内部网络服务器会认为父进程处于活动状态,直到子进程结束。此外,无缓冲的自动刷新输出不适用于我们的网络服务器,因此我无法打印任何类型的临时状态消息。

【讨论】:

    【解决方案2】:

    CGI 不应该自己做这项工作。相反,它应该简单地收集用户输入并立即完成,然后分派一个单独的程序离线完成工作。一种常见的解决方案是使用工作队列来存储来自用户的这些请求,而单独的程序会监听这个队列并根据请求执行工作。

    编辑:通常,会有一个一直在运行的守护进程来监听队列(例如,在我的 $work 中,我有一个使用 Beanstalk::Clientbeanstalkd 作为其作业队列的工作守护进程),但如果你如果作业很少添加,那么 cron 作业是一个很好的第一个实现。

    作为替代解决方案,您可以分叉您的 CGI 并在子进程中调用 exec 来启动您的工作程序:

    # there is work to be done; dispatch the worker script in a child process.
    my $pid = fork;
    exec "/path/to/worker/script.pl", $arg1, $arg2 if not $pid;
    
    # parent CGI is still alive; return an acknowledgement to the user and return.
    

    【讨论】:

    • 谢谢。我了解离线解决方案。我不确定如何实现它,也许有一个 cron 作业来检查该队列?但是,难道不能从 perl/CGI 执行另一个 perl 程序并在不等待的情况下终止吗?或者那个新的 perl 程序会被认为是另一个 CGI,所以它会产生另一个超时问题?
    • 谢谢以太。有用。子进程完成工作,但我仍然收到 CGI 超时错误。所以,我想我必须从父母那里发送一个终止信号?孩子?
    【解决方案3】:

    只是为了添加 cgi shell 脚本而不是 cgi perl 脚本的情况。通过在调用后台运行的脚本时将 stdout、stdin 和 stderr 重定向到 /dev/null 来执行 STDOUT 缓冲禁用:

    #!/bin/sh
    
    ./background.sh </dev/null >/dev/null 2> /dev/null
    

    background.sh 在后台调用脚本或程序的执行。例如。

    #background.sh
    # wait one minute and then log date/time in log.dat file
    sleep 60; echo date >> log.dat &
    

    在这种情况下,调用脚本会立即返回到调用 Web 服务器和网页,而等待一分钟的脚本是直到等待时间等待完成。最后将日期附加到 log.dat 文件中。

    【讨论】:

      【解决方案4】:

      在这些情况下,我调用了一个外部程序,它唯一的工作就是分叉进程。第一个进程可以与其子进程分离,并在实际工作继续进行时立即返回。

      您可能还会看到How can I fork a Perl CGI program to hive off long-running tasks?

      【讨论】:

      • 谢谢布赖恩。 Ehter 提出了相同的解决方案,对吧?关于守护进程,我无法让 Proc::Daemon 工作。所以,我在 CGI 中添加了两行:use Proc::Daemon;进程::守护进程::init();我用system来调用子进程吧?
      猜你喜欢
      • 2010-09-07
      • 2011-03-13
      • 2012-10-20
      • 1970-01-01
      • 2010-12-29
      • 1970-01-01
      • 1970-01-01
      • 2014-09-16
      • 1970-01-01
      相关资源
      最近更新 更多