pg.ddx.io  pgsql-hackers@postgresql.org mailing list archive  
help / color / mirror / Atom feed
From: Noah Misch <noah@leadboat.com>
To: Guillaume Lelarge <guillaume@lelarge.info>
To: Tom Lane <tgl@sss.pgh.pa.us>
Cc: Benoit Lobréau <benoit.lobreau@gmail.com>
Cc: PostgreSQL Developers <pgsql-hackers@postgresql.org>
Cc: Dr. Michael Meskes <michael.meskes@credativ.com>
Subject: Re: Probable memory leak with ECPG and AIX
Date: Sun, 17 Apr 2022 21:16:45 -0700
Message-ID: <20220418041645.GA1134228@rfd.leadboat.com> (raw)
In-Reply-To: <20220102000750.GB54621@rfd.leadboat.com>
References: <CAECtzeWMVW=b8=DJje_4F=OG9BeYLh-phLcCxZ-FBpii+O=SBQ@mail.gmail.com>
	<20211212073411.GA2218865@rfd.leadboat.com>
	<CAPE8EZ4ca2s5tDVHZzQ=iGh+rD8VX5r4Gk8zTTEeh9VaDOMnuQ@mail.gmail.com>
	<CAPE8EZ42eWAemjj58DO0WArCMDG2VYWut38aAGo9V8QH8ETqrg@mail.gmail.com>
	<20220101074055.GA54621@rfd.leadboat.com>
	<2865639.1641054902@sss.pgh.pa.us>
	<20220102000750.GB54621@rfd.leadboat.com>

On Sat, Jan 01, 2022 at 04:07:50PM -0800, Noah Misch wrote:
> On Sat, Jan 01, 2022 at 11:35:02AM -0500, Tom Lane wrote:
> > Noah Misch <noah@leadboat.com> writes:
> > > I get the same results.  The leak arises because AIX freelocale() doesn't free
> > > all memory allocated in newlocale().  The following program uses trivial
> > > memory on GNU/Linux, but it leaks like you're seeing on AIX:
> > 
> > Bleah.
> > 
> > > If you have access to file an AIX bug, I recommend doing so.  If we want
> > > PostgreSQL to work around this, one idea is to have ECPG do this newlocale()
> > > less often.  For example, do it once per process or once per connection
> > > instead of once per ecpg_do_prologue().
> > 
> > It's worse than that: see also ECPGget_desc().  Seems like a case
> > could be made for doing something about this just on the basis
> > of cycles expended, never mind freelocale() bugs.
> 
> Agreed.  Once per process seems best.  I only hesitated before since it means
> nothing will free this storage, which could be annoying in the context of
> Valgrind and similar.  However, ECPG already has bits of never-freed memory in
> the form of pthread_key_create() calls having no pthread_key_delete(), so I
> don't mind adding a bit more.

The comparison to pthread_key_create() wasn't completely fair.  While POSIX
welcomes pthread_key_create() to fail with ENOMEM, the glibc implementation
appears not to allocate memory.  Even so, I'm okay leaking one newlocale() per
process lifetime.

I had expected to use pthread_once() for the newlocale() call, but there would
be no useful way to report failure and try again later.  Instead, I called
newlocale() while ECPGconnect() holds connections_mutex.  See log message and
comments for details.  I tested "./configure ac_cv_func_uselocale=no ..." and
tested the scenario of newlocale() failing every time.

Attachments:

newlocale-once-ecpg-v1.patch (text/plain, 6.7K) download Author: Noah Misch <noah@leadboat.com> Commit: Noah Misch <noah@leadboat.com> ecpglib: call newlocale() one per process. ecpglib has been calling it once per SQL query and once per EXEC SQL GET DESCRIPTOR. Instead, if newlocale() has not succeeded before, call it while establishing a connection. This mitigates three problems: - If newlocale() failed in EXEC SQL GET DESCRIPTOR, the command silently proceeded without the intended locale change. - On AIX, each newlocale()+freelocale() cycle leaked memory. - newlocale() CPU usage may have been nontrivial. Fail the connection attempt if newlocale() fails. Rerrange ecpg_do_prologue() to validate the connection before its uselocale(). The sort of program that may regress is one running in an environment where newlocale() fails. If that program establishes connections without running SQL statements, it will stop working in response to this change. I'm betting against the importance of such an ECPG use case. Most SQL execution (any using ECPGdo()) has long required newlocale() success, so there's little a connection could do without newlocale(). Back-patch to v10 (all supported versions). Reviewed by FIXME. Reported by Guillaume Lelarge. Discussion: https://postgr.es/m/20220101074055.GA54621@rfd.leadboat.com diff --git a/src/interfaces/ecpg/ecpglib/connect.c b/src/interfaces/ecpg/ecpglib/connect.c index 8dfcabe..2e4b003 100644 --- a/src/interfaces/ecpg/ecpglib/connect.c +++ b/src/interfaces/ecpg/ecpglib/connect.c @@ -10,6 +10,10 @@ #include "ecpgtype.h" #include "sqlca.h" +#ifdef HAVE_USELOCALE +locale_t ecpg_clocale; +#endif + #ifdef ENABLE_THREAD_SAFETY static pthread_mutex_t connections_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_key_t actual_connection_key; @@ -502,6 +506,37 @@ ECPGconnect(int lineno, int c, const char *name, const char *user, const char *p #ifdef ENABLE_THREAD_SAFETY pthread_mutex_lock(&connections_mutex); #endif +#ifdef HAVE_USELOCALE + if (!ecpg_clocale) + { + ecpg_clocale = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0); + if (!ecpg_clocale) + { +#ifdef ENABLE_THREAD_SAFETY + pthread_mutex_unlock(&connections_mutex); +#endif + ecpg_raise(lineno, ECPG_OUT_OF_MEMORY, + ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL); + if (host) + ecpg_free(host); + if (port) + ecpg_free(port); + if (options) + ecpg_free(options); + if (realname) + ecpg_free(realname); + if (dbname) + ecpg_free(dbname); + if (conn_keywords) + ecpg_free(conn_keywords); + if (conn_values) + ecpg_free(conn_values); + free(this); + return false; + } + } +#endif + if (connection_name != NULL) this->name = ecpg_strdup(connection_name, lineno); else diff --git a/src/interfaces/ecpg/ecpglib/descriptor.c b/src/interfaces/ecpg/ecpglib/descriptor.c index 369c2f0..f1898de 100644 --- a/src/interfaces/ecpg/ecpglib/descriptor.c +++ b/src/interfaces/ecpg/ecpglib/descriptor.c @@ -486,9 +486,16 @@ ECPGget_desc(int lineno, const char *desc_name, int index,...) /* since the database gives the standard decimal point */ /* (see comments in execute.c) */ #ifdef HAVE_USELOCALE - stmt.clocale = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0); - if (stmt.clocale != (locale_t) 0) - stmt.oldlocale = uselocale(stmt.clocale); + + /* + * To get here, the above PQnfields() test must have found nonzero + * fields. One needs a connection to create such a descriptor. (EXEC + * SQL SET DESCRIPTOR can populate the descriptor's "items", but it + * can't change the descriptor's PQnfields().) Any successful + * connection initializes ecpg_clocale. + */ + Assert(ecpg_clocale); + stmt.oldlocale = uselocale(ecpg_clocale); #else #ifdef HAVE__CONFIGTHREADLOCALE stmt.oldthreadlocale = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); @@ -504,8 +511,6 @@ ECPGget_desc(int lineno, const char *desc_name, int index,...) #ifdef HAVE_USELOCALE if (stmt.oldlocale != (locale_t) 0) uselocale(stmt.oldlocale); - if (stmt.clocale) - freelocale(stmt.clocale); #else if (stmt.oldlocale) { diff --git a/src/interfaces/ecpg/ecpglib/ecpglib_extern.h b/src/interfaces/ecpg/ecpglib/ecpglib_extern.h index 1a98dea..c438cfb 100644 --- a/src/interfaces/ecpg/ecpglib/ecpglib_extern.h +++ b/src/interfaces/ecpg/ecpglib/ecpglib_extern.h @@ -59,6 +59,10 @@ struct ECPGtype_information_cache enum ARRAY_TYPE isarray; }; +#ifdef HAVE_USELOCALE +extern locale_t ecpg_clocale; /* LC_NUMERIC=C */ +#endif + /* structure to store one statement */ struct statement { @@ -73,7 +77,6 @@ struct statement struct variable *inlist; struct variable *outlist; #ifdef HAVE_USELOCALE - locale_t clocale; locale_t oldlocale; #else char *oldlocale; diff --git a/src/interfaces/ecpg/ecpglib/execute.c b/src/interfaces/ecpg/ecpglib/execute.c index 6a7ef0b..2ebe665 100644 --- a/src/interfaces/ecpg/ecpglib/execute.c +++ b/src/interfaces/ecpg/ecpglib/execute.c @@ -101,10 +101,7 @@ free_statement(struct statement *stmt) free_variable(stmt->outlist); ecpg_free(stmt->command); ecpg_free(stmt->name); -#ifdef HAVE_USELOCALE - if (stmt->clocale) - freelocale(stmt->clocale); -#else +#ifndef HAVE_USELOCALE ecpg_free(stmt->oldlocale); #endif ecpg_free(stmt); @@ -1965,6 +1962,15 @@ ecpg_do_prologue(int lineno, const int compat, const int force_indicator, return false; } +#ifdef ENABLE_THREAD_SAFETY + ecpg_pthreads_init(); +#endif + + con = ecpg_get_connection(connection_name); + + if (!ecpg_init(con, connection_name, lineno)) + return false; + stmt = (struct statement *) ecpg_alloc(sizeof(struct statement), lineno); if (stmt == NULL) @@ -1979,13 +1985,13 @@ ecpg_do_prologue(int lineno, const int compat, const int force_indicator, * treat that situation as if the function doesn't exist. */ #ifdef HAVE_USELOCALE - stmt->clocale = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0); - if (stmt->clocale == (locale_t) 0) - { - ecpg_do_epilogue(stmt); - return false; - } - stmt->oldlocale = uselocale(stmt->clocale); + + /* + * Since ecpg_init() succeeded, we have a connection. Any successful + * connection initializes ecpg_clocale. + */ + Assert(ecpg_clocale); + stmt->oldlocale = uselocale(ecpg_clocale); if (stmt->oldlocale == (locale_t) 0) { ecpg_do_epilogue(stmt); @@ -2004,18 +2010,6 @@ ecpg_do_prologue(int lineno, const int compat, const int force_indicator, setlocale(LC_NUMERIC, "C"); #endif -#ifdef ENABLE_THREAD_SAFETY - ecpg_pthreads_init(); -#endif - - con = ecpg_get_connection(connection_name); - - if (!ecpg_init(con, connection_name, lineno)) - { - ecpg_do_epilogue(stmt); - return false; - } - /* * If statement type is ECPGst_prepnormal we are supposed to prepare the * statement before executing them

view thread (20+ messages)  latest in thread

Message-ID: <20220418041645.GA1134228@rfd.leadboat.com>
Permalink:  ../20220418041645.GA1134228@rfd.leadboat.com/
Also on:    postgresql.org/message-id/20220418041645.GA1134228@rfd.leadboat.com

 ·  · 

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: pgsql-hackers@postgresql.org
  Cc: noah@leadboat.com, guillaume@lelarge.info, tgl@sss.pgh.pa.us, benoit.lobreau@gmail.com, michael.meskes@credativ.com
  Subject: Re: Probable memory leak with ECPG and AIX
  In-Reply-To: <20220418041645.GA1134228@rfd.leadboat.com>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox