Beefy Boxes and Bandwidth Generously Provided by pair Networks
Pathologically Eclectic Rubbish Lister

goto in CORE::GLOBAL::exit - is it safe?

by powerman (Friar)
on Aug 30, 2007 at 14:16 UTC ( #636089=perlquestion: print w/replies, xml ) Need Help??

powerman has asked for the wisdom of the Perl Monks concerning the following question:

I've a cgi script, which I wish to run under FastCGI. That script call exit() in several places (normal output, redirect, sendfile, die on error). And these places can be called inside eval (maybe - several evals each inside another). And there no guarantee 'unknown' errors catched by eval will be always propagated. Here pseudocode example:
sub that_cgi_script { ... eval { do_something() }; ... } sub do_something { # 1) may return if everything ok # 2) may die on error, but such error non-important to # calling code in above case and will be catched by # eval and ignored # 3) may redirect: print Location header and call exit() }
Now, in my FastCGI application, I must call this cgi script. Of course, I must intercept exit(), to prevent exiting to continue with FCGI->Accept(). There two options how to do this - with die():
my $FCGI_EXIT = "FCGI NORMAL EXIT\n"; BEGIN { *CORE::GLOBAL::exit = sub { die $FCGI_EXIT }; } while (CGI::Fast->new()) { eval { that_cgi_script(); }; die $@ if $@ ne q{} && $@ ne $FCGI_EXIT; $CGI::Fast::Ext_Request->Finish(); }
and with goto():
BEGIN { *CORE::GLOBAL::exit = sub { goto EXIT }; } while (CGI::Fast->new()) { eval { that_cgi_script(); }; die $@ if $@ ne q{}; EXIT: $CGI::Fast::Ext_Request->Finish(); }
AFAIK recommended is first solution with die(), and it used in mod_perl. But die() unable to break more than one eval, and in example above in case "3) may redirect" after calling exit I'll not return into my FastCGI loop, but continue executing inside that_cgi_script() after it eval (which will not propagate my die()). Second solution with goto() will work correctly. But I'm not sure is it safe. perldoc -f goto says:
It may not be used to go into any construct that requires initialization, such as a subroutine or a "foreach" loop. It also can't be used to go into a construct that is optimized away, or to get out of a block or subroutine given to "sort".
Looks like only possible case in my situation is calling exit() inside sort(). :) I believe this cgi script will not do this. Amen. Is there any other possible issues with goto() from CORE::GLOBAL::exit() handler?

P.S. A little offtopic, but any recommendations about CPAN modules for FastCGI support are welcome. Maybe it's better to use some other module instead of CGI::Fast because of some reason, etc.

Replies are listed 'Best First'.
Re: goto in CORE::GLOBAL::exit - is it safe?
by sgt (Deacon) on Aug 30, 2007 at 15:40 UTC

    maybe I am not answering the question either and I apologize in advance...

    Is seems somehow that it would be better to have a function my_exit() that would be able via switch to call normal exit() o fast_cgi_exit() supposing you'd like to test your code in non-CGI env. But then you need to filter all the code to do that. I don't know if it is an option.

    looking at your solution with exit, why this goto EXPR, can't you just do ' *CORE::GLOBAL::exit = sub { fast_cgi_exit() }'

    cheers --stephan

      What is fast_cgi_exit()? AFAIK there no such 'magical' function, so it's probably something I should write myself. Ok. How it should work? Remember, it may be executed inside several eval's, and I need to jump out from these evals into my FastCGI->Accept() (or CGI::Fast->new()) loop.

      Maybe I misunderstand you... can you please describe your idea about this funciton in more details, provide it pseudocode or point me to existing realization in some perl module?

Re: goto in CORE::GLOBAL::exit - is it safe?
by SFLEX (Chaplain) on Aug 30, 2007 at 16:08 UTC
    Ok, this code jumped out at me & it may help you out or it has nothing to pertain to the current title..
    BEGIN { # Override with itself so it can be overridden at run-time later. *CORE::GLOBAL::exit = sub { CORE::exit @_; } } { my $exit_code; no warnings 'redefine'; local *CORE::GLOBAL::exit = sub { $exit_code = shift; goto EXIT; }; # code which calls abort handler... EXIT: $CGI::Fast::Ext_Request->Finish(); }
      Yeah, I've already seen this code here. ;-)

      I propose exactly same solution, and I just wonder why it was not used by mod_perl? This make me thinking there can be some hidden issues with this solution.

        thats the same link i got the code from.

        for mod_perl i use this code to exit. * Edited
        use subs qw(exit); # this is also part of the exit mod_perl code # if anyone cares # Figure out how to exit mod_perl. *exit = $ENV{'MOD_PERL'} ? \&Apache::exit : sub { CORE::exit };

        I was playing with another code like it but that one seemed to work better.
Re: goto in CORE::GLOBAL::exit - is it safe?
by powerman (Friar) on Aug 31, 2007 at 13:58 UTC
    YES!!! My intuition was right again. :) Here it is, that bug with goto:
    perl -e ' BEGIN { *CORE::GLOBAL::exit = sub { goto FASTCGI_NEXT_REQUEST; }; } while (1) { eval { that_cgi_script() }; FASTCGI_NEXT_REQUEST: last; } sub that_cgi_script { local $SIG{__DIE__} = sub { print "<p>error: $_[0]"; exit; print "XX +X\n" }; print "before buggy code\n"; eval { buggy_code() }; print "after buggy code\n"; } sub buggy_code { die "error!"; print "after die\n"; } '
    This example output:
    before buggy code
    <p>error: error! at -e line 20.
    after buggy code
    i.e. goto doesn't work as expected, and execution continue after eval. Actually exit in $SIG{__DIE__} handler work like return - because "print "XXX"" doesn't executed.

    I've found a workaround for this bug. In BEGIN block must be added no-op handler for CORE::GLOBAL::die (which just emulate how perl's handler work):

    *CORE::GLOBAL::die = sub { if ($SIG{__DIE__}) { my $s = $_[0]; $s .= sprintf " at %s line %d.\n", (caller)[1,2] if $s !~ /\n\z/ +; $SIG{__DIE__}->($s); } };
    and now this example work correctly and output:
    before buggy code
    <p>error: error! at -e line 27.
    Sadly, but I'm not ready to explain, why adding that handler force goto to work correctly. :( Probably it's because my handler isn't work exactly like perl's handler. But I run a lot of different tests, and my handler work exactly like perl default handler for all tests except example shown above.

      hi again, my gut feeling is any use of goto EXPR (not goto sub) which is not "lexical" (meaning you goto to a label in the same scope) is almost guaranteed to wreak havoc. I have read 'perldoc -f goto' a few times and somehow it feels scary ;)

      ok, so you want to treat exit() like an exceptional condition and let it bubble up till the surface, err the top of your event loop. Well make it happen. i.e use '*CORE::GLOBAL::exit = sub { die X::EXIT->new; }' and somehow use a die-handler which re'die's if $@->isa("X::EXIT"). Some kind of automatic "rethrow"; sorry I don't have time to test it.

      cheers --stephan update I left an ampersand after not goto...
A reply falls below the community's threshold of quality. You may see it by logging in.

Log In?

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://636089]
Approved by ikegami
Front-paged by tye
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others having an uproarious good time at the Monastery: (2)
As of 2022-01-26 06:31 GMT
Find Nodes?
    Voting Booth?
    In 2022, my preferred method to securely store passwords is:

    Results (69 votes). Check out past polls.