Mojolicous:限制数量的承诺/ IOLoop->子流程

时间:2018-09-18 19:23:18

标签: multithreading perl promise subprocess mojolicious

我正在使用Mojolicious非阻塞方法(Promise)从外部系统请求数据。 1)我想立即通知用户该过程已经开始; 2)我想扩展这个程序。

下面的代码适用于少量数字(几百个),具有更多数字,我会收到一个错误[error] Can't create pipe: Too many open files at /path/lib/perl5/Mojo/IOLoop.pm line 156. 问题1 )如何限制我承诺的数量产生(以下代码中的map

#!/usr/bin/env perl

use Mojolicious::Lite;
use Mojolicious::Plugin::TtRenderer;

sub isPrime
{
    my ($n) = @_;
    my $e = sqrt($n);
    for (my $i=2; $i<$e; $i++) {
        return 0 if $n%$i==0;
    }
    return 1;
}

sub makeApromise
{
    my ($number) = @_;

    my $promise = Mojo::Promise->new;
    Mojo::IOLoop->subprocess(
    sub {  # first callback is executed in subprocess
        my %response;
        # Simulate a long computational process
        $response{'number'}  = $number;
        $response{'isPrime'} = isPrime($number);
        return \%response;
    },
        sub {  # second callback resolves promise with subprocess result
            my ($self, $err, @result) = @_;
            return $promise->reject($err) if $err;
            $promise->resolve(@result);
        },
    );
    return $promise;
}

plugin 'tt_renderer'; # automatically render *.html.tt templates

any '/' => sub {
    my ($self) = @_;
    my $lines = $self->param( 'textarea' );

    if ($lines) {
    my @numbers;
    foreach my $number (split(/\r?\n/, $lines)) {
        push(@numbers, $number) if $number =~ /^\d+$/;
    }
    if (@numbers) {
        ####################################
        ### This is the problem below... ###
        my @promises = map { makeApromise($_) } @numbers;
        ####################################
        # MojoPromise Wait
        Mojo::Promise->all(@promises)
        ->then(sub {
            my @values = map { $_->[0] } @_;
            foreach my $response (@values) {
            #print STDERR $response->{'number'}, " => ", $response->{'isPrime'}, "\n";
            # Prepare email...
            }
            # Send an email...
               })
        #->wait # Don't wait? I want to tell the user to wait for an email as quickly as possible...
        if @promises;
    }
    $self->stash(done => "1",);
    }
    $self->render(template => 'index', format => 'html', handler => 'tt');
};

app->start;
__DATA__

@@ index.html.tt
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Make A Promise</title>
  </head>
  <body>
    [% IF done %]
    <h3>Thank you! You will receive an email shortly with the results.</h3>
    [% ELSE %]
    <h3>Enter numbers...</h3>
    <form role="form" action="/" method="post">
      <textarea name="textarea" rows="5" autofocus required></textarea>
      <button type="submit">Submit</button>
    </form>
    [% END %]
  </body>
</html>

我注释了wait;但是,似乎代码仍在阻塞。 问题2 )如何立即通知用户该过程已经开始? (即当我stash done变量时)

1 个答案:

答案 0 :(得分:1)

问题不是承诺的数量,而是子流程的数量。一种限制方法是简单地限制一次在程序逻辑中创建的数量。设置一个限制并从@numbers中检索很多(也许使用splice),然后产生这些子过程,而不是一次在地图中产生它们。创建一个-> allpromise并等待它们,然后在该Promise上附加-> then以检索下一个数字,依此类推。

另一种选择是使用Future::Utils fmap_concat,它可以通过提供一些最大未偿还期货来处理限速代码。您的诺言兑现函数可以应用Mojo::Promise::Role::Futurify来链接后面的Future以这种方式使用。

#!/usr/bin/env perl

use Mojolicious::Lite;
use Mojo::File 'path';
use Mojo::IOLoop;
use Mojo::Promise;
use Future::Utils 'fmap_concat';

get '/' => sub {
  my $c = shift;
  my $count = $c->param('count') // 0;
  my @numbers = 1..$count;

  if (@numbers) {
    my $result_f = fmap_concat {
      my $number = shift;
      my $p = Mojo::Promise->new;
      Mojo::IOLoop->subprocess(sub {
        sleep 2;
        return $number+1;
      }, sub {
        my ($subprocess, $err, @result) = @_;
        return $p->reject($err) if $err;
        $p->resolve(@result);
      });
      return $p->with_roles('Mojo::Promise::Role::Futurify')->futurify;
    } foreach => \@numbers, concurrent => 20;

    $result_f->on_done(sub {
      my @values = @_;
      foreach my $response (@values) {
        $c->app->log->info($response);
      }
    })->on_fail(sub {
      my $error = shift;
      $c->app->log->fatal($error);
    })->retain;

    $c->stash(done => 1);
  }
  $c->render(text => "Processing $count numbers\n");
};

app->start;

对于wait方法,如果事件循环已在运行,则此操作将不执行任何操作(如果您在Mojolicious守护程序中启动应用程序,则该事件将在webapp响应处理程序中运行)不支持异步响应)。设置子流程后,将立即运行回调之外的-> stash和-> render调用。然后响应处理程序将完成,并且事件循环将再次获得控制,这将在Promise解决后触发相应的-> then回调。渲染不应等待设置子流程之外的任何事情;由于您说的可能有数百个,这可能是您遇到的速度下降。确保您使用的是Mojolicious 7.86或更高版本,因为Subprocess已更改,因此在事件循环的下一个刻度(响应处理程序完成之后)才会发生派生。

我还将注意到,子流程并不是真正为此设计的;它们是为执行缓慢的代码而设计的,该代码仍会在响应中将最终结果返回给浏览器(Mojolicious::Plugin::Subprocess对于此用例而言非常有用)。我可以看到的一个问题是,如果重新启动应用程序,则任何仍在等待处理的子进程都将被忽略。对于您想起而忘却的工作,您可以考虑像Minion这样的工作队列,它可以很好地集成到Mojolicious应用中,并通过单独的工作进程运行。