Обсуждение: missing "SPI_finish" that isn't missing

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

missing "SPI_finish" that isn't missing

От
Chapman Flack
Дата:
I probably just need to learn the right coding pattern for this.
What is it?

What I want this code to do:

a) If there is a table 'mytable', set theSavedStuff to a pstrdup'd in  TopMemoryContext copy of column s from one row
ifpresent.
 

b) In case of an error because there is no table 'mytable', just swallow  the error and do nothing. That's an expected
case.

c) In case of any other error (something unexpected), rethrow.

Case (a) works. Case (c) works. Case (b) works but produces this:

WARNING:  01000: transaction left non-empty SPI stack
HINT:  Check for missing "SPI_finish" calls.

I can verify that my SPI_finish call is being reached. In fact, in
case b, it returns SPI_ERROR_UNCONNECTED, as if even before my
PG_CATCH received control, the SPI "connection" got closed. But the
"SPI stack" still appears as if something is open?

In the context where this is called, there is no SPI connection already
open (if I try SPI_push_conditional at the start, it returns false).

I'm sure the only bug is in my understanding of how to approach this.
Can someone point out what I'm missing?

Thanks,
-Chap

{   MemoryContext curr;   SPI_connect();   curr = CurrentMemoryContext;   PG_TRY();   {       if ( SPI_OK_SELECT ==
SPI_execute(          "SELECT s FROM mytable", true, 1) && 1 == SPI_processed )       {
MemoryContextSwitchTo(TopMemoryContext);          theSavedStuff = (char const *)SPI_getvalue(
SPI_tuptable->vals[0],SPI_tuptable->tupdesc, 1);           MemoryContextSwitchTo(curr);       }   }   PG_CATCH();   {
   if ( ERRCODE_UNDEFINED_TABLE != geterrcode() )           PG_RE_THROW();       MemoryContextSwitchTo(curr);
FlushErrorState();  }   PG_END_TRY();   SPI_finish();
 
}



Re: missing "SPI_finish" that isn't missing

От
Tom Lane
Дата:
Chapman Flack <chap@anastigmatix.net> writes:
> I probably just need to learn the right coding pattern for this.
> What is it?

You can't just ignore a thrown error like that.  What you'd have to do
to make this coding pattern work is to set up a subtransaction, and either
commit it in the success path or roll it back upon catching an error.
(This is not terribly well documented, but the exception-block handling
in plpgsql provides a working model to follow.)  Without subtransaction
cleanup, lots of stuff will be left in incorrect states --- the unbalanced
SPI stack you're seeing is just the tip of the iceberg.

99% of the time, the path of least resistance at the C-code level is to
avoid expected error conditions in the first place, rather than try to
catch and clean up.  In this example, there are plenty of ways you might
test whether "mytable" exists before risking the SPI_execute call.
        regards, tom lane



Re: missing "SPI_finish" that isn't missing

От
Chapman Flack
Дата:
On 12/24/15 16:37, Tom Lane wrote:

> to make this coding pattern work is to set up a subtransaction, and either
> commit it in the success path or roll it back upon catching an error.
> (This is not terribly well documented, but the exception-block handling
> in plpgsql provides a working model to follow.)

Ok, I've just had a look at that in plpgsql, and I see what you mean about
the path of least resistance ... though it's not completely obvious to me
how many pieces of that are there for other plpgsql purposes, and how many
are inherently necessary just to be able to catch and recover from an
error thrown from SPI.

BeginInternalSubTransaction? CreateExecutorState? CreateExprContext?
xact callback? subxact callback?

> 99% of the time, the path of least resistance at the C-code level is to
> avoid expected error conditions in the first place, rather than try to
> catch and clean up.  In this example, there are plenty of ways you might
> test whether "mytable" exists before risking the SPI_execute call.

I can think of:

- another SPI query "select 1 from pg_catalog.pg_class join pg_catalog.pg_namespace n on relnamespace = n.oid where
nspname= 'myschema' and relname = 'mytable'"
 

- InvalidOid != get_relname_relid("mytable",   GetSysCacheOid1(NAMESPACENAME, CStringGetDatum("myschema")))

- ... other variations on those ...

The first one strikes me funny because it's an even heavier SQL query
to plan and execute just to find out if I can execute the original
trivial one. (Which doesn't really matter, admittedly, since I need to
do it exactly once.) The second one strikes me funny for the mixture of API
levels, undocumented low level to check existence followed by documented
SPI level for the original query. Almost tempts me to ditch SPI entirely
and learn how to use heap_* methods for the single trivial row retrieval
I need, only by the time I've figured out all that, I'll have forgotten
what I was trying to do in the first place.

Is there a particular, reasonably tidy idiom that has emerged as the usual,
customary approach to a task like this?

Thanks,
-Chap



Re: missing "SPI_finish" that isn't missing

От
Tom Lane
Дата:
Chapman Flack <chap@anastigmatix.net> writes:
> Is there a particular, reasonably tidy idiom that has emerged as the usual,
> customary approach to a task like this?

You could construct a RangeVar node and then use RangeVarGetRelid,
but I dunno that that's really any cleaner than inquiring about it
using the syscaches.  The syscache APIs are really quite unlikely
to change in any meaningful way, if only because there's too much
code depending on them.
        regards, tom lane