Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

Copy a builtin sub to a different name and then override

by bliako (Prior)
on Jun 01, 2018 at 21:43 UTC ( #1215668=perlquestion: print w/replies, xml ) Need Help??

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

Hi Monks, I would like to keep track of the total time of sleep() seconds my program got (in main and across ALL modules).

So what I would like to do is to have a counter in my main which initially is set to zero. And override sleep() so that it increments the counter and then sleeps.

I thought it would be easy to copy builtin sleep() to actual_sleep() and then re-implement my own sleep() which will modify the counter AND sleep. Alas it is not ... Here is a short demo:

#!/usr/bin/env perl use strict; use warnings; my $total_sleep_time = 0; BEGIN { *actual_sleep = *CORE::GLOBAL::sleep; # ????? *CORE::GLOBAL::sleep = sub { $total_sleep_time+=$_[0]; actual_sleep($_ +[0]) } # << line 7 } # use this module and the other # ... sleep(2); print "total sleep is $total_sleep_time\n";

the result is:

Deep recursion on anonymous subroutine at xx line 7.

How can I make actual_sleep() point to the original sleep() builtin and not, by the looks of it, to the new sleep()?

Thanks, bliako

Update: The eventual solution provided by LanX is not to rename sleep() to actual_sleep() but to use the original sleep() code which resides untouched in CORE even after overriding its alias in CORE::GLOBAL.

a summary of provided solutions from below:

LanX's solution to

simply call CORE::sleep()
worked (Re: Copy a builtin sub to a different name and then override) and also worked when sleep() is called from any other package.

beech provided working code based on that (Re: Copy a builtin sub to a different name and then override).

pme made the suggestion to use sleep()'s return value which is the actual seconds slept (Re^3: Copy a builtin sub to a different name and then override).

Useful links: pme suggested reading ex::override which is an easy way to override builtins, alas not "renaming", also CORE. I only had in mind Perl Cookbook's https://docstore.mik.ua/orelly/perl4/cook/ch12_14.htm which also does not mention how to "rename".

Update2:

Most importantly, the new sleep() will be seen only be those packages whose use follows the BEGIN block with the re-aliased sleep() code. See Re^3: Copy a builtin sub to a different name and then override by BillKSmith.

(in order ot timestamps)

Thanks again. Perl is great.

Replies are listed 'Best First'.
Re: Copy a builtin sub to a different name and then override
by beech (Parson) on Jun 01, 2018 at 22:31 UTC

    Hi,

    Which version of perl do you have?

    See CORE and ex::override

    #!/usr/bin/perl -- use strict; use warnings; use diagnostics; BEGIN { my $sleepcounter = 0; *CORE::GLOBAL::hex = sub { print qq{hex(@_)\n}; # goto &CORE::hex; ## FAIL CORE::hex(@_); }; *CORE::GLOBAL::sleep = sub(;@) { print qq{sleep(@_)\n}; $sleepcounter += $_[0]; CORE::sleep(@_); }; END { print "total sleep( $sleepcounter )\n"; } } print "\$]=$] \$^V=$^V\n"; sleep 1; print hex("0x50"),"\n"; sleep 2; print hex("0x50"),"\n"; __END__ $ perl core-global-sleep-override.pl $]=5.016001 $^V=v5.16.1 hex(0x50) 1 hex(0x50) 1 total sleep( 0 ) $ perl core-global-sleep-override.pl $]=5.020003 $^V=v5.20.3 sleep(1) hex(0x50) 1 sleep(2) hex(0x50) 1 total sleep( 3 )
      This works for me under 5.016003 even without the correct prototype:

      use strict; use warnings; warn "Version $]"; warn "Prototype", prototype "CORE::sleep"; my $sleepcounter; BEGIN { *CORE::GLOBAL::sleep = sub { warn "start wrapper"; $sleepcounter += $_[0]; CORE::sleep(@_); warn "stop wrapper"; }; } sleep 3; warn "total sleep( $sleepcounter )"
      C:/Perl_64/bin\perl.exe d:/Users/lanx/pm/core_sleep.pl Version 5.016003 at d:/Users/lanx/pm/core_sleep.pl line 4. Prototype;$ at d:/Users/lanx/pm/core_sleep.pl line 5. start wrapper at d:/Users/lanx/pm/core_sleep.pl line 13. stop wrapper at d:/Users/lanx/pm/core_sleep.pl line 16. total sleep( 3 ) at d:/Users/lanx/pm/core_sleep.pl line 26.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

      that worked!

      #!/usr/bin/env perl use strict; use warnings; my $total_sleep_time = 0; BEGIN { *CORE::GLOBAL::sleep = sub(;@) { $total_sleep_time+=$_[0]; CORE::sleep +($_[0]) } } # use this module and the other # ... print "\$]=$] \$^V=$^V\n"; sleep(2); print "total sleep is $total_sleep_time\n";
      $]=5.026002       $^V=v5.26.2
      total sleep is 2
      
      thanks
        sleep sleeps integer seconds and returns the integer number of seconds actually slept.
        #!/usr/bin/perl use strict; use warnings; my $total_sleep_time = 0; BEGIN { *CORE::GLOBAL::sleep = sub(@) { $total_sleep_time += CORE::sleep($_[0]); } } print "\$]=$] \$^V=$^V\n"; sleep(2); sleep(1); sleep(1.3); print "total sleep is $total_sleep_time\n"; $ time -p ./sleep.pl $]=5.024001 $^V=v5.24.1 total sleep is 4 real 4.00 user 0.00 sys 0.00
        Very nice.

        Overriding CORE::GLOBAL::sleep but calling CORE::sleep makes total sense when you see it (as it avoids the recursion).

        This is of course what Lanx has been trying to say all along :-)

      Hi Beech,

      > # goto &CORE::hex; ## FAIL

      I can't reproduce any problems with goto &SUB here.

      Neither with hex nor with sleep .

      How does it "fail" for you?

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

      update

      use strict; use warnings; warn "Version $]"; warn "Prototype", prototype "CORE::sleep"; my $sleepcounter; BEGIN { *CORE::GLOBAL::sleep = sub { warn "start wrapper ($_[0])"; $sleepcounter += int $_[0]; goto &CORE::sleep; }; } warn "return: ", sleep 2.3; warn "total sleep( $sleepcounter )";
      Version 5.016003 at d:/Users/lanx/pm/core_sleep.pl line 4. Prototype;$ at d:/Users/lanx/pm/core_sleep.pl line 5. start wrapper (2.3) at d:/Users/lanx/pm/core_sleep.pl line 15. return: 2 at d:/Users/lanx/pm/core_sleep.pl line 23. total sleep( 2 ) at d:/Users/lanx/pm/core_sleep.pl line 25.

        # goto &CORE::hex; ## FAIL This works for me under 5.016003 even without the correct prototype:

        Hi,

        It turns out it works as designed, it merely returns different from CORE::hex(@_) , because of the prototype . I only checked for prototype on "hex" because it worked on "sleep", I forgot I preload Time::HiRes, I should have checked CORE::hex

Re: Copy a builtin sub to a different name and then override
by LanX (Cardinal) on Jun 01, 2018 at 22:23 UTC
    After reading perlsub#Overriding-Built-in-Functions

    > To unambiguously refer to the built-in form, precede the built-in name with the special package qualifier CORE:: .  For example, saying CORE::open() always refers to the built-in open()

    You should probably simply call CORE::sleep()

    Have you tested if CORE::GLOBAL::sleep() even exists before overriding? *

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

    update

    *) nope it doesn't

    update

    see also an explanation how to fix it with exists

       You should probably simply call CORE::sleep()
      

      yep, that worked! The builtin sleep() cam be accessed via CORE::sleep(). I have posted the code under another comment. Many Thanks!

    A reply falls below the community's threshold of quality. You may see it by logging in.
Re: Copy a builtin sub to a different name and then override
by LanX (Cardinal) on Jun 01, 2018 at 22:07 UTC

      still the same problem with this:

      #!/usr/bin/env perl use strict; use warnings; my $total_sleep_time = 0; BEGIN { my $actual_sleep = \&CORE::GLOBAL::sleep; *CORE::GLOBAL::sleep = sub { $total_sleep_time+=$_[0]; $actual_sleep-> +($_[0]) } } # use this module and the other # ... sleep(2); print "total sleep is $total_sleep_time\n";
      thanks
        > still the same problem with this:

        > ...

        > my $actual_sleep = \&CORE::GLOBAL::sleep;

        This should solve it in a robust manner:

        my $actual_sleep = exists &CORE::GLOBAL::sleep # already overridden + ? ? \&CORE::GLOBAL::sleep : \&CORE::sleep

        Point is you are changing sleep globally, but some other modules might be trying to do the same thing already. (well ...)

        If you install your override later, you'll be playing safe.

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery

        update

        even more robust with defined instead of exists , see Haarg's comment in this thread for details.

        > still the same problem with this:

        Really?

        You still have Deep recursion on anonymous subroutine ?

        As I said that's the normal way to install a wrapper, but in the case of builtins it seems risky.

        calling CORE::sleep() should be the recommended way.

        ... but I seem to remember reading that overriding sleep used to be problematic.(?)

        I'll look tomorrow into it.

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery

    A reply falls below the community's threshold of quality. You may see it by logging in.
Re: Copy a builtin sub to a different name and then override
by BillKSmith (Prior) on Jun 02, 2018 at 13:16 UTC
    This is not exactly what you asked for, but it is an 'approved' way to meet your requirement. Refer to the section "Overriding Built-in Functions" in perlsub
    use strict; use warnings; use subs 'sleep'; my $total_sleep_time; sub sleep (;$) { $total_sleep_time += $_[0]; CORE::sleep($_[0]); } sleep(2); sleep(3); print $total_sleep_time;
    Bill

      thanks Bill, but i need it to affect ALL imported modules too

        As I now understand your problem, we still do not have a solution. You use one (or more) existing module(s) which call sleep(). You wish to include the module sleep time(s) in your total. I devised the following test:
        C:\Users\Bill\forums\monks>type Existing_module.pm use strict; use warnings; package Existing_Module; sub Do_Something { print "Doing something.\n"; sleep(3); } 1 C:\Users\Bill\forums\monks>type bliako.t use strict; use warnings; use lib '.'; use Test::More tests => 2; use Existing_Module; our $total_sleep_time; BEGIN { *actual_sleep = *CORE::sleep; *CORE::GLOBAL::sleep = sub (;$) { $total_sleep_time+=$_[0]; actual_sleep($_[0]) } } sleep(2); is($total_sleep_time, 2, 'Local'); Existing_Module::Do_Something(); # Sleeps for 3 sec. is($total_sleep_time, 2+3, 'Module'); C:\Users\Bill\forums\monks>perl bliako.t 1..2 ok 1 - Local Doing something. not ok 2 - Module # Failed test 'Module' # at bliako.t line 21. # got: '2' # expected: '5' # Looks like you failed 1 test of 2.

        Several proposed solutions (including my previous post) can pass the first test. I have not found any that can pass the second. I have learned not to say that anything is impossible, but it does seem that this is intended to be.

        Bill

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1215668]
Approved by beech
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others taking refuge in the Monastery: (3)
As of 2020-11-25 03:07 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?