Reputation: 58534
In the following fragment, how can I distinguish the second invocation instance of my sub foo
from the first?
while ($whatever) {
foo(); foo(); # foo() and foo() have the same caller package, file, and line
}
Something like a super-caller()
that returned file, line and column would do the trick. I'd prefer not to use source filters.
Background, or, isn't this a bit of an XY Problem?
I have a convenience module, Local::Thread::Once, that exposes functionality like pthread_once
/std::call_once
in an OO-ish way and also as a subroutine attribute. These are easy enough, since there is a natural and unambiguous "once_control" or "once_flag" in either case.
However, there is additionally a procedural interface — once { ... }
— that currently serializes based on the $filename
and $line
returned by caller
. Something like this:
sub once(&) {
my $user_routine = shift;
my (undef, $file, $line) = caller;
my $once_control = get_a_shared_flag_just_for_this_invocation($file, $line);
lock($once_control);
if (! $once_control) { $once_control++; $user_routine->(); }
return;
}
That's not precisely how it works — the real one is more efficient — but the point, again, is that invocation is keyed off of the file and line of the caller. This works, except that it cannot distinguish two invocations on the same line.
while ($whatever) {
once { foo(); }
once { bar(); } # OK, foo() and bar() each called only once
once { baz(); }; once { buz(); }; # :( buz() not called, not even once
}
Note that the address of $user_routine
cannot be used as an additional discriminant, since subs are copied from one ithread to another.
I can live with this problem as a documented limitation for a very contrived use case, but I'd prefer to fix it somehow.
Upvotes: 4
Views: 118
Reputation: 118605
The optree is still so much black magic to me, but here are my observations:
B::COP
structureB::COP
structure has file
, line
, and cop_seq
properties (among others)cop_seq
property is different for different subroutine definitionsAss-u-me-ing these are true and not a horribly incomplete model of what is happening, you can use file, line, and cop_seq
as a key, or maybe even just cop_seq
. Here's a proof of concept:
use B;
sub once (&) {
my $code = shift;
my $key = get_cop_seq($code);
print "once called with code '$key'\n";
}
my $optreedata;
sub get_cop_seq {
my $code = shift;
$optreedata = "";
B::walkoptree( B::svref_2object($code)->ROOT, "find_cop_seq" );
return $optreedata;
}
sub B::OP::find_cop_seq {
my $op = shift;
if (ref $op eq 'B::COP') {
$optreedata .= sprintf "%s:%d:%d", $op->file, $op->line, $op->cop_seq;
}
}
sub foo { 42 }
sub bar { 19 };
once { foo }; # this is line 26
once { bar };
once { foo }; once { bar };
once { bar } for 1..5; # line 29
And here's the output (your results may vary):
once called with code 'super-caller2.pl:26:205'
once called with code 'super-caller2.pl:27:206'
once called with code 'super-caller2.pl:28:207' <--- two calls for line 28
once called with code 'super-caller2.pl:28:208' |- with different cop_seq
once called with code 'super-caller2.pl:29:209'
once called with code 'super-caller2.pl:29:209'
once called with code 'super-caller2.pl:29:209' <--- but 5 calls for line 29
once called with code 'super-caller2.pl:29:209' with the same cop_seq
once called with code 'super-caller2.pl:29:209'
Upvotes: 1
Reputation: 118605
I had to read this a couple of times before I understood what you are talking about. How about a "super caller" function like:
my @last_caller = ("","","",0);
sub super_caller {
my ($pkg,$file,$line) = caller(1 + shift);
if ($pkg eq $last_caller[0] &&
$file eq $last_caller[1] &&
$line eq $last_caller[2]) {
$last_caller[3]++;
} else {
@last_caller = ($pkg,$file,$line,1);
}
return @last_caller;
}
It's like caller
but the 4th element is a count of how many times we've seen this exact package, file, and line in a row.
Upvotes: 3