Mojolicous: ограничение числа обещаний / IOLoop-> подпроцесс - PullRequest
0 голосов
/ 18 сентября 2018

Я использую Mojolicious неблокирующие методы (Promises) для запроса данных из внешних систем.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 голосов
/ 19 сентября 2018

Проблема не в количестве обещаний, а в количестве подпроцессов.Один из способов ограничить это - просто ограничить количество, которое вы создаете за раз в логике вашей программы.Вместо того, чтобы порождать их все сразу на карте, установите лимит и получите их из @numbers (возможно, используя splice ) и породите эти подпроцессы;создайте -> все обещания, которые ожидают их, и прикрепите -> затем к этому обещанию, чтобы получить ваш следующий кусок чисел и т. д.

Другой вариант - использовать Future :: Utils fmap_concat, который может позаботиться о коде, ограничивающем скорость, если вы предоставите количество максимально ожидаемых фьючерсов.Ваша функция, возвращающая обещание, может применять Mojo :: Promise :: Role :: Futurify для создания цепочки следующего будущего для использования таким образом.

#!/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;

Что касается метода ожидания, этотничего не делает, когда цикл событий уже запущен, что в обработчике ответов webapp будет, если вы запустили приложение в Mojolicious-демоне (в отличие от сервера PSGI или CGI, который не поддерживает асинхронные ответы).Вызовы -> stash и -> render вне колбэков будут выполняться сразу после настройки подпроцессов.Затем обработчик ответа завершится, и цикл обработки событий снова получит управление, которое вызовет соответствующие -> затем обратные вызовы после разрешения обещаний.Рендер не должен ждать чего-либо кроме настройки подпроцессов;так как вы сказали, что могут быть сотни, это может быть замедлением, которое вы испытываете.Убедитесь, что вы используете Mojolicious 7.86 или новее, так как Subprocess был изменен, так что разветвление не произойдет до следующего тика цикла обработки событий (после того, как обработчик вашего ответа завершится).

Я также отмечу, что подпроцессы не нужныдействительно предназначен для этого;они предназначены для выполнения медленного кода, который все еще возвращает возможный результат браузеру в ответе (и Mojolicious :: Plugin :: Subprocess хорош для этого варианта использования).Одна проблема, которую я вижу, состоит в том, что если вы перезапустите приложение, все еще ожидающие подпроцессы будут просто игнорироваться.Для заданий, которые вы хотите отключить и забыть, вы можете рассмотреть очередь заданий, например Minion , которая прекрасно интегрируется в приложения Mojolicious и запускается через отдельный рабочий процесс.

...