Skip Menu |

This queue is for tickets about the mod_perl CPAN distribution.

Report information
The Basics
Id: 30061
Status: resolved
Priority: 0/
Queue: mod_perl

People
Owner: Nobody in particular
Requestors: TIMB [...] cpan.org
Cc:
AdminCc:

Bug Information
Severity: Important
Broken in: (no value)
Fixed in: (no value)



Subject: $r->pnotes stores aliases not copies so is subject to action at a distance
This code: $x = 42; $r->pnotes( 'foo', $x ); ++$x; warn $r->pnotes( 'foo', $x ); should print the value that was stored, 42, but it actually prints 43! The value stored has been modified by action at a distance. *Not good* This can cause some very hard to debug problems, where values stored in pnotes change for no readily apparent reason. I know, I've spent way too long doing just that before I identified this as the cause. A workaround for affected code is to assign to a temporary variable so the alias taken by pnotes isn't an alias for the orginal value: $r->pnotes( 'foo', my $tmp = $x ) The minimal fix is to change: hv_store(cfg->pnotes, key, len, SvREFCNT_inc(val), FALSE); in pnotes in src/modules/perl/Apache.xs to something more like SV **svp = hv_fetch(cfg->pnotes, key, len, TRUE); sv_setsv(*svp, val); with a little more work you could eliminate the earlier hv_exists and hv_fetch by doing SV **svp = hv_fetch(cfg->pnotes, key, len, (val) ? TRUE : FALSE); and working with svp. Tim Bunce.
From: GEOFF [...] cpan.org
hi tim :) On Wed Oct 17 10:57:58 2007, TIMB wrote: Show quoted text
> This code: > > $x = 42; > $r->pnotes( 'foo', $x ); > ++$x; > warn $r->pnotes( 'foo', $x ); > > should print the value that was stored, 42, but it actually prints 43!
this is a documented feature and unlikely to change. see http://perl.apache.org/docs/2.0/api/Apache2/RequestUtil.html#C_pnotes_ "Note: sharing variables really means it. The variable is not copied. Only its reference count is incremented. If it is changed after being put in pnotes that change also affects the stored value. The following example illustrates the effect..."
I just want to add a note of explanation about why one particular form of code can trigger this problem in a subtle way: $r->pnotes( 'original_uri', $r->uri); # or some other method The uri method is implemented in XS using the mod_perl get_set_PVp macro which assigns to to the magic XS RETVAL pseudo variable. XS generates code that looks like dXSTARG; RETVAL = ...; sv_setpv(TARG, RETVAL); XSprePUSH; PUSHTARG; dXSTARG is defined by perl's XSUB.h as #define dXSTARG SV * targ = ((PL_op->op_private & OPpENTERSUB_HASTARG) ? PAD_SV (PL_op->op_targ) : sv_newmortal()) This means that in some cases the value returned by the method is actually stored in the op tree. Effectively stored in that line of code. So when pnotes takes an alias to the value it's actually taking an alias to the value stored in the op tree. So when that code is executed again, e.g., via an internal_redirect or subrequest, a new value is returned by $r->uri and stored in the op tree. Instantly all other pnotes set by that line of code will have their values changed. In my case when an internal redirect called $r->pnotes( 'original_uri', $r->uri) it also instantly changed the value of the $r->prev pnote. You can imagine how much fun that was to debug :)
It's not documented for mod_perl 1, which was where I encountered it. Assuming you still don't want to change it then the docs should be updated to explain the (very surprising) action at a distance caused by the use of op_targ.
I've just been bitten by this bug again! What's wrong with this code: sub foo { my ($r, $status, $why) = @_; $r->pnotes('foo', ($why) ? "$status:$why" : $status); return; } Nothing, except it doesn't work as expected due to this pnotes bug: If the same code is called in a sub-request then the pnote of $r->prev is magically updated at a distance to the same value! Try explain why that is to anyone not deeply familar with perl internals! The fix is to avoid pnotes taking a ref to the invisible op_targ embededed in the code by passing a simple lexical variable as the actual argument. That can be done in-line like this: sub mark_as_internally_redirected { my ($r, $status, $why) = @_; $r->pnotes('foo', my $tmp = (($why) ? "$status:$why" : $status)); return; } At the very least the docs need a big warning that the second argument to the pnotes method should only be a simple lexical variable.
Committed revision 1062310. Hi Tim, I've added your notes on how to share by value in pnotes to RequestUtil.pod. Thanks for the summary and workaround to the issue.