mojolicious / mojo

:sparkles: Mojolicious - Perl real-time web framework
https://mojolicious.org
Artistic License 2.0
2.66k stars 576 forks source link

Can't locate object method "on_cancel" via package "Mojo::Promise" #1910

Open toratora opened 2 years ago

toratora commented 2 years ago

I get this exception when using async/await on Mojo::UserAgent::get_p (or any Mojo::Promise) after upgrading from Mojolicious 9.19 (and Future::AsyncAwait 0.48) to Mojolicious 9.22 (and Future::AsyncAwait 0.55). This issue appears on perl v5.20.3 but disappears when I switch to v5.30.0.

I came across this in some code in one of my Mojolicious app controller classes but here's a script I used to replicate the problem:

package Request;
use Mojo::Base -base, -async_await;

use Mojo::Log;
use Mojo::IOLoop;
use Mojo::UserAgent;

has log => sub { Mojo::Log->new };

sub run {
  my ($self) = @_;
  my $loop   = Mojo::IOLoop->singleton;
  my $ua     = Mojo::UserAgent->new(ioloop => $loop);
  my $url = 'https://fastapi.metacpan.org/source/SRI/Mojolicious-9.22/Changes';

  $SIG{TERM} = $SIG{HUP} = $SIG{INT} = $SIG{QUIT} = sub {
    $self->log->info("signal, stop loop");
    $loop->stop;
  };

  $loop->recurring(
    3 => sub {
      $self->log->info("3s recurring tick");
    }
  );

  $loop->timer(
    5 => async sub {
      my $text = eval { await $self->_make_request($ua, $url) };

      $@ and return $self->log->error($@);

      $self->log->info($text);
    }
  );

  $self->log->info("Starting loop");
  $loop->start;
  $self->log->info("done");
}

async sub _make_request {
  my ($self, $ua, $url) = @_;

  $self->log->info("Make request to $url");
  my $tx = await $ua->get_p($url);

  $self->log->info("Request complete");
  my $res = __success_or_die($tx);

  return substr $res->text, 0, 500;
}

sub __success_or_die {
  my $tx  = shift;
  my $err = $tx->error or return $tx->result;

  die "$err->{code} $err->{message} during request to $tx->{req}->{url}"
    if $err->{code};
  die "Connection error: $err->{message} during request to $tx->{req}->{url}";
}

__PACKAGE__->new->run;

1;

cpanfile:

requires 'Mozilla::CA';
requires 'EV';
requires 'IO::Socket::SSL';
requires 'Net::DNS::Native';
requires 'Cpanel::JSON::XS';
requires 'Future::AsyncAwait';
requires 'Mojolicious';

Expected output for this script is:

[2022-01-24 16:10:26.76805] [71609] [info] Starting loop
[2022-01-24 16:10:29.76901] [71609] [info] 3s recurring tick
[2022-01-24 16:10:31.77183] [71609] [info] Make request to https://fastapi.metacpan.org/source/SRI/Mojolicious-9.22/Changes
[2022-01-24 16:10:32.07126] [71609] [info] Request complete
[2022-01-24 16:10:32.07566] [71609] [info] 
9.22  2021-10-16
  - Added a referer method to Mojo::Headers, as an alias for the referrer method.
  - Fixed response status log message to use the "trace" log level instead of "debug".

9.21  2021-08-13
  - Added EXPERIMENTAL support for top-level await to Mojo::Promise.
  - Updated Future::AsyncAwait requirement to 0.52 for new features and bug fixes.
  - Improved *_attr and *_text methods in Test::Mojo to return undef instead of empty string for values that do not
    exist. (tim-2)
  - Fixe
[2022-01-24 16:10:32.77127] [71609] [info] 3s recurring tick
^C[2022-01-24 16:10:35.76871] [71609] [info] signal, stop loop
[2022-01-24 16:10:35.76889] [71609] [info] 3s recurring tick
[2022-01-24 16:10:35.76898] [71609] [info] done

It works fine on perl 5.20.3, <= Mojolicious 9.19, and Future::AsyncAwait 0.48. It also works fine on perl 5.30.0, Mojolicious >=9.21, and Future::AsyncAwait >= 0.52.

However, on perl 5.20.3, Mojolicious >=9.21, and Future::AsyncAwait >= 0.52 I get the following output with the exception Can't locate object method "on_cancel" via package "Mojo::Promise":

[2022-01-24 15:22:43.09739] [16339] [info] Starting loop
[2022-01-24 15:22:46.10901] [16339] [info] 3s recurring tick
[2022-01-24 15:22:48.09981] [16339] [info] Make request to https://fastapi.metacpan.org/source/SRI/Mojolicious-9.22/Changes
[2022-01-24 15:22:48.10308] [16339] [error] Can't locate object method "on_cancel" via package "Mojo::Promise" at /home/user/development/mojo-await-async/request.pl line 46.

[2022-01-24 15:22:48.43884] [16339] [info] Request complete
[2022-01-24 15:22:58.10113] [16339] [info] signal, stop loop
[2022-01-24 15:22:58.10148] [16339] [info] 3s recurring tick
[2022-01-24 15:22:58.10165] [16339] [info] done

This may ultimately be an issue with Future::AsyncAwait but it affects Mojolicious since Future::AsyncAwait >= 0.52 is required since Mojolicious 9.21

kraih commented 2 years ago

Yes, that has to be a Future::AsyncAwait bug, since the async/await protocol only uses uppercase methods like AWAIT_ON_CANCEL.

toratora commented 2 years ago

I think https://rt.cpan.org/Public/Bug/Display.html?id=137723 refers to the same issue.

monkey_patch 'Mojo::Promise', on_cancel => sub {};

seems to resolve the issue.

kraih commented 2 years ago

It's probably this line. https://metacpan.org/release/PEVANS/Future-AsyncAwait-0.55/source/lib/Future/AsyncAwait.xs#L1753

toratora commented 2 years ago

I think that's supposed to be a call to AWAIT_ON_CANCEL like in MY_future_chain_on_cancel

call_method("AWAIT_ON_CANCEL", G_VOID);

I see on_cancel in that code but not in the context of being a parameter to call_method.

leonerd commented 2 years ago

Ahyes, requires some API reshaping first in Future itself. This part wasn't quite finished:

https://metacpan.org/pod/Future::AsyncAwait::Awaitable#AWAIT_ON_CANCEL

toratora commented 2 years ago

Issue seems resolved with release of Future::AsyncAwait 0.56. Thanks for the quick fix @leonerd.

@kraih Might want to update Future::AsyncAwait requirement to 0.56.