3

This is a complex question with regard to mro.pm, and the interplay with set_subname, and goto

In troubleshooting a problem, I think the core of my misunderstanding relates to how mro.pm works -- especially with regard to set_subname.

What is the difference between these three constructs,

  1. Plain call to set_subname

    *Foo::bar = set_subname( 'Foo::bar', $codeRef );
    
  2. Anon sub which wraps a set_subname

    *Foo::bar = sub {
      my $codeRef2 = set_subname('Foo::bar', $codeRef);
      goto $codeRef2
    };
    
  3. Anon sub which has its name set with set_subname

    *Foo::bar = set_subname(
      'Foo::bar',
      sub { goto $codeRef }
    );
    

Specifically, the Mojo test suits fails with either of these modifications with anon subs applied to Mojo::Utils's monkey_patch Running the two variants above against t/mojo/websocket_proxy.t,

  • With the 2 (second) option I have

    *{"${class}::$k"} = sub {                                                                                                                          
      my $cr = set_subname("${class}::$k", $patch{$k});                                                                                                
      goto $cr;                                                                                                                                        
    }; 
    

    And I get

    Mojo::Reactor::Poll: Timer failed: Can't locate object method "send" via package "Mojo::Transaction::HTTP" at t/mojo/websocket_proxy.t line 66.
    
  • With the 3 (third) option I have,

    *{"${class}::$k"} = set_subname("${class}::$k", sub { goto $patch{$k} })
    

    And I get

    No next::method 'new' found for Mojolicious::Routes at /usr/lib/x86_64-linux-gnu/perl/5.28/mro.pm line 30.
    

Obviously, the first version works (it's from the code I linked), the question is why are the other two variants giving me different errors (especially the second variant) and what's happening there -- why don't they work?

Evan Carroll
  • 78,363
  • 46
  • 261
  • 468
  • I'm here because I have failed to isolate the problem. I'm wanting to know how these three things interact so I can better understand this and isolate the problem. The problem here isn't driven by an effect I'm trying to gain. I'm trying to explain why three things behave differently. – Evan Carroll Nov 03 '19 at 18:50

3 Answers3

2

That is indeed complicated, but maybe you overcomplicated it?

Remember that MRO is only concerned with locating a method, which is just a symbol table entry to a coderef, through a defined order of package names. The internal subname only has to do with what caller() reports AFAIK.

From: Mojo

*{"${class}::$_"} =          ## symbol table entry
set_subname("${class}::$_",  ## an internal name
   $patch{$_})               ## for a code ref
for keys %patch;

HTH

Edit After Seeing Error Messages:

The subroutines have not been validly installed. I suspect that since in option 2 and 3 you are deferring the calls to set_subname() to call time, the coderef $patch{$k} never has a subname assigned to it and that breaks a link in the chain of mro::_nextcan()'s XS magic. Particularly if $patch{$k} calls next::method. The closures seem to be valid though.

Although I must say my testing seems to show that option 2 is valid.

Enter command: my ($class, $k) = qw/W S/; my %patch = (S =>
sub {print "patch here\n"; decall;}); *{"${class}::$k"} = 
sub { print "goto'r here\n";  my $cr = set_subname("${class}::$k",
$patch{$k}); goto $cr;};


Enter command: decall
0       "console"
1       "console.pl"
2       "114"
3       "(eval)"
4       "0"
5       0
6       "package W; decall"
7       ""
8       "256"
9       "\020\001\000\000\000P\004\000\000\000\000\000\000U\025U\005"
10      0

Enter command: S
goto'r here
patch here
0       "W"
1       "(eval 110)"
2       "1"
3       "W::S"
4       "1"
5       0
6       0
7       0
8       "256"
9       "\020\001\000\000\000P\004\000\000\000\000\000\000U\025U\005"
10      0

You might have to start looking farther afield for the problem with option 2.

lordadmira
  • 1,807
  • 5
  • 14
  • I'm not sure that answers the question. Explain to me why this doesn't pass the Mojo test suite following the same logic, `foreach my $k ( keys %patch ) { *{"${class}::$k"} = set_subname("${class}::$k", sub {goto $patch{$k}) }` – Evan Carroll Nov 03 '19 at 03:35
  • 1
    I've updated the question with the actual error messages. – Evan Carroll Nov 03 '19 at 03:50
  • I also had a hard time isolating it. I can just say that it seems to occur by changing that one line (that I first linked too and that you copied into your answer) with the other methods in my answer. Why it's happening is the basis of the question. I can't isolate it. If you could explain how `mro::_nextcan()'s` method treats the three examples differently I would be satisfied with the answer and mark it as chosen. That may be exactly what I don't understand. – Evan Carroll Nov 03 '19 at 18:52
2

Your second option is not working because the sub you are using as a wrapper does not match the prototype of the inner sub. monkey_patch is not only used for methods, and this changes how some functions are parsed. In particular, Mojo::Util::steady_time has an empty prototype and is often called without using parenthesis.

*{"${class}::$k"} = Sub::Util::set_prototype(
  Sub::Util::prototype( $patch{$k} ),
  Sub::Util::set_subname(
    "${class}::$k",
    sub {
      my $cr = Sub::Util::set_subname("${class}::$k", $patch{$k});
      goto $cr;
    }
  )
);

The third construct is not working because you are using goto to remove the renamed wrapper sub from the call stack, leaving only the inner sub which has no name. This breaks next::method's ability to find the correct method name.

Evan Carroll
  • 78,363
  • 46
  • 261
  • 468
haarg
  • 211
  • 1
  • 5
  • Wow!! So the prototypes are throwing it off. Is there anyway to achieve what I want with regard to wrapping a prototyped sub? – Evan Carroll Nov 03 '19 at 21:36
  • Ah, it hadn't occurred to me that the error could have been thrown from a function call rather than a method. The error msg however is from `$tx->send('test1')` which is a method call. The prototype is ignored there. Evan, try using Carp::Always in the test and you'll get a full stack trace when it dies. – lordadmira Nov 04 '19 at 04:29
  • The problem happens because without the correct prototype on `steady_time`, all timers resolve immediately. This results in the transaction getting resolved before it has time to be upgraded to a websocket transaction, which is needed for the `->send` call. – haarg Nov 04 '19 at 13:37
0

After modifying Mojo/Util.pm with

  foreach my $k (keys %patch) {
    *{"${class}::$k"} = sub {
      my $cr = set_subname("${class}::$k", $patch{$k});
      goto $cr;
    };
   }

and isolating the test case, I get:

$ perl -MCarp::Always t/mojo/websocket_proxy2.t
Mojo::Reactor::Poll: Timer failed: Can't locate object method "send" via package "Mojo::Transaction::HTTP" at t/mojo/websocket_proxy2.t line 59.
        main::__ANON__(Mojo::UserAgent=HASH(0x60280dad0), Mojo::Transaction::HTTP=HASH(0x6029ee7b0)) called at blib/lib/Mojo/UserAgent.pm line 252
        Mojo::UserAgent::_finish(Mojo::UserAgent=HASH(0x60280dad0), "927210c53042c6142eda3f4010c8b17c", 1) called at blib/lib/Mojo/UserAgent.pm line 220
        Mojo::UserAgent::_error(Mojo::UserAgent=HASH(0x60280dad0), "927210c53042c6142eda3f4010c8b17c", "Connect timeout") called at blib/lib/Mojo/UserAgent.pm line 128
        Mojo::UserAgent::__ANON__(Mojo::IOLoop=HASH(0x601f9abb8), "Connect timeout", undef) called at blib/lib/Mojo/IOLoop.pm line 63
        Mojo::IOLoop::__ANON__(Mojo::IOLoop::Client=HASH(0x601e34598)) called at blib/lib/Mojo/EventEmitter.pm line 15
        Mojo::EventEmitter::emit(Mojo::IOLoop::Client=HASH(0x601e34598), "error", "Connect timeout") called at blib/lib/Mojo/IOLoop/Client.pm line 39
        Mojo::IOLoop::Client::__ANON__(Mojo::Reactor::Poll=HASH(0x6001a8390)) called at blib/lib/Mojo/Reactor/Poll.pm line 143
        eval {...} called at blib/lib/Mojo/Reactor/Poll.pm line 143
        Mojo::Reactor::Poll::_try(Mojo::Reactor::Poll=HASH(0x6001a8390), "Timer", CODE(0x601e24ca0)) called at blib/lib/Mojo/Reactor/Poll.pm line 81
        Mojo::Reactor::Poll::one_tick(Mojo::Reactor::Poll=HASH(0x6001a8390)) called at blib/lib/Mojo/Reactor/Poll.pm line 99
        Mojo::Reactor::Poll::start(Mojo::Reactor::Poll=HASH(0x6001a8390)) called at blib/lib/Mojo/IOLoop.pm line 134
        Mojo::IOLoop::start("Mojo::IOLoop") called at t/mojo/websocket_proxy2.t line 62
 at blib/lib/Mojo/IOLoop.pm line 23.
        Mojo::IOLoop::__ANON__(Mojo::Reactor::Poll=HASH(0x6001a8390), "Timer failed: Can't locate object method \"send\" via package \"Mojo::Transaction::HTTP\" at t/mojo/websocket_proxy2.t line 59.\x{a}\x{9}main::__ANON__(Mojo::UserAgent=HASH(0x60280dad0), Mojo::Transaction::HTTP=HASH(0x6029ee7b0)) called at blib/lib/Mojo/UserAgent.pm line 252\x{a}\x{9}Mojo::UserAgent::_finish(Mojo::UserAgent=HASH(0x60280dad0), \"927210c53042c6142eda3f4010c8b17c\", 1) called at blib/lib/Mojo/UserAgent.pm line 220\x{a}\x{9}Mojo::UserAgent::_error(Mojo::UserAgent=HASH(0x60280dad0), \"927210c53042c6142eda3f4010c8b17c\", \"Connect timeout\") called at blib/lib/Mojo/UserAgent.pm line 128\x{a}\x{9}Mojo::UserAgent::__ANON__(Mojo::IOLoop=HASH(0x601f9abb8), \"Connect timeout\", undef) called at blib/lib/Mojo/IOLoop.pm line 63\x{a}\x{9}Mojo::IOLoop::__ANON__(Mojo::IOLoop::Client=HASH(0x601e34598)) called at blib/lib/Mojo/EventEmitter.pm line 15\x{a}\x{9}Mojo::EventEmitter::emit(Mojo::IOLoop::Client=HASH(0x601e34598), \"error\", \"Connect timeout\") called at blib/lib/Mojo/IOLoop/Client.pm line 39\x{a}\x{9}Mojo::IOLoop::Client::__ANON__(Mojo::Reactor::Poll=HASH(0x6001a8390)) called at blib/lib/Mojo/Reactor/Poll.pm line 143\x{a}\x{9}eval {...} called at blib/lib/Mojo/Reactor/Poll.pm line 143\x{a}\x{9}Mojo::Reactor::Poll::_try(Mojo::Reactor::Poll=HASH(0x6001a8390), \"Timer\", CODE(0x601e24ca0)) called at blib/lib/Mojo/Reactor/Poll.pm line 81\x{a}\x{9}Mojo::Reactor::Poll::one_tick(Mojo::Reactor::Poll=HASH(0x6001a8390)) called at blib/lib/Mojo/Reactor/Poll.pm line 99\x{a}\x{9}Mojo::Reactor::Poll::start(Mojo::Reactor::Poll=HASH(0x6001a8390)) called at blib/lib/Mojo/IOLoop.pm line 134\x{a}\x{9}Mojo::IOLoop::start(\"Mojo::IOLoop\") called at t/mojo/websocket_proxy2.t line 62\x{a}") called at blib/lib/Mojo/EventEmitter.pm line 15
        Mojo::EventEmitter::emit(Mojo::Reactor::Poll=HASH(0x6001a8390), "error", "Timer failed: Can't locate object method \"send\" via package \"Mojo::Transaction::HTTP\" at t/mojo/websocket_proxy2.t line 59.\x{a}\x{9}main::__ANON__(Mojo::UserAgent=HASH(0x60280dad0), Mojo::Transaction::HTTP=HASH(0x6029ee7b0)) called at blib/lib/Mojo/UserAgent.pm line 252\x{a}\x{9}Mojo::UserAgent::_finish(Mojo::UserAgent=HASH(0x60280dad0), \"927210c53042c6142eda3f4010c8b17c\", 1) called at blib/lib/Mojo/UserAgent.pm line 220\x{a}\x{9}Mojo::UserAgent::_error(Mojo::UserAgent=HASH(0x60280dad0), \"927210c53042c6142eda3f4010c8b17c\", \"Connect timeout\") called at blib/lib/Mojo/UserAgent.pm line 128\x{a}\x{9}Mojo::UserAgent::__ANON__(Mojo::IOLoop=HASH(0x601f9abb8), \"Connect timeout\", undef) called at blib/lib/Mojo/IOLoop.pm line 63\x{a}\x{9}Mojo::IOLoop::__ANON__(Mojo::IOLoop::Client=HASH(0x601e34598)) called at blib/lib/Mojo/EventEmitter.pm line 15\x{a}\x{9}Mojo::EventEmitter::emit(Mojo::IOLoop::Client=HASH(0x601e34598), \"error\", \"Connect timeout\") called at blib/lib/Mojo/IOLoop/Client.pm line 39\x{a}\x{9}Mojo::IOLoop::Client::__ANON__(Mojo::Reactor::Poll=HASH(0x6001a8390)) called at blib/lib/Mojo/Reactor/Poll.pm line 143\x{a}\x{9}eval {...} called at blib/lib/Mojo/Reactor/Poll.pm line 143\x{a}\x{9}Mojo::Reactor::Poll::_try(Mojo::Reactor::Poll=HASH(0x6001a8390), \"Timer\", CODE(0x601e24ca0)) called at blib/lib/Mojo/Reactor/Poll.pm line 81\x{a}\x{9}Mojo::Reactor::Poll::one_tick(Mojo::Reactor::Poll=HASH(0x6001a8390)) called at blib/lib/Mojo/Reactor/Poll.pm line 99\x{a}\x{9}Mojo::Reactor::Poll::start(Mojo::Reactor::Poll=HASH(0x6001a8390)) called at blib/lib/Mojo/IOLoop.pm line 134\x{a}\x{9}Mojo::IOLoop::start(\"Mojo::IOLoop\") called at t/mojo/websocket_proxy2.t line 62\x{a}") called at blib/lib/Mojo/Reactor/Poll.pm line 143
        Mojo::Reactor::Poll::_try(Mojo::Reactor::Poll=HASH(0x6001a8390), "Timer", CODE(0x601e24ca0)) called at blib/lib/Mojo/Reactor/Poll.pm line 81
        Mojo::Reactor::Poll::one_tick(Mojo::Reactor::Poll=HASH(0x6001a8390)) called at blib/lib/Mojo/Reactor/Poll.pm line 99
        Mojo::Reactor::Poll::start(Mojo::Reactor::Poll=HASH(0x6001a8390)) called at blib/lib/Mojo/IOLoop.pm line 134
        Mojo::IOLoop::start("Mojo::IOLoop") called at t/mojo/websocket_proxy2.t line 62

I can also confirm that setting the prototype fixes it.

lordadmira
  • 1,807
  • 5
  • 14