Обсуждение: plpython is broken for recursive use

Поиск
Список
Период
Сортировка

plpython is broken for recursive use

От
Tom Lane
Дата:
I looked into bug #13683,
http://www.postgresql.org/message-id/20151015135804.3019.31908@wrigleys.postgresql.org

It looks to me like plpython basically doesn't work at all for re-entrant
calls to plpython functions, because all executions of a given function
share the same "globals" dict, which doesn't seem sane.  Shouldn't each
execution have its own?

I believe the specific sequence of events shown here is

1. On the way into the recursion, each level does PLy_function_build_args
which sets "i" as a key in the proc's globals dict.

2. On the way out, each level does PLy_function_delete_args which deletes
"i" from the dict.  At levels after the innermost, this results in setting
"KeyError: 'i'" as the active Python error, because that key is already
gone.  We fail to notice the error indicator, though.

3. The pending error indicator causes the attempt to "def" the second
function to fail.

If I change the test case to

create or replace function a(i integer) returns integer as
$$
if i > 1:   return i
j = plpy.execute("select a(%s)" % (i+1))[0]['a']
return i+j
$$ language plpythonu;

select a(0);

then I get

ERROR:  spiexceptions.ExternalRoutineException: NameError: global name 'i' is not defined
CONTEXT:  Traceback (most recent call last): PL/Python function "a", line 4, in <module>   j = plpy.execute("select
a(%s)"% (i+1))[0]['a']
 
PL/Python function "a"

which shows that a()'s own access to "i" is broken too, though I confess
I'm not sure why it's failing at line 4 rather than 5.

I think this means that we should get rid of proc->globals and instead
manufacture a new globals dict locally in each call to PLy_exec_function
or PLy_exec_trigger.  For SETOF functions it would be necessary to keep
the globals dict reference somewhere in the FunctionCallInfo struct,
probably.  Not sure about cleaning up after an error that occurs between
SETOF callbacks --- we might need plpython to grow an at-abort callback to
do decref's on unreleased dicts.

Thoughts?
        regards, tom lane



Re: plpython is broken for recursive use

От
Josh Berkus
Дата:
On 10/15/2015 01:10 PM, Tom Lane wrote:
> I think this means that we should get rid of proc->globals and instead
> manufacture a new globals dict locally in each call to PLy_exec_function
> or PLy_exec_trigger.  For SETOF functions it would be necessary to keep
> the globals dict reference somewhere in the FunctionCallInfo struct,
> probably.  Not sure about cleaning up after an error that occurs between
> SETOF callbacks --- we might need plpython to grow an at-abort callback to
> do decref's on unreleased dicts.

Don't people currently specifically treat the state of the globals dict
as a feature?  That is, make use of the fact that you can store
session-persistent data in it?

-- 
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com



Re: plpython is broken for recursive use

От
Andrew Dunstan
Дата:

On 10/15/2015 05:16 PM, Josh Berkus wrote:
> On 10/15/2015 01:10 PM, Tom Lane wrote:
>> I think this means that we should get rid of proc->globals and instead
>> manufacture a new globals dict locally in each call to PLy_exec_function
>> or PLy_exec_trigger.  For SETOF functions it would be necessary to keep
>> the globals dict reference somewhere in the FunctionCallInfo struct,
>> probably.  Not sure about cleaning up after an error that occurs between
>> SETOF callbacks --- we might need plpython to grow an at-abort callback to
>> do decref's on unreleased dicts.
> Don't people currently specifically treat the state of the globals dict
> as a feature?  That is, make use of the fact that you can store
> session-persistent data in it?
>


That was the thinking behind plperl's %_SHARED, and I assume this is 
similar.

cheers

andrew




Re: plpython is broken for recursive use

От
"Joshua D. Drake"
Дата:
On 10/15/2015 02:16 PM, Josh Berkus wrote:
> On 10/15/2015 01:10 PM, Tom Lane wrote:
>> I think this means that we should get rid of proc->globals and instead
>> manufacture a new globals dict locally in each call to PLy_exec_function
>> or PLy_exec_trigger.  For SETOF functions it would be necessary to keep
>> the globals dict reference somewhere in the FunctionCallInfo struct,
>> probably.  Not sure about cleaning up after an error that occurs between
>> SETOF callbacks --- we might need plpython to grow an at-abort callback to
>> do decref's on unreleased dicts.
>
> Don't people currently specifically treat the state of the globals dict
> as a feature?  That is, make use of the fact that you can store
> session-persistent data in it?

Yes, just like the plperl feature.

jD

>


-- 
Command Prompt, Inc. - http://www.commandprompt.com/  503-667-4564
PostgreSQL Centered full stack support, consulting and development.
New rule for social situations: "If you think to yourself not even
JD would say this..." Stop and shut your mouth. It's going to be bad.



Re: plpython is broken for recursive use

От
Tom Lane
Дата:
Andrew Dunstan <andrew@dunslane.net> writes:
> On 10/15/2015 05:16 PM, Josh Berkus wrote:
>> On 10/15/2015 01:10 PM, Tom Lane wrote:
>>> I think this means that we should get rid of proc->globals and instead
>>> manufacture a new globals dict locally in each call to PLy_exec_function
>>> or PLy_exec_trigger.

>> Don't people currently specifically treat the state of the globals dict
>> as a feature?  That is, make use of the fact that you can store
>> session-persistent data in it?

> That was the thinking behind plperl's %_SHARED, and I assume this is 
> similar.

I poked around in the code a bit more, and I now see that the procedure's
"globals" dictionary actually is global, in the sense that it's not the
most closely nested namespace when the procedure's code runs.  It's not
so surprising that if you write "global foo" then foo will have a value
that persists across calls.  But then it is fair to ask what the heck is
the point of the "SD" dict, which has got *exactly* the same lifespan as
the procedure's globals dictionary --- if you want to share a value across
multiple executions of the procedure, it does not matter whether you make
it within SD or just declare it "global".  So why'd we bother with SD?

Anyway, the real problem here is the decision to pass procedure arguments
by assigning them to keys in the global dict.  That is just brain-dead,
both because it means that recursive calls can't possibly work and because
it creates the bizarre scoping behavior mentioned in
http://www.postgresql.org/docs/devel/static/plpython-funcs.html

I suppose we cannot change that in back branches for fear of creating
subtle compatibility problems, but surely we can do better going forward?

Another interesting point is that if the procedure cache entry is rebuilt
for any reason whatever, such as an ALTER on the function definition or
even just an sinval flush, you lose whatever may have been in either SD or
the procedure's "global" namespace.  That seems like a rather surprising
implementation behavior.  I'd have expected plpython to make some effort
to make "SD" have actual session lifespan, not just "maybe it'll survive
and maybe it won't".
        regards, tom lane



Re: plpython is broken for recursive use

От
Tom Lane
Дата:
I wrote:
> Anyway, the real problem here is the decision to pass procedure arguments
> by assigning them to keys in the global dict.  That is just brain-dead,
> both because it means that recursive calls can't possibly work and because
> it creates the bizarre scoping behavior mentioned in
> http://www.postgresql.org/docs/devel/static/plpython-funcs.html
> I suppose we cannot change that in back branches for fear of creating
> subtle compatibility problems, but surely we can do better going forward?

I looked into this a little more. The Python function we use to execute
plpython code, PyEval_EvalCode, has arguments to supply both a global and
a local namespace dict.  Right now we just pass proc->globals for both,
which sounds and is weird.  However, what we're actually executing in
that context is just an argument-less function call, eg
"__plpython_procedure_foo_nnn()".  So I believe that no use of the passed
"local" namespace actually happens: the user-supplied code executes with
proc->globals as its global namespace and some transient dict created by
the function call action as a local namespace.

This seems like a very Rube-Goldbergian way of setting up a local
namespace for the user-defined code.  I think perhaps what we should do
is:

1. Compile the user-supplied code directly into a code object, without
wrapping it in a "def".  (Hence, PLy_procedure_munge_source goes away
entirely, which would be nice.)  Forget about generating a code object
containing a call, too.

2. During function call startup, create a dict to be the local namespace
for that call.  Shove the argument values into entries in that dict, not
the global one.

3. Run the user-supplied code directly with PyEval_EvalCode, passing
proc->globals as its global namespace and the transient dict as its
local namespace.

This would fix the problem with recursive calls and probably be a lot
cleaner as well.  It would not be 100% backwards compatible, because
the technique shown in
http://www.postgresql.org/docs/devel/static/plpython-funcs.html
of declaring an argument "global" would not work, nor be necessary,
anymore.  That does not seem like a bad thing, but it would be a reason
not to back-patch.

Thoughts?
        regards, tom lane



Re: plpython is broken for recursive use

От
Tom Lane
Дата:
I wrote:
> This seems like a very Rube-Goldbergian way of setting up a local
> namespace for the user-defined code.  I think perhaps what we should do
> is:

> 1. Compile the user-supplied code directly into a code object, without
> wrapping it in a "def".  (Hence, PLy_procedure_munge_source goes away
> entirely, which would be nice.)  Forget about generating a code object
> containing a call, too.

After further study, it appears this approach won't work because it
breaks "yield" --- AFAICT, Python only allows "yield" inside a "def".

At this point I think what we need is to find a way of passing the
function parameters honestly, that is, as actual parameters in the
manufactured call.  I've not looked into how that might be done.
        regards, tom lane



Re: plpython is broken for recursive use

От
Andrew Dunstan
Дата:

On 10/16/2015 10:03 PM, Tom Lane wrote:
> I wrote:
>> This seems like a very Rube-Goldbergian way of setting up a local
>> namespace for the user-defined code.  I think perhaps what we should do
>> is:
>> 1. Compile the user-supplied code directly into a code object, without
>> wrapping it in a "def".  (Hence, PLy_procedure_munge_source goes away
>> entirely, which would be nice.)  Forget about generating a code object
>> containing a call, too.
> After further study, it appears this approach won't work because it
> breaks "yield" --- AFAICT, Python only allows "yield" inside a "def".
>
> At this point I think what we need is to find a way of passing the
> function parameters honestly, that is, as actual parameters in the
> manufactured call.  I've not looked into how that might be done.


+1 if it can be done

I haven't looked very closely at plpython for a long time, but anything 
else seems ugly.

cheers

andrew