Thread
Commits
-
Applied patch by Peter Harris to free auto_mem structure on connect
- 0c96e42797db 8.2.0 cited
-
Per-thread leak in ECPG's memory.c
Thomas Munro <thomas.munro@gmail.com> — 2026-06-29T13:19:29Z
Hi, ECPG's auto_mem_destructor() doesn't seem quite right: 1. POSIX thread-specific keys are reset to NULL before their at-exit destructors are called, so its call to ECPGfree_auto_mem() gets NULL from get_auto_allocs(), so nothing much happens. It should use the value passed to the destructor. 2. ECPGfree_auto_mem() doesn't seem to be the right thing to do anyway, because the user is expected to free heap objects allocated by the library, for example where src/interfaces/ecpg/test/thread/alloc.pgc does this: char **r = NULL; ... for (i = 1; i <= REPEATS; ++i) { EXEC SQL SELECT relname INTO :r FROM pg_class WHERE relname = 'pg_class'; free(r); r = NULL; } With a fix for only problem #1 in place, the thread-exit destructor double-frees "r" from the final loop. I *think* what is wanted here is ecpg_clear_auto_mem(), to free just the list structure and not the values themselves. Draft patch like that attached. It still leaks on Windows, but that's a known issue and I have a fix for that as part of a larger refactoring of thread-related stuff, more on that shortly. This looked like a bug to report separately first. Hmm, I wonder why ecpg_raise() frees auto-allocated values for all connections just because one connection raised an error. -
Re: Per-thread leak in ECPG's memory.c
Thom Brown <thom@linux.com> — 2026-06-29T14:30:51Z
On Mon, 29 Jun 2026 at 14:09, Thomas Munro <thomas.munro@gmail.com> wrote: > > Hi, > > ECPG's auto_mem_destructor() doesn't seem quite right: > > 1. POSIX thread-specific keys are reset to NULL before their at-exit > destructors are called, so its call to ECPGfree_auto_mem() gets NULL > from get_auto_allocs(), so nothing much happens. It should use the > value passed to the destructor. > > 2. ECPGfree_auto_mem() doesn't seem to be the right thing to do > anyway, because the user is expected to free heap objects allocated by > the library, for example where > src/interfaces/ecpg/test/thread/alloc.pgc does this: > > char **r = NULL; > ... > for (i = 1; i <= REPEATS; ++i) > { > EXEC SQL SELECT relname INTO :r FROM pg_class WHERE relname = > 'pg_class'; > free(r); > r = NULL; > } > > With a fix for only problem #1 in place, the thread-exit destructor > double-frees "r" from the final loop. I *think* what is wanted here > is ecpg_clear_auto_mem(), to free just the list structure and not the > values themselves. Draft patch like that attached. > > It still leaks on Windows, but that's a known issue and I have a fix > for that as part of a larger refactoring of thread-related stuff, more > on that shortly. This looked like a bug to report separately first. > > Hmm, I wonder why ecpg_raise() frees auto-allocated values for all > connections just because one connection raised an error. I got curious, and found this in connect.c: /* * clear auto_mem structure because some error handling functions might * access it */ ecpg_clear_auto_mem(); If I skip the pgindent commit, the blame gives us this commit: commit 0c96e42797dbe2918c909209abbaee4d2c985e38 Author: Michael Meskes <meskes@postgresql.org> Date: Wed Nov 8 10:46:47 2006 +0000 Applied patch by Peter Harris to free auto_mem structure on connect Digging into bug reports from that time, we get: https://www.postgresql.org/message-id/200611071423.kA7ENpJ1080586%40wwwmaster.postgresql.org "When using more than one database connection with ECPG, you might have obtained and freed blocks of data on one connection before trying to open the other. If the second connection fails, ECPGraise will be called and call ECPGfree_auto_mem. This can cause an invalid free() of a pointer you've already freed." I couldn't find the actual patch though. Thom -
Re: Per-thread leak in ECPG's memory.c
Thomas Munro <thomas.munro@gmail.com> — 2026-06-29T23:34:31Z
On Tue, Jun 30, 2026 at 2:31 AM Thom Brown <thom@linux.com> wrote: > On Mon, 29 Jun 2026 at 14:09, Thomas Munro <thomas.munro@gmail.com> wrote: > > Hmm, I wonder why ecpg_raise() frees auto-allocated values for all > > connections just because one connection raised an error. > Digging into bug reports from that time, we get: > https://www.postgresql.org/message-id/200611071423.kA7ENpJ1080586%40wwwmaster.postgresql.org > > "When using more than one database connection with ECPG, you might have > obtained and freed blocks of data on one connection before trying to open > the other. > If the second connection fails, ECPGraise will be called and call > ECPGfree_auto_mem. This can cause an invalid free() of a pointer you've > already freed." Thanks for finding that. Hmm, OK, but I was wondering about the opposite scenario, where you *haven't* freed blocks of data before doing something on another connection that frees everything for the thread: EXEC SQL AT con1 SELECT datname INTO :anything FROM pg_database; EXEC SQL AT con2 ... something that reaches ecpg_raise() ... /* Why should "anything" not be accessible, and mine to free(), here? */
-
Re: Per-thread leak in ECPG's memory.c
Thom Brown <thom@linux.com> — 2026-06-30T01:21:20Z
On Tue, 30 Jun 2026 at 00:24, Thomas Munro <thomas.munro@gmail.com> wrote: > > On Tue, Jun 30, 2026 at 2:31 AM Thom Brown <thom@linux.com> wrote: > > On Mon, 29 Jun 2026 at 14:09, Thomas Munro <thomas.munro@gmail.com> wrote: > > > Hmm, I wonder why ecpg_raise() frees auto-allocated values for all > > > connections just because one connection raised an error. > > > Digging into bug reports from that time, we get: > > https://www.postgresql.org/message-id/200611071423.kA7ENpJ1080586%40wwwmaster.postgresql.org > > > > "When using more than one database connection with ECPG, you might have > > obtained and freed blocks of data on one connection before trying to open > > the other. > > If the second connection fails, ECPGraise will be called and call > > ECPGfree_auto_mem. This can cause an invalid free() of a pointer you've > > already freed." > > Thanks for finding that. Hmm, OK, but I was wondering about the > opposite scenario, where you *haven't* freed blocks of data before > doing something on another connection that frees everything for the > thread: > > EXEC SQL AT con1 SELECT datname INTO :anything FROM pg_database; > EXEC SQL AT con2 ... something that reaches ecpg_raise() ... > > /* Why should "anything" not be accessible, and mine to free(), here? */ I see, yeah, that does seem wrong. So would it just be a matter of moving ecpg_clear_auto_mem() out of ecpg_do_prologue() and put it into ecpg_do_epilogue() so that it cleans up at the end of the statement instead of the start of the next one? Thom
-
Re: Per-thread leak in ECPG's memory.c
Thomas Munro <thomas.munro@gmail.com> — 2026-06-30T02:16:47Z
On Tue, Jun 30, 2026 at 1:21 PM Thom Brown <thom@linux.com> wrote: > On Tue, 30 Jun 2026 at 00:24, Thomas Munro <thomas.munro@gmail.com> wrote: > > EXEC SQL AT con1 SELECT datname INTO :anything FROM pg_database; > > EXEC SQL AT con2 ... something that reaches ecpg_raise() ... > > > > /* Why should "anything" not be accessible, and mine to free(), here? */ > > I see, yeah, that does seem wrong. So would it just be a matter of > moving ecpg_clear_auto_mem() out of ecpg_do_prologue() and put it into > ecpg_do_epilogue() so that it cleans up at the end of the statement > instead of the start of the next one? Oh, that sounds plausible and simple! Have I understood the lifetime/ownership rule correctly here?: after a successful statement, ownership of auto-allocated objects is transferred to the caller, and the fact that we currently don't "clear" (forget, disown) the list until the next statement is just an implementation detail and we could indeed make it "eager". In contrast, ECPGfree_auto_mem() is only used to clean up partially allocated variables on failure, since then the caller doesn't own anything. If that all makes sense, then perhaps the auto-mem list could always be NULL between ECPG statements, meaning that ownership of "anything" would already have been transferred to the caller (cleared) in this example. And then perhaps we wouldn't even need a thread-exit destructor here? On the other hand, that'd be a subtle semantic change that could upset an application. So perhaps this would have to be a well-signalled master-only change, if it makes sense to do it? I haven't actually tried any of that or written a test (feel free if you're interested in looking at this), I'm just trying to confirm my understanding of the design. This is independent of the destructor issue I started with, which I assume we'd want to back-patch. It would be nice if we could do something like the above in master and that leads to deleting the destructor though (we still need thread-exit cleanup for other ECPG things though, and need a new system for that that also works on Windows, more patches soon..., so it's not a big deal either way).
-
Re: Per-thread leak in ECPG's memory.c
Thom Brown <thom@linux.com> — 2026-06-30T05:56:04Z
On Tue, 30 Jun 2026 at 03:17, Thomas Munro <thomas.munro@gmail.com> wrote: > > On Tue, Jun 30, 2026 at 1:21 PM Thom Brown <thom@linux.com> wrote: > > On Tue, 30 Jun 2026 at 00:24, Thomas Munro <thomas.munro@gmail.com> wrote: > > > EXEC SQL AT con1 SELECT datname INTO :anything FROM pg_database; > > > EXEC SQL AT con2 ... something that reaches ecpg_raise() ... > > > > > > /* Why should "anything" not be accessible, and mine to free(), here? */ > > > > I see, yeah, that does seem wrong. So would it just be a matter of > > moving ecpg_clear_auto_mem() out of ecpg_do_prologue() and put it into > > ecpg_do_epilogue() so that it cleans up at the end of the statement > > instead of the start of the next one? > > Oh, that sounds plausible and simple! Have I understood the > lifetime/ownership rule correctly here?: after a successful statement, > ownership of auto-allocated objects is transferred to the caller, and > the fact that we currently don't "clear" (forget, disown) the list > until the next statement is just an implementation detail and we could > indeed make it "eager". In contrast, ECPGfree_auto_mem() is only used > to clean up partially allocated variables on failure, since then the > caller doesn't own anything. If that all makes sense, then perhaps > the auto-mem list could always be NULL between ECPG statements, > meaning that ownership of "anything" would already have been > transferred to the caller (cleared) in this example. And then perhaps > we wouldn't even need a thread-exit destructor here? On the other > hand, that'd be a subtle semantic change that could upset an > application. So perhaps this would have to be a well-signalled > master-only change, if it makes sense to do it? I haven't actually > tried any of that or written a test (feel free if you're interested in > looking at this), I'm just trying to confirm my understanding of the > design. > > This is independent of the destructor issue I started with, which I > assume we'd want to back-patch. It would be nice if we could do > something like the above in master and that leads to deleting the > destructor though (we still need thread-exit cleanup for other ECPG > things though, and need a new system for that that also works on > Windows, more patches soon..., so it's not a big deal either way). AFAICT, ownership transfers to the caller on success, and the lazy clearing at next statement is just timing. But I think ECPGfree_auto_mem() isn't only internal failure-cleanup. It's exported and called by application code as the bulk-free for GET DESCRIPTOR ... INTO :array results, which the caller does own. I'm looking at dynalloc.pgc, line 83, and those arrays look like they're auto-allocated in descriptor.c: Line 437 onward, for the data array: /* allocate storage if needed */ if (arrsize == 0 && *(void **) var == NULL) { void *mem = ecpg_auto_alloc(offset * ntuples, lineno); if (!mem) { va_end(args); return false; } *(void **) var = mem; var = mem; } And there's another one at line 557 onward for the indicator array So maybe it really does both cleanup on failure, and free descriptor arrays that the caller owns. The change I semi-proposed would mean the list would be NULL between statements, which holds for SELECT-INTO, although not for descriptors. I don't think the destructor fully goes away. A thread that does GET DESCRIPTOR and exits before bulk-freeing still leaves nodes on the list. So thread-exit node cleanup is still needed for the descriptor case, which matches what you said about needing a new Windows-capable mechanism anyway. Thom -
Re: Per-thread leak in ECPG's memory.c
Thom Brown <thom@linux.com> — 2026-07-02T02:46:14Z
On Tue, 30 Jun 2026 at 06:56, Thom Brown <thom@linux.com> wrote: > > On Tue, 30 Jun 2026 at 03:17, Thomas Munro <thomas.munro@gmail.com> wrote: > > > > On Tue, Jun 30, 2026 at 1:21 PM Thom Brown <thom@linux.com> wrote: > > > On Tue, 30 Jun 2026 at 00:24, Thomas Munro <thomas.munro@gmail.com> wrote: > > > > EXEC SQL AT con1 SELECT datname INTO :anything FROM pg_database; > > > > EXEC SQL AT con2 ... something that reaches ecpg_raise() ... > > > > > > > > /* Why should "anything" not be accessible, and mine to free(), here? */ > > > > > > I see, yeah, that does seem wrong. So would it just be a matter of > > > moving ecpg_clear_auto_mem() out of ecpg_do_prologue() and put it into > > > ecpg_do_epilogue() so that it cleans up at the end of the statement > > > instead of the start of the next one? > > > > Oh, that sounds plausible and simple! Have I understood the > > lifetime/ownership rule correctly here?: after a successful statement, > > ownership of auto-allocated objects is transferred to the caller, and > > the fact that we currently don't "clear" (forget, disown) the list > > until the next statement is just an implementation detail and we could > > indeed make it "eager". In contrast, ECPGfree_auto_mem() is only used > > to clean up partially allocated variables on failure, since then the > > caller doesn't own anything. If that all makes sense, then perhaps > > the auto-mem list could always be NULL between ECPG statements, > > meaning that ownership of "anything" would already have been > > transferred to the caller (cleared) in this example. And then perhaps > > we wouldn't even need a thread-exit destructor here? On the other > > hand, that'd be a subtle semantic change that could upset an > > application. So perhaps this would have to be a well-signalled > > master-only change, if it makes sense to do it? I haven't actually > > tried any of that or written a test (feel free if you're interested in > > looking at this), I'm just trying to confirm my understanding of the > > design. > > > > This is independent of the destructor issue I started with, which I > > assume we'd want to back-patch. It would be nice if we could do > > something like the above in master and that leads to deleting the > > destructor though (we still need thread-exit cleanup for other ECPG > > things though, and need a new system for that that also works on > > Windows, more patches soon..., so it's not a big deal either way). > > AFAICT, ownership transfers to the caller on success, and the lazy > clearing at next statement is just timing. But I think > ECPGfree_auto_mem() isn't only internal failure-cleanup. It's exported > and called by application code as the bulk-free for GET DESCRIPTOR ... > INTO :array results, which the caller does own. > > I'm looking at dynalloc.pgc, line 83, and those arrays look like > they're auto-allocated in descriptor.c: > > Line 437 onward, for the data array: > > /* allocate storage if needed */ > if (arrsize == 0 && *(void **) var == NULL) > { > void *mem = ecpg_auto_alloc(offset * ntuples, lineno); > > if (!mem) > { > va_end(args); > return false; > } > *(void **) var = mem; > var = mem; > } > > And there's another one at line 557 onward for the indicator array Correction: the allocation I pointed at in descriptor.c (line 437) isn't for the data array. That branch handles RETURNED_LENGTH/RETURNED_OCTET_LENGTH items. The data and indicator arrays in dynalloc.pgc actually come from ecpg_store_result(), execute.c lines 398 and 409, which ECPGget_desc() calls at descriptor.c line 519. The one at line 557 is for an indicator given without a data variable. Doesn't change the conclusion: everything GET DESCRIPTOR materialises into a zero-sized pointer variable ends up on the auto-mem list, so the bulk-free usage and the need for thread-exit node cleanup still stand. Thom -
Re: Per-thread leak in ECPG's memory.c
Heikki Linnakangas <hlinnaka@iki.fi> — 2026-07-06T19:34:59Z
On 29/06/2026 16:19, Thomas Munro wrote: > Hi, > > ECPG's auto_mem_destructor() doesn't seem quite right: > > 1. POSIX thread-specific keys are reset to NULL before their at-exit > destructors are called, so its call to ECPGfree_auto_mem() gets NULL > from get_auto_allocs(), so nothing much happens. It should use the > value passed to the destructor. > > 2. ECPGfree_auto_mem() doesn't seem to be the right thing to do > anyway, because the user is expected to free heap objects allocated by > the library, for example where > src/interfaces/ecpg/test/thread/alloc.pgc does this: > > char **r = NULL; > ... > for (i = 1; i <= REPEATS; ++i) > { > EXEC SQL SELECT relname INTO :r FROM pg_class WHERE relname = > 'pg_class'; > free(r); > r = NULL; > } > > With a fix for only problem #1 in place, the thread-exit destructor > double-frees "r" from the final loop. I *think* what is wanted here > is ecpg_clear_auto_mem(), to free just the list structure and not the > values themselves. Draft patch like that attached. > > It still leaks on Windows, but that's a known issue and I have a fix > for that as part of a larger refactoring of thread-related stuff, more > on that shortly. This looked like a bug to report separately first. Good catch! I wonder if we should refrain from doing the "set_auto_allocs(NULL);" call from the destructor. I guess it's harmless, but it feels a bit wrong to me. Like in the attached. > Hmm, I wonder why ecpg_raise() frees auto-allocated values for all > connections just because one connection raised an error. I didn't look at that - Heikki