public inbox for [email protected]help / color / mirror / Atom feed
[PATCH v24 1/4] vacuum errcontext to show block being processed 41+ messages / 10 participants [nested] [flat]
* [PATCH v24 1/4] vacuum errcontext to show block being processed @ 2019-12-13 02:54 Justin Pryzby <[email protected]> 0 siblings, 0 replies; 41+ messages in thread From: Justin Pryzby @ 2019-12-13 02:54 UTC (permalink / raw) Discussion: https://www.postgresql.org/message-id/[email protected] --- src/backend/access/heap/vacuumlazy.c | 192 +++++++++++++++++++++++---- 1 file changed, 167 insertions(+), 25 deletions(-) diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index 03c43efc32..084b43c178 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -268,8 +268,19 @@ typedef struct LVParallelState int nindexes_parallel_condcleanup; } LVParallelState; +typedef enum { + VACUUM_ERRCB_PHASE_UNKNOWN, + VACUUM_ERRCB_PHASE_SCAN_HEAP, + VACUUM_ERRCB_PHASE_VACUUM_INDEX, + VACUUM_ERRCB_PHASE_VACUUM_HEAP, + VACUUM_ERRCB_PHASE_INDEX_CLEANUP, + VACUUM_ERRCB_PHASE_VACUUM_FSM, +} errcb_phase; + typedef struct LVRelStats { + char *relnamespace; + char *relname; /* useindex = true means two-pass strategy; false means one-pass */ bool useindex; /* Overall statistics about rel */ @@ -290,8 +301,12 @@ typedef struct LVRelStats int num_index_scans; TransactionId latestRemovedXid; bool lock_waiter_detected; -} LVRelStats; + /* Used for error callback: */ + char *indname; + BlockNumber blkno; /* used only for heap operations */ + errcb_phase phase; +} LVRelStats; /* A few variables that don't seem worth passing around as parameters */ static int elevel = -1; @@ -314,10 +329,10 @@ static void lazy_vacuum_all_indexes(Relation onerel, Relation *Irel, LVRelStats *vacrelstats, LVParallelState *lps, int nindexes); static void lazy_vacuum_index(Relation indrel, IndexBulkDeleteResult **stats, - LVDeadTuples *dead_tuples, double reltuples); + LVDeadTuples *dead_tuples, double reltuples, LVRelStats *vacrelstats); static void lazy_cleanup_index(Relation indrel, IndexBulkDeleteResult **stats, - double reltuples, bool estimated_count); + double reltuples, bool estimated_count, LVRelStats *vacrelstats); static int lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer, int tupindex, LVRelStats *vacrelstats, Buffer *vmbuffer); static bool should_attempt_truncation(VacuumParams *params, @@ -337,13 +352,13 @@ static void lazy_parallel_vacuum_indexes(Relation *Irel, IndexBulkDeleteResult * int nindexes); static void parallel_vacuum_index(Relation *Irel, IndexBulkDeleteResult **stats, LVShared *lvshared, LVDeadTuples *dead_tuples, - int nindexes); + int nindexes, LVRelStats *vacrelstats); static void vacuum_indexes_leader(Relation *Irel, IndexBulkDeleteResult **stats, LVRelStats *vacrelstats, LVParallelState *lps, int nindexes); static void vacuum_one_index(Relation indrel, IndexBulkDeleteResult **stats, LVShared *lvshared, LVSharedIndStats *shared_indstats, - LVDeadTuples *dead_tuples); + LVDeadTuples *dead_tuples, LVRelStats *vacrelstats); static void lazy_cleanup_all_indexes(Relation *Irel, IndexBulkDeleteResult **stats, LVRelStats *vacrelstats, LVParallelState *lps, int nindexes); @@ -361,6 +376,9 @@ static void end_parallel_vacuum(Relation *Irel, IndexBulkDeleteResult **stats, LVParallelState *lps, int nindexes); static LVSharedIndStats *get_indstats(LVShared *lvshared, int n); static bool skip_parallel_vacuum_index(Relation indrel, LVShared *lvshared); +static void vacuum_error_callback(void *arg); +static void update_vacuum_error_cbarg(LVRelStats *errcbarg, int phase, + BlockNumber blkno, Relation rel); /* @@ -460,6 +478,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params, vacrelstats = (LVRelStats *) palloc0(sizeof(LVRelStats)); + vacrelstats->relnamespace = get_namespace_name(RelationGetNamespace(onerel)); + vacrelstats->relname = pstrdup(RelationGetRelationName(onerel)); + vacrelstats->indname = NULL; vacrelstats->old_rel_pages = onerel->rd_rel->relpages; vacrelstats->old_live_tuples = onerel->rd_rel->reltuples; vacrelstats->num_index_scans = 0; @@ -699,7 +720,6 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, BlockNumber nblocks, blkno; HeapTupleData tuple; - char *relname; TransactionId relfrozenxid = onerel->rd_rel->relfrozenxid; TransactionId relminmxid = onerel->rd_rel->relminmxid; BlockNumber empty_pages, @@ -724,20 +744,20 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, PROGRESS_VACUUM_MAX_DEAD_TUPLES }; int64 initprog_val[3]; + ErrorContextCallback errcallback; pg_rusage_init(&ru0); - relname = RelationGetRelationName(onerel); if (aggressive) ereport(elevel, (errmsg("aggressively vacuuming \"%s.%s\"", - get_namespace_name(RelationGetNamespace(onerel)), - relname))); + vacrelstats->relnamespace, + vacrelstats->relname))); else ereport(elevel, (errmsg("vacuuming \"%s.%s\"", - get_namespace_name(RelationGetNamespace(onerel)), - relname))); + vacrelstats->relnamespace, + vacrelstats->relname))); empty_pages = vacuumed_pages = 0; next_fsm_block_to_vacuum = (BlockNumber) 0; @@ -870,6 +890,14 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, else skipping_blocks = false; + /* Setup error traceback support for ereport() */ + update_vacuum_error_cbarg(vacrelstats, VACUUM_ERRCB_PHASE_SCAN_HEAP, + InvalidBlockNumber, NULL); + errcallback.callback = vacuum_error_callback; + errcallback.arg = vacrelstats; + errcallback.previous = error_context_stack; + error_context_stack = &errcallback; + for (blkno = 0; blkno < nblocks; blkno++) { Buffer buf; @@ -891,6 +919,8 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, #define FORCE_CHECK_PAGE() \ (blkno == nblocks - 1 && should_attempt_truncation(params, vacrelstats)) + vacrelstats->blkno = blkno; + pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_SCANNED, blkno); if (blkno == next_unskippable_block) @@ -1005,12 +1035,18 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, * Vacuum the Free Space Map to make newly-freed space visible on * upper-level FSM pages. Note we have not yet processed blkno. */ + update_vacuum_error_cbarg(vacrelstats, + VACUUM_ERRCB_PHASE_VACUUM_FSM, InvalidBlockNumber, NULL); FreeSpaceMapVacuumRange(onerel, next_fsm_block_to_vacuum, blkno); next_fsm_block_to_vacuum = blkno; /* Report that we are once again scanning the heap */ pgstat_progress_update_param(PROGRESS_VACUUM_PHASE, PROGRESS_VACUUM_PHASE_SCAN_HEAP); + + /* Set the error context while continuing heap scan */ + update_vacuum_error_cbarg(vacrelstats, + VACUUM_ERRCB_PHASE_SCAN_HEAP, blkno, NULL); } /* @@ -1488,9 +1524,15 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, */ if (blkno - next_fsm_block_to_vacuum >= VACUUM_FSM_EVERY_PAGES) { + update_vacuum_error_cbarg(vacrelstats, + VACUUM_ERRCB_PHASE_VACUUM_FSM, InvalidBlockNumber, + NULL); FreeSpaceMapVacuumRange(onerel, next_fsm_block_to_vacuum, blkno); next_fsm_block_to_vacuum = blkno; + /* Set the error context while continuing heap scan */ + update_vacuum_error_cbarg(vacrelstats, + VACUUM_ERRCB_PHASE_SCAN_HEAP, blkno, NULL); } } @@ -1534,7 +1576,7 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, && VM_ALL_VISIBLE(onerel, blkno, &vmbuffer)) { elog(WARNING, "page is not marked all-visible but visibility map bit is set in relation \"%s\" page %u", - relname, blkno); + vacrelstats->relname, blkno); visibilitymap_clear(onerel, blkno, vmbuffer, VISIBILITYMAP_VALID_BITS); } @@ -1555,7 +1597,7 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, else if (PageIsAllVisible(page) && has_dead_tuples) { elog(WARNING, "page containing dead tuples is marked as all-visible in relation \"%s\" page %u", - relname, blkno); + vacrelstats->relname, blkno); PageClearAllVisible(page); MarkBufferDirty(buf); visibilitymap_clear(onerel, blkno, vmbuffer, @@ -1642,7 +1684,11 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, * not there were indexes. */ if (blkno > next_fsm_block_to_vacuum) + { + update_vacuum_error_cbarg(vacrelstats, + VACUUM_ERRCB_PHASE_VACUUM_FSM, InvalidBlockNumber, NULL); FreeSpaceMapVacuumRange(onerel, next_fsm_block_to_vacuum, blkno); + } /* report all blocks vacuumed */ pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_VACUUMED, blkno); @@ -1651,6 +1697,9 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, if (vacrelstats->useindex) lazy_cleanup_all_indexes(Irel, indstats, vacrelstats, lps, nindexes); + /* Pop the error context stack */ + error_context_stack = errcallback.previous; + /* * End parallel mode before updating index statistics as we cannot write * during parallel mode. @@ -1744,7 +1793,7 @@ lazy_vacuum_all_indexes(Relation onerel, Relation *Irel, for (idx = 0; idx < nindexes; idx++) lazy_vacuum_index(Irel[idx], &stats[idx], vacrelstats->dead_tuples, - vacrelstats->old_live_tuples); + vacrelstats->old_live_tuples, vacrelstats); } /* Increase and report the number of index scans */ @@ -1847,6 +1896,10 @@ lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer, pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_VACUUMED, blkno); + /* Setup error traceback support for ereport() */ + update_vacuum_error_cbarg(vacrelstats, VACUUM_ERRCB_PHASE_VACUUM_HEAP, + blkno, NULL); + START_CRIT_SECTION(); for (; tupindex < dead_tuples->num_tuples; tupindex++) @@ -2083,7 +2136,7 @@ lazy_parallel_vacuum_indexes(Relation *Irel, IndexBulkDeleteResult **stats, * indexes in the case where no workers are launched. */ parallel_vacuum_index(Irel, stats, lps->lvshared, - vacrelstats->dead_tuples, nindexes); + vacrelstats->dead_tuples, nindexes, vacrelstats); /* Wait for all vacuum workers to finish */ WaitForParallelWorkersToFinish(lps->pcxt); @@ -2106,7 +2159,7 @@ lazy_parallel_vacuum_indexes(Relation *Irel, IndexBulkDeleteResult **stats, static void parallel_vacuum_index(Relation *Irel, IndexBulkDeleteResult **stats, LVShared *lvshared, LVDeadTuples *dead_tuples, - int nindexes) + int nindexes, LVRelStats *vacrelstats) { /* * Increment the active worker count if we are able to launch any worker. @@ -2140,7 +2193,7 @@ parallel_vacuum_index(Relation *Irel, IndexBulkDeleteResult **stats, /* Do vacuum or cleanup of the index */ vacuum_one_index(Irel[idx], &(stats[idx]), lvshared, shared_indstats, - dead_tuples); + dead_tuples, vacrelstats); } /* @@ -2180,7 +2233,8 @@ vacuum_indexes_leader(Relation *Irel, IndexBulkDeleteResult **stats, if (shared_indstats == NULL || skip_parallel_vacuum_index(Irel[i], lps->lvshared)) vacuum_one_index(Irel[i], &(stats[i]), lps->lvshared, - shared_indstats, vacrelstats->dead_tuples); + shared_indstats, vacrelstats->dead_tuples, + vacrelstats); } /* @@ -2200,7 +2254,7 @@ vacuum_indexes_leader(Relation *Irel, IndexBulkDeleteResult **stats, static void vacuum_one_index(Relation indrel, IndexBulkDeleteResult **stats, LVShared *lvshared, LVSharedIndStats *shared_indstats, - LVDeadTuples *dead_tuples) + LVDeadTuples *dead_tuples, LVRelStats *vacrelstats) { IndexBulkDeleteResult *bulkdelete_res = NULL; @@ -2220,10 +2274,10 @@ vacuum_one_index(Relation indrel, IndexBulkDeleteResult **stats, /* Do vacuum or cleanup of the index */ if (lvshared->for_cleanup) lazy_cleanup_index(indrel, stats, lvshared->reltuples, - lvshared->estimated_count); + lvshared->estimated_count, vacrelstats); else lazy_vacuum_index(indrel, stats, dead_tuples, - lvshared->reltuples); + lvshared->reltuples, vacrelstats); /* * Copy the index bulk-deletion result returned from ambulkdelete and @@ -2298,7 +2352,8 @@ lazy_cleanup_all_indexes(Relation *Irel, IndexBulkDeleteResult **stats, for (idx = 0; idx < nindexes; idx++) lazy_cleanup_index(Irel[idx], &stats[idx], vacrelstats->new_rel_tuples, - vacrelstats->tupcount_pages < vacrelstats->rel_pages); + vacrelstats->tupcount_pages < vacrelstats->rel_pages, + vacrelstats); } } @@ -2313,7 +2368,7 @@ lazy_cleanup_all_indexes(Relation *Irel, IndexBulkDeleteResult **stats, */ static void lazy_vacuum_index(Relation indrel, IndexBulkDeleteResult **stats, - LVDeadTuples *dead_tuples, double reltuples) + LVDeadTuples *dead_tuples, double reltuples, LVRelStats *vacrelstats) { IndexVacuumInfo ivinfo; const char *msg; @@ -2329,6 +2384,11 @@ lazy_vacuum_index(Relation indrel, IndexBulkDeleteResult **stats, ivinfo.num_heap_tuples = reltuples; ivinfo.strategy = vac_strategy; + /* Setup error traceback support for ereport() */ + update_vacuum_error_cbarg(vacrelstats, + VACUUM_ERRCB_PHASE_VACUUM_INDEX, InvalidBlockNumber, + indrel); + /* Do bulk deletion */ *stats = index_bulk_delete(&ivinfo, *stats, lazy_tid_reaped, (void *) dead_tuples); @@ -2354,7 +2414,7 @@ lazy_vacuum_index(Relation indrel, IndexBulkDeleteResult **stats, static void lazy_cleanup_index(Relation indrel, IndexBulkDeleteResult **stats, - double reltuples, bool estimated_count) + double reltuples, bool estimated_count, LVRelStats *vacrelstats) { IndexVacuumInfo ivinfo; const char *msg; @@ -2371,6 +2431,10 @@ lazy_cleanup_index(Relation indrel, ivinfo.num_heap_tuples = reltuples; ivinfo.strategy = vac_strategy; + /* Setup error traceback support for ereport() */ + update_vacuum_error_cbarg(vacrelstats, + VACUUM_ERRCB_PHASE_INDEX_CLEANUP, InvalidBlockNumber, indrel); + *stats = index_vacuum_cleanup(&ivinfo, *stats); if (!(*stats)) @@ -3320,6 +3384,8 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc) int nindexes; char *sharedquery; IndexBulkDeleteResult **stats; + LVRelStats vacrelstats; + ErrorContextCallback errcallback; lvshared = (LVShared *) shm_toc_lookup(toc, PARALLEL_VACUUM_KEY_SHARED, false); @@ -3341,6 +3407,18 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc) */ onerel = table_open(lvshared->relid, ShareUpdateExclusiveLock); + /* Init vacrelstats for use as error callback by parallel worker: */ + vacrelstats.relnamespace = get_namespace_name(RelationGetNamespace(onerel)); + vacrelstats.relname = pstrdup(RelationGetRelationName(onerel)); + vacrelstats.indname = NULL; + vacrelstats.phase = VACUUM_ERRCB_PHASE_UNKNOWN; /* Not yet processing */ + + /* Setup error traceback support for ereport() */ + errcallback.callback = vacuum_error_callback; + errcallback.arg = &vacrelstats; + errcallback.previous = error_context_stack; + error_context_stack = &errcallback; + /* * Open all indexes. indrels are sorted in order by OID, which should be * matched to the leader's one. @@ -3370,9 +3448,73 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc) maintenance_work_mem = lvshared->maintenance_work_mem_worker; /* Process indexes to perform vacuum/cleanup */ - parallel_vacuum_index(indrels, stats, lvshared, dead_tuples, nindexes); + parallel_vacuum_index(indrels, stats, lvshared, dead_tuples, nindexes, + &vacrelstats); vac_close_indexes(nindexes, indrels, RowExclusiveLock); table_close(onerel, ShareUpdateExclusiveLock); pfree(stats); } + +/* + * Error context callback for errors occurring during vacuum. + */ +static void +vacuum_error_callback(void *arg) +{ + LVRelStats *cbarg = arg; + + switch (cbarg->phase) { + case VACUUM_ERRCB_PHASE_SCAN_HEAP: + if (BlockNumberIsValid(cbarg->blkno)) + errcontext("while scanning block %u of relation \"%s.%s\"", + cbarg->blkno, cbarg->relnamespace, cbarg->relname); + break; + + case VACUUM_ERRCB_PHASE_VACUUM_HEAP: + if (BlockNumberIsValid(cbarg->blkno)) + errcontext("while vacuuming block %u of relation \"%s.%s\"", + cbarg->blkno, cbarg->relnamespace, cbarg->relname); + break; + + case VACUUM_ERRCB_PHASE_VACUUM_INDEX: + errcontext("while vacuuming index \"%s\" of relation \"%s.%s\"", + cbarg->indname, cbarg->relnamespace, cbarg->relname); + break; + + case VACUUM_ERRCB_PHASE_INDEX_CLEANUP: + errcontext("while cleaning up index \"%s\" of relation \"%s.%s\"", + cbarg->indname, cbarg->relnamespace, cbarg->relname); + break; + + case VACUUM_ERRCB_PHASE_VACUUM_FSM: + errcontext("while vacuuming free space map of relation \"%s.%s\"", + cbarg->relnamespace, cbarg->relname); + break; + + case VACUUM_ERRCB_PHASE_UNKNOWN: + default: + return; /* do nothing; the cbarg maybe isn't yet initialized */ + } +} + +/* Update vacuum error callback for current phase, block and index */ +static void +update_vacuum_error_cbarg(LVRelStats *errcbarg, int phase, BlockNumber blkno, + Relation indrel) +{ + errcbarg->blkno = blkno; + errcbarg->phase = phase; + + /* Free index name from any previous phase */ + if (errcbarg->indname) { + pfree(errcbarg->indname); + errcbarg->indname = NULL; + } + + /* For index phases, save the name of the current index for the callback */ + if (indrel) { + Assert(indrel->rd_rel->relkind == RELKIND_INDEX); + errcbarg->indname = pstrdup(RelationGetRelationName(indrel)); + } +} -- 2.17.0 --a8Wt8u1KmwUX3Y2C Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v24-0002-Drop-reltuples.patch" ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2023-11-15 17:45 Tom Lane <[email protected]> 0 siblings, 2 replies; 41+ messages in thread From: Tom Lane @ 2023-11-15 17:45 UTC (permalink / raw) To: Tristan Partin <[email protected]>; +Cc: pgsql-hackers "Tristan Partin" <[email protected]> writes: > I would like to propose removing HAVE_USELOCALE, and just have WIN32, > which means that Postgres would require uselocale(3) on anything that > isn't WIN32. You would need to do some research and try to prove that that won't be a problem on any modern platform. Presumably it once was a problem, or we'd not have bothered with a configure check. (Some git archaeology might yield useful info about when and why we added the check.) regards, tom lane ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2023-11-15 18:38 Thomas Munro <[email protected]> parent: Tom Lane <[email protected]> 1 sibling, 1 reply; 41+ messages in thread From: Thomas Munro @ 2023-11-15 18:38 UTC (permalink / raw) To: Tom Lane <[email protected]>; +Cc: Tristan Partin <[email protected]>; pgsql-hackers On Thu, Nov 16, 2023 at 6:45 AM Tom Lane <[email protected]> wrote: > "Tristan Partin" <[email protected]> writes: > > I would like to propose removing HAVE_USELOCALE, and just have WIN32, > > which means that Postgres would require uselocale(3) on anything that > > isn't WIN32. > > You would need to do some research and try to prove that that won't > be a problem on any modern platform. Presumably it once was a problem, > or we'd not have bothered with a configure check. > > (Some git archaeology might yield useful info about when and why > we added the check.) According to data I scraped from the build farm, the last two systems we had that didn't have uselocale() were curculio (OpenBSD 5.9) and wrasse (Solaris 11.3), but those were both shut down (though wrasse still runs old branches) as they were well out of support. OpenBSD gained uselocale() in 6.2, and Solaris in 11.4, as part of the same suite of POSIX changes that we already required in commit 8d9a9f03. +1 for the change. https://man.openbsd.org/uselocale.3 https://docs.oracle.com/cd/E88353_01/html/E37843/uselocale-3c.html ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2023-11-15 18:42 Dagfinn Ilmari Mannsåker <[email protected]> parent: Tom Lane <[email protected]> 1 sibling, 2 replies; 41+ messages in thread From: Dagfinn Ilmari Mannsåker @ 2023-11-15 18:42 UTC (permalink / raw) To: Tom Lane <[email protected]>; +Cc: Tristan Partin <[email protected]>; pgsql-hackers Tom Lane <[email protected]> writes: > "Tristan Partin" <[email protected]> writes: >> I would like to propose removing HAVE_USELOCALE, and just have WIN32, >> which means that Postgres would require uselocale(3) on anything that >> isn't WIN32. > > You would need to do some research and try to prove that that won't > be a problem on any modern platform. Presumably it once was a problem, > or we'd not have bothered with a configure check. > > (Some git archaeology might yield useful info about when and why > we added the check.) For reference, the Perl effort to use the POSIX.1-2008 thread-safe locale APIs have revealed several platform-specific bugs that cause it to disable them on FreeBSD and macOS: https://github.com/perl/perl5/commit/9cbc12c368981c56d4d8e40cc9417ac26bec2c35 https://github.com/perl/perl5/commit/dd4eb78c55aab441aec1639b1dd49f88bd960831 and work around bugs on others (e.g. OpenBSD): https://github.com/perl/perl5/commit/0f3830f3997cf7ef1531bad26d2e0f13220dd862 But Perl actually makes use of per-thread locales, because it has a separate interpereer per thread, each of which can have a different locale active. Since Postgres isn't actually multi-threaded (yet), these concerns might not apply to the same degree. > regards, tom lane - ilmari ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2023-11-15 19:04 Tom Lane <[email protected]> parent: Dagfinn Ilmari Mannsåker <[email protected]> 1 sibling, 0 replies; 41+ messages in thread From: Tom Lane @ 2023-11-15 19:04 UTC (permalink / raw) To: Dagfinn Ilmari Mannsåker <[email protected]>; +Cc: Tristan Partin <[email protected]>; pgsql-hackers =?utf-8?Q?Dagfinn_Ilmari_Manns=C3=A5ker?= <[email protected]> writes: > Tom Lane <[email protected]> writes: >> "Tristan Partin" <[email protected]> writes: >>> I would like to propose removing HAVE_USELOCALE, and just have WIN32, >>> which means that Postgres would require uselocale(3) on anything that >>> isn't WIN32. >> You would need to do some research and try to prove that that won't >> be a problem on any modern platform. Presumably it once was a problem, >> or we'd not have bothered with a configure check. > For reference, the Perl effort to use the POSIX.1-2008 thread-safe > locale APIs have revealed several platform-specific bugs that cause it > to disable them on FreeBSD and macOS: > https://github.com/perl/perl5/commit/9cbc12c368981c56d4d8e40cc9417ac26bec2c35 > https://github.com/perl/perl5/commit/dd4eb78c55aab441aec1639b1dd49f88bd960831 > and work around bugs on others (e.g. OpenBSD): > https://github.com/perl/perl5/commit/0f3830f3997cf7ef1531bad26d2e0f13220dd862 > But Perl actually makes use of per-thread locales, because it has a > separate interpereer per thread, each of which can have a different > locale active. Since Postgres isn't actually multi-threaded (yet), > these concerns might not apply to the same degree. Interesting. That need not stop us from dropping the configure check for uselocale(), but it might be a problem for Tristan's larger ambitions. regards, tom lane ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2023-11-15 19:16 Thomas Munro <[email protected]> parent: Dagfinn Ilmari Mannsåker <[email protected]> 1 sibling, 0 replies; 41+ messages in thread From: Thomas Munro @ 2023-11-15 19:16 UTC (permalink / raw) To: Dagfinn Ilmari Mannsåker <[email protected]>; +Cc: Tom Lane <[email protected]>; Tristan Partin <[email protected]>; pgsql-hackers On Thu, Nov 16, 2023 at 7:42 AM Dagfinn Ilmari Mannsåker <[email protected]> wrote: > Tom Lane <[email protected]> writes: > > > "Tristan Partin" <[email protected]> writes: > >> I would like to propose removing HAVE_USELOCALE, and just have WIN32, > >> which means that Postgres would require uselocale(3) on anything that > >> isn't WIN32. > > > > You would need to do some research and try to prove that that won't > > be a problem on any modern platform. Presumably it once was a problem, > > or we'd not have bothered with a configure check. > > > > (Some git archaeology might yield useful info about when and why > > we added the check.) > > For reference, the Perl effort to use the POSIX.1-2008 thread-safe > locale APIs have revealed several platform-specific bugs that cause it > to disable them on FreeBSD and macOS: > > https://github.com/perl/perl5/commit/9cbc12c368981c56d4d8e40cc9417ac26bec2c35 Interesting that C vs C.UTF-8 has come up there, something that has also confused us and others (in fact I still owe Daniel Vérité a response to his complaint about how we treat the latter; I got stuck on a logical problem with the proposal and then dumped core...). The idea of C.UTF-8 is relatively new, and seems to have shaken a few bugs out in a few places. Anyway, that in particular is a brand new FreeBSD bug report and I am sure it will be addressed soon. > https://github.com/perl/perl5/commit/dd4eb78c55aab441aec1639b1dd49f88bd960831 As for macOS, one thing I noticed is that the FreeBSD -> macOS pipeline appears to have re-awoken after many years of slumber. I don't know anything about that other than that when I recently upgraded my Mac to 14.1, suddenly a few userspace tools are now running the recentish FreeBSD versions of certain userland tools (tar, grep, ...?), instead of something from the Jurassic. Whether that might apply to libc, who can say... they seemed to have quite ancient BSD locale code last time I checked. > https://github.com/perl/perl5/commit/0f3830f3997cf7ef1531bad26d2e0f13220dd862 That linked issue appears to be fixed already. > But Perl actually makes use of per-thread locales, because it has a > separate interpereer per thread, each of which can have a different > locale active. Since Postgres isn't actually multi-threaded (yet), > these concerns might not apply to the same degree. ECPG might use them in multi-threaded code. I'm not sure if it's a problem and whose problem it is. ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2023-11-15 20:51 Tom Lane <[email protected]> parent: Thomas Munro <[email protected]> 0 siblings, 1 reply; 41+ messages in thread From: Tom Lane @ 2023-11-15 20:51 UTC (permalink / raw) To: Thomas Munro <[email protected]>; +Cc: Tristan Partin <[email protected]>; pgsql-hackers Thomas Munro <[email protected]> writes: > On Thu, Nov 16, 2023 at 6:45 AM Tom Lane <[email protected]> wrote: >> You would need to do some research and try to prove that that won't >> be a problem on any modern platform. Presumably it once was a problem, >> or we'd not have bothered with a configure check. > According to data I scraped from the build farm, the last two systems > we had that didn't have uselocale() were curculio (OpenBSD 5.9) and > wrasse (Solaris 11.3), but those were both shut down (though wrasse > still runs old branches) as they were well out of support. AFAICS, NetBSD still doesn't have it. They have no on-line man page for it, and my animal mamba shows it as not found. regards, tom lane ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2023-11-15 21:08 Thomas Munro <[email protected]> parent: Tom Lane <[email protected]> 0 siblings, 1 reply; 41+ messages in thread From: Thomas Munro @ 2023-11-15 21:08 UTC (permalink / raw) To: Tom Lane <[email protected]>; +Cc: Tristan Partin <[email protected]>; pgsql-hackers On Thu, Nov 16, 2023 at 9:51 AM Tom Lane <[email protected]> wrote: > Thomas Munro <[email protected]> writes: > > On Thu, Nov 16, 2023 at 6:45 AM Tom Lane <[email protected]> wrote: > >> You would need to do some research and try to prove that that won't > >> be a problem on any modern platform. Presumably it once was a problem, > >> or we'd not have bothered with a configure check. > > > According to data I scraped from the build farm, the last two systems > > we had that didn't have uselocale() were curculio (OpenBSD 5.9) and > > wrasse (Solaris 11.3), but those were both shut down (though wrasse > > still runs old branches) as they were well out of support. > > AFAICS, NetBSD still doesn't have it. They have no on-line man page > for it, and my animal mamba shows it as not found. Oh :-( I see that but had missed that sidewinder was NetBSD and my scraped data predates mamba. Sorry for the wrong info. Currently pg_locale.c requires systems to have *either* uselocale() or mbstowcs_l()/wcstombs_l(), but NetBSD satisfies the second requirement. The other uses of uselocale() are in ECPG code that must be falling back to the setlocale() path. In other words, isn't it the case that we don't require uselocale() to compile ECPG stuff, but it'll probably crash or corrupt itself or give wrong answers if you push it on NetBSD, so... uhh, really we do require it? ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2023-11-15 21:17 Tom Lane <[email protected]> parent: Thomas Munro <[email protected]> 0 siblings, 1 reply; 41+ messages in thread From: Tom Lane @ 2023-11-15 21:17 UTC (permalink / raw) To: Thomas Munro <[email protected]>; +Cc: Tristan Partin <[email protected]>; pgsql-hackers Thomas Munro <[email protected]> writes: > Currently pg_locale.c requires systems to have *either* uselocale() or > mbstowcs_l()/wcstombs_l(), but NetBSD satisfies the second > requirement. Check. > The other uses of uselocale() are in ECPG code that must > be falling back to the setlocale() path. In other words, isn't it the > case that we don't require uselocale() to compile ECPG stuff, but it'll > probably crash or corrupt itself or give wrong answers if you push it > on NetBSD, so... uhh, really we do require it? Dunno. mamba is getting through the ecpg regression tests okay, but we all know that doesn't prove a lot. (AFAICS, ecpg only cares about this to the extent of not wanting an LC_NUMERIC locale where the decimal point isn't '.'. I'm not sure that NetBSD supports any such locale anyway --- I think they're like OpenBSD in having only pro-forma locale support.) regards, tom lane ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2023-11-15 22:40 Thomas Munro <[email protected]> parent: Tom Lane <[email protected]> 0 siblings, 1 reply; 41+ messages in thread From: Thomas Munro @ 2023-11-15 22:40 UTC (permalink / raw) To: Tom Lane <[email protected]>; +Cc: Tristan Partin <[email protected]>; pgsql-hackers On Thu, Nov 16, 2023 at 10:17 AM Tom Lane <[email protected]> wrote: > Thomas Munro <[email protected]> writes: > > The other uses of uselocale() are in ECPG code that must > > be falling back to the setlocale() path. In other words, isn't it the > > case that we don't require uselocale() to compile ECPG stuff, but it'll > > probably crash or corrupt itself or give wrong answers if you push it > > on NetBSD, so... uhh, really we do require it? > > Dunno. mamba is getting through the ecpg regression tests okay, > but we all know that doesn't prove a lot. (AFAICS, ecpg only > cares about this to the extent of not wanting an LC_NUMERIC > locale where the decimal point isn't '.'. I'm not sure that > NetBSD supports any such locale anyway --- I think they're like > OpenBSD in having only pro-forma locale support.) Idea #1 For output, which happens with sprintf(ptr, "%.15g%s", ...) in execute.c, perhaps we could use our in-tree Ryu routine instead? For input, which happens with strtod() in data.c, rats, we don't have a parser and I understand that it is not for the faint of heart (naive implementation gets subtle things wrong, cf "How to read floating point numbers accurately" by W D Clinger + whatever improvements have happened in this space since 1990). Idea #2 Perhaps we could use snprintf_l() and strtod_l() where available. They're not standard, but they are obvious extensions that NetBSD and Windows have, and those are the two systems for which we are doing grotty things in that code. That would amount to extending pg_locale.c's philosophy: either you must have uselocale(), or the full set of _l() functions (that POSIX failed to standardise, dunno what the history is behind that, seems weird). ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2023-11-15 23:06 Tom Lane <[email protected]> parent: Thomas Munro <[email protected]> 0 siblings, 1 reply; 41+ messages in thread From: Tom Lane @ 2023-11-15 23:06 UTC (permalink / raw) To: Thomas Munro <[email protected]>; +Cc: Tristan Partin <[email protected]>; pgsql-hackers Thomas Munro <[email protected]> writes: > Idea #1 > For output, which happens with sprintf(ptr, "%.15g%s", ...) in > execute.c, perhaps we could use our in-tree Ryu routine instead? > For input, which happens with strtod() in data.c, rats, we don't have > a parser and I understand that it is not for the faint of heart Yeah. Getting rid of ecpg's use of uselocale() would certainly be nice, but I'm not ready to add our own implementation of strtod() to get there. > Idea #2 > Perhaps we could use snprintf_l() and strtod_l() where available. > They're not standard, but they are obvious extensions that NetBSD and > Windows have, and those are the two systems for which we are doing > grotty things in that code. Oooh, shiny. I do not see any man page for strtod_l, but I do see that it's declared on mamba's host. I wonder how long they've had it? The man page for snprintf_l appears to be quite ancient, so we could hope that strtod_l is available on all versions anyone cares about. > That would amount to extending > pg_locale.c's philosophy: either you must have uselocale(), or the > full set of _l() functions (that POSIX failed to standardise, dunno > what the history is behind that, seems weird). Yeah. I'd say the _l functions should be preferred over uselocale() if available, but sadly they're not there on common systems. (It looks like glibc has strtod_l but not snprintf_l, which is odd.) regards, tom lane ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2023-11-16 19:57 Thomas Munro <[email protected]> parent: Tom Lane <[email protected]> 0 siblings, 2 replies; 41+ messages in thread From: Thomas Munro @ 2023-11-16 19:57 UTC (permalink / raw) To: Tom Lane <[email protected]>; +Cc: Tristan Partin <[email protected]>; pgsql-hackers On Thu, Nov 16, 2023 at 12:06 PM Tom Lane <[email protected]> wrote: > Thomas Munro <[email protected]> writes: > > Perhaps we could use snprintf_l() and strtod_l() where available. > > They're not standard, but they are obvious extensions that NetBSD and > > Windows have, and those are the two systems for which we are doing > > grotty things in that code. > > Oooh, shiny. I do not see any man page for strtod_l, but I do see > that it's declared on mamba's host. I wonder how long they've had it? > The man page for snprintf_l appears to be quite ancient, so we could > hope that strtod_l is available on all versions anyone cares about. A decade[1]. And while I'm doing archeology, I noticed that POSIX has agreed[2] in principle that *all* functions affected by the thread's current locale should have a _l() variant, it's just that no one has sent in the patch. > > That would amount to extending > > pg_locale.c's philosophy: either you must have uselocale(), or the > > full set of _l() functions (that POSIX failed to standardise, dunno > > what the history is behind that, seems weird). > > Yeah. I'd say the _l functions should be preferred over uselocale() > if available, but sadly they're not there on common systems. (It > looks like glibc has strtod_l but not snprintf_l, which is odd.) Here is a first attempt. In this version, new functions are exported by pgtypeslib. I realised that I had to do it in there because ECPG's uselocale() jiggery-pokery is clearly intended to affect the conversions happening in there too, and we probably don't want circular dependencies between pgtypeslib and ecpglib. I think this means that pgtypeslib is actually subtly b0rked if you use it independently without an ECPG connection (is that a thing people do?), because all that code copied-and-pasted from the backend when run in frontend code with eg a French locale will produce eg "0,42"; this patch doesn't change that. I also had a go[3] at doing it with static inlined functions, to avoid creating a load of new exported functions and associated function call overheads. It worked fine, except on Windows: I needed a global variable PGTYPESclocale that all the inlined functions can see when called from ecpglib or pgtypeslib code, but if I put that in the exports list then on that platform it seems to contain garbage; there is probably some other magic needed to export non-function symbols from the DLL or something like that, I didn't look into it. See CI failure + crash dumps. [1] https://github.com/NetBSD/src/commit/c99aac45e540bc210cc660619a6b5323cbb5c17f [2] https://www.austingroupbugs.net/view.php?id=1004 [3] https://github.com/macdice/postgres/tree/strtod_l_inline Attachments: [application/octet-stream] 0001-ecpg-Use-thread-safe-_l-functions-if-possible.patch (24.0K, ../../CA+hUKGLLb1MiqAUwtSb6gQsqm8wgAh+7UEE-6P1tQxjhHCFmrw@mail.gmail.com/2-0001-ecpg-Use-thread-safe-_l-functions-if-possible.patch) download | inline diff: From 512110458d6916efb7515770d6c4f4b7477b1d22 Mon Sep 17 00:00:00 2001 From: Thomas Munro <[email protected]> Date: Thu, 16 Nov 2023 15:53:51 +1300 Subject: [PATCH] ecpg: Use thread-safe _l() functions if possible. In order to use snprintf("%g") and strtod() with the C locale in certain parts of the ECPG code, we call uselocale() around those sections. Two operating systems don't have uselocale(), but our fallbacks were non-ideal. For NetBSD we'd use setlocale(), which is not thread-safe and dangerous, and for Windows we'd either use setlocale() or a Windows API that is egregiously different and hard to maintain, depending on the compiler tool chain. We can remove all of that and use not-yet-standardized [v]s[n]printf_l() and strtod_l(), if available. Those two operating systems, along with the other BSDs and macOS have them. Glibc has strtod_l() but not yet the other. Discussion: https://postgr.es/m/CWZBBRR6YA8D.8EHMDRGLCKCD%40neon.tech --- configure | 13 +- configure.ac | 5 +- meson.build | 5 +- src/include/pg_config.h.in | 15 +- src/interfaces/ecpg/ecpglib/connect.c | 55 ++++---- src/interfaces/ecpg/ecpglib/data.c | 3 +- src/interfaces/ecpg/ecpglib/descriptor.c | 29 +--- src/interfaces/ecpg/ecpglib/ecpglib_extern.h | 11 -- src/interfaces/ecpg/ecpglib/execute.c | 54 +------- src/interfaces/ecpg/include/pgtypes.h | 6 + src/interfaces/ecpg/include/pgtypes_format.h | 26 ++++ src/interfaces/ecpg/pgtypeslib/common.c | 138 ++++++++++++++++++- src/interfaces/ecpg/pgtypeslib/exports.txt | 6 + src/interfaces/ecpg/pgtypeslib/interval.c | 5 +- src/interfaces/ecpg/pgtypeslib/numeric.c | 3 +- src/tools/msvc/Solution.pm | 5 +- 16 files changed, 233 insertions(+), 146 deletions(-) create mode 100644 src/interfaces/ecpg/include/pgtypes_format.h diff --git a/configure b/configure index c064115038..91c9865ae3 100755 --- a/configure +++ b/configure @@ -15539,7 +15539,7 @@ fi LIBS_including_readline="$LIBS" LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'` -for ac_func in backtrace_symbols copyfile getifaddrs getpeerucred inet_pton kqueue mbstowcs_l memset_s posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast strchrnul strsignal syncfs sync_file_range uselocale wcstombs_l +for ac_func in backtrace_symbols copyfile getifaddrs getpeerucred inet_pton kqueue mbstowcs_l memset_s posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast strchrnul strsignal strtod_l syncfs sync_file_range vsnprintf_l vsprintf_l wcstombs_l do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" @@ -16309,17 +16309,6 @@ fi # Win32 (really MinGW) support if test "$PORTNAME" = "win32"; then - for ac_func in _configthreadlocale -do : - ac_fn_c_check_func "$LINENO" "_configthreadlocale" "ac_cv_func__configthreadlocale" -if test "x$ac_cv_func__configthreadlocale" = xyes; then : - cat >>confdefs.h <<_ACEOF -#define HAVE__CONFIGTHREADLOCALE 1 -_ACEOF - -fi -done - case " $LIBOBJS " in *" dirmod.$ac_objext "* ) ;; *) LIBOBJS="$LIBOBJS dirmod.$ac_objext" diff --git a/configure.ac b/configure.ac index f220b379b3..67ecb83447 100644 --- a/configure.ac +++ b/configure.ac @@ -1782,9 +1782,11 @@ AC_CHECK_FUNCS(m4_normalize([ setproctitle_fast strchrnul strsignal + strtod_l syncfs sync_file_range - uselocale + vsnprintf_l + vsprintf_l wcstombs_l ])) @@ -1868,7 +1870,6 @@ fi # Win32 (really MinGW) support if test "$PORTNAME" = "win32"; then - AC_CHECK_FUNCS(_configthreadlocale) AC_LIBOBJ(dirmod) AC_LIBOBJ(kill) AC_LIBOBJ(open) diff --git a/meson.build b/meson.build index 286d7e4269..e24277b473 100644 --- a/meson.build +++ b/meson.build @@ -2417,7 +2417,6 @@ endif # XXX: Might be worth conditioning some checks on the OS, to avoid doing # unnecessary checks over and over, particularly on windows. func_checks = [ - ['_configthreadlocale', {'skip': host_system != 'windows'}], ['backtrace_symbols', {'dependencies': [execinfo_dep]}], ['clock_gettime', {'dependencies': [rt_dep], 'define': false}], ['copyfile'], @@ -2456,9 +2455,11 @@ func_checks = [ ['strlcpy'], ['strnlen'], ['strsignal'], + ['strtod_l'], ['sync_file_range'], ['syncfs'], - ['uselocale'], + ['vsnprintf_l'], + ['vsprintf_l'], ['wcstombs_l'], ] diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index d8a2985567..ea047e85cf 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -429,6 +429,9 @@ /* Define to 1 if you have the `strsignal' function. */ #undef HAVE_STRSIGNAL +/* Define to 1 if you have the `strtod_l' function. */ +#undef HAVE_STRTOD_L + /* Define to 1 if the system has the type `struct option'. */ #undef HAVE_STRUCT_OPTION @@ -495,9 +498,6 @@ /* Define to 1 if you have the <unistd.h> header file. */ #undef HAVE_UNISTD_H -/* Define to 1 if you have the `uselocale' function. */ -#undef HAVE_USELOCALE - /* Define to 1 if you have BSD UUID support. */ #undef HAVE_UUID_BSD @@ -516,6 +516,12 @@ /* Define to 1 if your compiler knows the visibility("hidden") attribute. */ #undef HAVE_VISIBILITY_ATTRIBUTE +/* Define to 1 if you have the `vsnprintf_l' function. */ +#undef HAVE_VSNPRINTF_L + +/* Define to 1 if you have the `vsprintf_l' function. */ +#undef HAVE_VSPRINTF_L + /* Define to 1 if you have the `wcstombs_l' function. */ #undef HAVE_WCSTOMBS_L @@ -561,9 +567,6 @@ /* Define to 1 if your compiler understands __builtin_unreachable. */ #undef HAVE__BUILTIN_UNREACHABLE -/* Define to 1 if you have the `_configthreadlocale' function. */ -#undef HAVE__CONFIGTHREADLOCALE - /* Define to 1 if you have __cpuid. */ #undef HAVE__CPUID diff --git a/src/interfaces/ecpg/ecpglib/connect.c b/src/interfaces/ecpg/ecpglib/connect.c index 8afb1f0a26..cf2083713c 100644 --- a/src/interfaces/ecpg/ecpglib/connect.c +++ b/src/interfaces/ecpg/ecpglib/connect.c @@ -8,12 +8,9 @@ #include "ecpglib.h" #include "ecpglib_extern.h" #include "ecpgtype.h" +#include "pgtypes.h" #include "sqlca.h" -#ifdef HAVE_USELOCALE -locale_t ecpg_clocale = (locale_t) 0; -#endif - static pthread_mutex_t connections_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_key_t actual_connection_key; static pthread_once_t actual_connection_key_once = PTHREAD_ONCE_INIT; @@ -484,37 +481,31 @@ ECPGconnect(int lineno, int c, const char *name, const char *user, const char *p pthread_mutex_lock(&connections_mutex); /* - * ... but first, make certain we have created ecpg_clocale. Rely on - * holding connections_mutex to ensure this is done by only one thread. + * ... but first, make certain pgtypes' locale is set up. Rely on holding + * connections_mutex to ensure this is done by only one thread. */ -#ifdef HAVE_USELOCALE - if (!ecpg_clocale) + if (PGTYPESinit() < 0) { - ecpg_clocale = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0); - if (!ecpg_clocale) - { - pthread_mutex_unlock(&connections_mutex); - 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; - } + pthread_mutex_unlock(&connections_mutex); + 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); diff --git a/src/interfaces/ecpg/ecpglib/data.c b/src/interfaces/ecpg/ecpglib/data.c index fa56276758..0f3ab36575 100644 --- a/src/interfaces/ecpg/ecpglib/data.c +++ b/src/interfaces/ecpg/ecpglib/data.c @@ -10,6 +10,7 @@ #include "ecpglib_extern.h" #include "ecpgtype.h" #include "pgtypes_date.h" +#include "pgtypes_format.h" #include "pgtypes_interval.h" #include "pgtypes_numeric.h" #include "pgtypes_timestamp.h" @@ -466,7 +467,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno, pval++; if (!check_special_value(pval, &dres, &scan_length)) - dres = strtod(pval, &scan_length); + dres = PGTYPESstrtod(pval, &scan_length); if (isarray && *scan_length == '"') scan_length++; diff --git a/src/interfaces/ecpg/ecpglib/descriptor.c b/src/interfaces/ecpg/ecpglib/descriptor.c index ad279e245c..e7d4cdf971 100644 --- a/src/interfaces/ecpg/ecpglib/descriptor.c +++ b/src/interfaces/ecpg/ecpglib/descriptor.c @@ -12,6 +12,7 @@ #include "ecpglib.h" #include "ecpglib_extern.h" #include "ecpgtype.h" +#include "pgtypes_format.h" #include "sql3types.h" #include "sqlca.h" #include "sqlda.h" @@ -478,43 +479,21 @@ ECPGget_desc(int lineno, const char *desc_name, int index,...) /* Make sure we do NOT honor the locale for numeric input */ /* since the database gives the standard decimal point */ /* (see comments in execute.c) */ -#ifdef HAVE_USELOCALE /* * 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. + * connection calls PGTYPESinit(). */ - Assert(ecpg_clocale); - stmt.oldlocale = uselocale(ecpg_clocale); -#else -#ifdef HAVE__CONFIGTHREADLOCALE - stmt.oldthreadlocale = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); -#endif - stmt.oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno); - setlocale(LC_NUMERIC, "C"); -#endif + PGTYPESbegin_clocale(&stmt.oldlocale); /* desperate try to guess something sensible */ stmt.connection = ecpg_get_connection(NULL); ecpg_store_result(ECPGresult, index, &stmt, &data_var); -#ifdef HAVE_USELOCALE - if (stmt.oldlocale != (locale_t) 0) - uselocale(stmt.oldlocale); -#else - if (stmt.oldlocale) - { - setlocale(LC_NUMERIC, stmt.oldlocale); - ecpg_free(stmt.oldlocale); - } -#ifdef HAVE__CONFIGTHREADLOCALE - if (stmt.oldthreadlocale != -1) - (void) _configthreadlocale(stmt.oldthreadlocale); -#endif -#endif + PGTYPESend_clocale(stmt.oldlocale); } else if (data_var.ind_type != ECPGt_NO_INDICATOR && data_var.ind_pointer != NULL) diff --git a/src/interfaces/ecpg/ecpglib/ecpglib_extern.h b/src/interfaces/ecpg/ecpglib/ecpglib_extern.h index 01b4309a71..ae4c49d87e 100644 --- a/src/interfaces/ecpg/ecpglib/ecpglib_extern.h +++ b/src/interfaces/ecpg/ecpglib/ecpglib_extern.h @@ -59,10 +59,6 @@ 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 { @@ -76,14 +72,7 @@ struct statement bool questionmarks; struct variable *inlist; struct variable *outlist; -#ifdef HAVE_USELOCALE locale_t oldlocale; -#else - char *oldlocale; -#ifdef HAVE__CONFIGTHREADLOCALE - int oldthreadlocale; -#endif -#endif int nparams; char **paramvalues; int *paramlengths; diff --git a/src/interfaces/ecpg/ecpglib/execute.c b/src/interfaces/ecpg/ecpglib/execute.c index 04d0b40c53..a9afc88a61 100644 --- a/src/interfaces/ecpg/ecpglib/execute.c +++ b/src/interfaces/ecpg/ecpglib/execute.c @@ -24,6 +24,7 @@ #include "ecpglib_extern.h" #include "ecpgtype.h" #include "pgtypes_date.h" +#include "pgtypes_format.h" #include "pgtypes_interval.h" #include "pgtypes_numeric.h" #include "pgtypes_timestamp.h" @@ -101,9 +102,6 @@ free_statement(struct statement *stmt) free_variable(stmt->outlist); ecpg_free(stmt->command); ecpg_free(stmt->name); -#ifndef HAVE_USELOCALE - ecpg_free(stmt->oldlocale); -#endif ecpg_free(stmt); } @@ -465,7 +463,7 @@ sprintf_double_value(char *ptr, double value, const char *delim) sprintf(ptr, "%s%s", "Infinity", delim); } else - sprintf(ptr, "%.15g%s", value, delim); + PGTYPESsprintf(ptr, "%.15g%s", value, delim); } static void @@ -481,7 +479,7 @@ sprintf_float_value(char *ptr, float value, const char *delim) sprintf(ptr, "%s%s", "Infinity", delim); } else - sprintf(ptr, "%.15g%s", value, delim); + PGTYPESsprintf(ptr, "%.15g%s", value, delim); } static char * @@ -1975,37 +1973,13 @@ ecpg_do_prologue(int lineno, const int compat, const int force_indicator, /* * Make sure we do NOT honor the locale for numeric input/output since the - * database wants the standard decimal point. If available, use - * uselocale() for this because it's thread-safe. Windows doesn't have - * that, but it usually does have _configthreadlocale(). In some versions - * of MinGW, _configthreadlocale() exists but always returns -1 --- so - * treat that situation as if the function doesn't exist. + * database wants the standard decimal point. */ -#ifdef HAVE_USELOCALE - - /* - * 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); - return false; - } -#else -#ifdef HAVE__CONFIGTHREADLOCALE - stmt->oldthreadlocale = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); -#endif - stmt->oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno); - if (stmt->oldlocale == NULL) + if (PGTYPESbegin_clocale(&stmt->oldlocale) < 0) { ecpg_do_epilogue(stmt); return false; } - setlocale(LC_NUMERIC, "C"); -#endif /* * If statement type is ECPGst_prepnormal we are supposed to prepare the @@ -2213,23 +2187,7 @@ ecpg_do_epilogue(struct statement *stmt) if (stmt == NULL) return; -#ifdef HAVE_USELOCALE - if (stmt->oldlocale != (locale_t) 0) - uselocale(stmt->oldlocale); -#else - if (stmt->oldlocale) - setlocale(LC_NUMERIC, stmt->oldlocale); -#ifdef HAVE__CONFIGTHREADLOCALE - - /* - * This is a bit trickier than it looks: if we failed partway through - * statement initialization, oldthreadlocale could still be 0. But that's - * okay because a call with 0 is defined to be a no-op. - */ - if (stmt->oldthreadlocale != -1) - (void) _configthreadlocale(stmt->oldthreadlocale); -#endif -#endif + PGTYPESend_clocale(stmt->oldlocale); free_statement(stmt); } diff --git a/src/interfaces/ecpg/include/pgtypes.h b/src/interfaces/ecpg/include/pgtypes.h index dbf759b45f..18251d74c9 100644 --- a/src/interfaces/ecpg/include/pgtypes.h +++ b/src/interfaces/ecpg/include/pgtypes.h @@ -10,6 +10,12 @@ extern "C" extern void PGTYPESchar_free(char *ptr); +/* + * Initialize. Not thread-safe and should only be called in one thread at a + * time, but idempotent. Returns 0 on success, -1 on failure and sets errno. + */ +extern int PGTYPESinit(void); + #ifdef __cplusplus } #endif diff --git a/src/interfaces/ecpg/include/pgtypes_format.h b/src/interfaces/ecpg/include/pgtypes_format.h new file mode 100644 index 0000000000..d6dd06d361 --- /dev/null +++ b/src/interfaces/ecpg/include/pgtypes_format.h @@ -0,0 +1,26 @@ +/* + * We would like to be able to convert between strings and doubles using the C + * locale. We can set the thread's current locale with uselocale() and use + * snprintf() and strtod() according to the POSIX standard, but some systems + * haven't implemented uselocale() yet. We can use snprintf_l() and + * strtod_l() on some systems, but POSIX hasn't standardized those yet. All + * systems we target can do one or the other, so provide a simple abstraction. + */ + +#ifndef PGTYPES_FORMAT_H +#define PGTYPES_FORMAT_H + +#if defined(LOCALE_T_IN_XLOCALE) +#include <xlocale.h> +#else +#include <locale.h> +#endif + +extern int PGTYPESbegin_clocale(locale_t *old_locale); +extern void PGTYPESend_clocale(locale_t old_locale); + +extern double PGTYPESstrtod(const char *str, char **endptr); +extern int PGTYPESsprintf(char *str, const char *format, ...) pg_attribute_printf(2, 3); +extern int PGTYPESsnprintf(char *str, size_t size, const char *format, ...) pg_attribute_printf(3, 4); + +#endif diff --git a/src/interfaces/ecpg/pgtypeslib/common.c b/src/interfaces/ecpg/pgtypeslib/common.c index 8972229ca2..37ba35bc7a 100644 --- a/src/interfaces/ecpg/pgtypeslib/common.c +++ b/src/interfaces/ecpg/pgtypeslib/common.c @@ -3,8 +3,142 @@ #include "postgres_fe.h" #include "pgtypes.h" +#include "pgtypes_format.h" #include "pgtypeslib_extern.h" +/* + * Use wcstombs_l's header as a clue about where to find the other extra _l + * functions. + */ +#if defined(LOCALE_T_IN_XLOCALE) || defined(WCSTOMBS_L_IN_XLOCALE) +#include <xlocale.h> +#else +#include <locale.h> +#endif + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> + +static locale_t PGTYPESclocale = (locale_t) 0; + +int +PGTYPESinit(void) +{ + /* Already called? */ + if (PGTYPESclocale != (locale_t) 0) + return 0; + +#ifdef WIN32 + PGTYPESclocale = _create_locale(LC_ALL, "C"); +#else + PGTYPESclocale = newlocale(LC_ALL_MASK, "C", (locale_t) 0); +#endif + if (PGTYPESclocale == (locale_t) 0) + return -1; + return -0; +} + +/* + * If any of these _l() functions are missing, we need to mess with the + * thread-local locale. + */ +#if !defined(HAVE_STRTOD_L) || !defined(HAVE_VSPRINTF_L) || !defined(HAVE_VSNPRINTF_L) +#if defined(WIN32) +/* Windows has all of these in slightly scrambled form. */ +#else +/* At least one of these functions is missing. We'll do the save/restore. */ +#define PGTYPES_MUST_SAVE_RESTORE_THREAD_LOCALE +#endif +#endif + +/* + * Before running code that might do conversions, call this to change the + * thread's current locale on platforms that need it. This is done as a + * separate function rather than inside individual conversion wrappers for + * some batching effect, avoiding repeated save/restore. + * + * PGTYPESinit() must have been called, or this will have no effect. + */ +int +PGTYPESbegin_clocale(locale_t *old_locale) +{ +#ifdef PGTYPES_MUST_SAVE_RESTORE_THREAD_LOCALE + /* + * On this platform, at least one of the functions below expects us to + * have changed the thread's locale. The caller provides space for us to + * store the current locale, so we can change it back later. + */ + *old_locale = uselocale(PGTYPESclocale); + return (*old_locale == (locale_t) 0) ? -1 : 0; +#else + /* + * Dummy value. We have _l() variants of the functions we need, and we + * might not even have uselocale() on this platform. + */ + *old_locale = (locale_t) 0; + return 0; +#endif +} + +/* + * Restore the current thread's locale. Call with the value returned by + * PGTYPESbegin_clocale(). Does nothing on platforms with all the required + * _l() functions. + */ +void +PGTYPESend_clocale(locale_t old_locale) +{ +#ifdef PGTYPES_MUST_SAVE_RESTORE_THREAD_LOCALE + /* Put it back. */ + uselocale(old_locale); +#endif +} + +double +PGTYPESstrtod(const char *str, char **endptr) +{ +#if defined(WIN32) + return _strtod_l(str, endptr, PGTYPESclocale); +#elif defined(HAVE_STRTOD_L) + return strtod_l(str, endptr, PGTYPESclocale); +#else + return strtod(str, endptr); +#endif +} + +int +PGTYPESsprintf(char *str, const char *format, ...) +{ + va_list args; + + va_start(args, format); + +#if defined(WIN32) + return _vsprintf_l(str, format, PGTYPESclocale, args); +#elif defined(HAVE_VSPRINTF_L) + return vsprintf_l(str, PGTYPESclocale, format, args); +#else + return vsprintf(str, format, args); +#endif +} + +int +PGTYPESsnprintf(char *str, size_t size, const char *format, ...) +{ + va_list args; + + va_start(args, format); + +#if defined(WIN32) + return _vsnprintf_l(str, size, format, PGTYPESclocale, args); +#elif defined(HAVE_VSPRINTF_L) + return vsnprintf_l(str, size, PGTYPESclocale, format, args); +#else + return vsnprintf(str, size, format, args); +#endif +} + /* Return value is zero-filled. */ char * pgtypes_alloc(long size) @@ -81,8 +215,8 @@ pgtypes_fmt_replace(union un_fmt_comb replace_val, int replace_type, char **outp switch (replace_type) { case PGTYPES_TYPE_DOUBLE_NF: - i = snprintf(t, PGTYPES_FMT_NUM_MAX_DIGITS, - "%0.0g", replace_val.double_val); + i = PGTYPESsnprintf(t, PGTYPES_FMT_NUM_MAX_DIGITS, + "%0.0g", replace_val.double_val); break; case PGTYPES_TYPE_INT64: i = snprintf(t, PGTYPES_FMT_NUM_MAX_DIGITS, diff --git a/src/interfaces/ecpg/pgtypeslib/exports.txt b/src/interfaces/ecpg/pgtypeslib/exports.txt index 2d5ec17656..3fe02bc60a 100644 --- a/src/interfaces/ecpg/pgtypeslib/exports.txt +++ b/src/interfaces/ecpg/pgtypeslib/exports.txt @@ -46,3 +46,9 @@ PGTYPEStimestamp_sub 43 PGTYPEStimestamp_sub_interval 44 PGTYPEStimestamp_to_asc 45 PGTYPESchar_free 46 +PGTYPESinit 47 +PGTYPESsprintf 48 +PGTYPESsnprintf 49 +PGTYPESstrtod 50 +PGTYPESbegin_clocale 51 +PGTYPESend_clocale 52 diff --git a/src/interfaces/ecpg/pgtypeslib/interval.c b/src/interfaces/ecpg/pgtypeslib/interval.c index 936a688381..124ed5e1c2 100644 --- a/src/interfaces/ecpg/pgtypeslib/interval.c +++ b/src/interfaces/ecpg/pgtypeslib/interval.c @@ -13,6 +13,7 @@ #include "common/string.h" #include "dt.h" #include "pgtypes_error.h" +#include "pgtypes_format.h" #include "pgtypes_interval.h" #include "pgtypeslib_extern.h" @@ -60,7 +61,7 @@ ParseISO8601Number(const char *str, char **endptr, int *ipart, double *fpart) if (!(isdigit((unsigned char) *str) || *str == '-' || *str == '.')) return DTERR_BAD_FORMAT; errno = 0; - val = strtod(str, endptr); + val = PGTYPESstrtod(str, endptr); /* did we not see anything that looks like a double? */ if (*endptr == str || errno != 0) return DTERR_BAD_FORMAT; @@ -455,7 +456,7 @@ DecodeInterval(char **field, int *ftype, int nf, /* int range, */ else if (*cp == '.') { errno = 0; - fval = strtod(cp, &cp); + fval = PGTYPESstrtod(cp, &cp); if (*cp != '\0' || errno != 0) return DTERR_BAD_FORMAT; diff --git a/src/interfaces/ecpg/pgtypeslib/numeric.c b/src/interfaces/ecpg/pgtypeslib/numeric.c index 35e7b92da4..a8bc19cc8f 100644 --- a/src/interfaces/ecpg/pgtypeslib/numeric.c +++ b/src/interfaces/ecpg/pgtypeslib/numeric.c @@ -7,6 +7,7 @@ #include <limits.h> #include "pgtypes_error.h" +#include "pgtypes_format.h" #include "pgtypes_numeric.h" #include "pgtypeslib_extern.h" @@ -1414,7 +1415,7 @@ PGTYPESnumeric_from_double(double d, numeric *dst) numeric *tmp; int i; - if (sprintf(buffer, "%.*g", DBL_DIG, d) <= 0) + if (PGTYPESsprintf(buffer, "%.*g", DBL_DIG, d) <= 0) return -1; if ((tmp = PGTYPESnumeric_from_asc(buffer, NULL)) == NULL) diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm index 98a5b5d872..6c550f3d37 100644 --- a/src/tools/msvc/Solution.pm +++ b/src/tools/msvc/Solution.pm @@ -339,6 +339,7 @@ sub GenerateFiles HAVE_STRLCPY => undef, HAVE_STRNLEN => 1, HAVE_STRSIGNAL => undef, + HAVE_STRTOD_L => undef, HAVE_STRUCT_OPTION => undef, HAVE_STRUCT_SOCKADDR_SA_LEN => undef, HAVE_STRUCT_TM_TM_ZONE => undef, @@ -361,7 +362,6 @@ sub GenerateFiles HAVE_UINT8 => undef, HAVE_UNION_SEMUN => undef, HAVE_UNISTD_H => 1, - HAVE_USELOCALE => undef, HAVE_UUID_BSD => undef, HAVE_UUID_E2FS => undef, HAVE_UUID_OSSP => undef, @@ -369,6 +369,8 @@ sub GenerateFiles HAVE_UUID_UUID_H => undef, HAVE_WCSTOMBS_L => undef, HAVE_VISIBILITY_ATTRIBUTE => undef, + HAVE_VSNPRINTF_L => undef, + HAVE_VSPRINTF_L => undef, HAVE_X509_GET_SIGNATURE_INFO => undef, HAVE_X86_64_POPCNTQ => undef, HAVE__BOOL => undef, @@ -383,7 +385,6 @@ sub GenerateFiles HAVE__BUILTIN_POPCOUNT => undef, HAVE__BUILTIN_TYPES_COMPATIBLE_P => undef, HAVE__BUILTIN_UNREACHABLE => undef, - HAVE__CONFIGTHREADLOCALE => 1, HAVE__CPUID => 1, HAVE__GET_CPUID => undef, HAVE__STATIC_ASSERT => undef, -- 2.39.3 (Apple Git-145) ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2023-11-17 18:18 Tom Lane <[email protected]> parent: Thomas Munro <[email protected]> 1 sibling, 1 reply; 41+ messages in thread From: Tom Lane @ 2023-11-17 18:18 UTC (permalink / raw) To: Thomas Munro <[email protected]>; +Cc: Tristan Partin <[email protected]>; pgsql-hackers Thomas Munro <[email protected]> writes: > On Thu, Nov 16, 2023 at 12:06 PM Tom Lane <[email protected]> wrote: >> Thomas Munro <[email protected]> writes: >>> Perhaps we could use snprintf_l() and strtod_l() where available. >>> They're not standard, but they are obvious extensions that NetBSD and >>> Windows have, and those are the two systems for which we are doing >>> grotty things in that code. >> Yeah. I'd say the _l functions should be preferred over uselocale() >> if available, but sadly they're not there on common systems. (It >> looks like glibc has strtod_l but not snprintf_l, which is odd.) > Here is a first attempt. I've not reviewed this closely, but I did try it on mamba's host. It compiles and passes regression testing, but I see two warnings: common.c: In function 'PGTYPESsprintf': common.c:120:2: warning: function 'PGTYPESsprintf' might be a candidate for 'gnu_printf' format attribute [-Wsuggest-attribute=format] 120 | return vsprintf_l(str, PGTYPESclocale, format, args); | ^~~~~~ common.c: In function 'PGTYPESsnprintf': common.c:136:2: warning: function 'PGTYPESsnprintf' might be a candidate for 'gnu_printf' format attribute [-Wsuggest-attribute=format] 136 | return vsnprintf_l(str, size, PGTYPESclocale, format, args); | ^~~~~~ That happens because on NetBSD, we define PG_PRINTF_ATTRIBUTE as "__syslog__" so that the compiler will not warn about use of %m (apparently, they support %m in syslog() but not printf(), sigh). I think this is telling us about an actual problem: these new functions are based on libc's printf not what we have in snprintf.c, and therefore we really shouldn't be assuming that they will support any format specs beyond what POSIX requires for printf. If somebody tried to use %m in one of these calls, we'd like to get warnings about that. I experimented with the attached delta patch and it does silence these warnings. I suspect that ecpg_log() should be marked as pg_attribute_std_printf() too, because it has the same issue, but I didn't try that. (Probably, we see no warning for that because the compiler isn't quite bright enough to connect the format argument with the string that gets passed to vfprintf().) regards, tom lane Attachments: [text/x-diff] 0002-use-correct-printf-attribute.patch (1.7K, ../../[email protected]/2-0002-use-correct-printf-attribute.patch) download | inline diff: diff --git a/src/include/c.h b/src/include/c.h index 82f8e9d4c7..98e3bbf386 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -171,13 +171,19 @@ #define PG_USED_FOR_ASSERTS_ONLY pg_attribute_unused() #endif -/* GCC and XLC support format attributes */ +/* + * GCC and XLC support format attributes. Use pg_attribute_printf() + * for our src/port/snprintf.c implementation and functions based on it. + * Use pg_attribute_std_printf() for functions based on libc's printf. + */ #if defined(__GNUC__) || defined(__IBMC__) #define pg_attribute_format_arg(a) __attribute__((format_arg(a))) #define pg_attribute_printf(f,a) __attribute__((format(PG_PRINTF_ATTRIBUTE, f, a))) +#define pg_attribute_std_printf(f,a) __attribute__((format(printf, f, a))) #else #define pg_attribute_format_arg(a) #define pg_attribute_printf(f,a) +#define pg_attribute_std_printf(f,a) #endif /* GCC, Sunpro and XLC support aligned, packed and noreturn */ diff --git a/src/interfaces/ecpg/include/pgtypes_format.h b/src/interfaces/ecpg/include/pgtypes_format.h index d6dd06d361..87160fab59 100644 --- a/src/interfaces/ecpg/include/pgtypes_format.h +++ b/src/interfaces/ecpg/include/pgtypes_format.h @@ -20,7 +20,7 @@ extern int PGTYPESbegin_clocale(locale_t *old_locale); extern void PGTYPESend_clocale(locale_t old_locale); extern double PGTYPESstrtod(const char *str, char **endptr); -extern int PGTYPESsprintf(char *str, const char *format, ...) pg_attribute_printf(2, 3); -extern int PGTYPESsnprintf(char *str, size_t size, const char *format, ...) pg_attribute_printf(3, 4); +extern int PGTYPESsprintf(char *str, const char *format, ...) pg_attribute_std_printf(2, 3); +extern int PGTYPESsnprintf(char *str, size_t size, const char *format, ...) pg_attribute_std_printf(3, 4); #endif ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2023-11-17 22:58 Tom Lane <[email protected]> parent: Tom Lane <[email protected]> 0 siblings, 1 reply; 41+ messages in thread From: Tom Lane @ 2023-11-17 22:58 UTC (permalink / raw) To: Thomas Munro <[email protected]>; +Cc: Tristan Partin <[email protected]>; pgsql-hackers I wrote: > I've not reviewed this closely, but I did try it on mamba's host. > It compiles and passes regression testing, but I see two warnings: > common.c: In function 'PGTYPESsprintf': > common.c:120:2: warning: function 'PGTYPESsprintf' might be a candidate for 'gnu_printf' format attribute [-Wsuggest-attribute=format] > 120 | return vsprintf_l(str, PGTYPESclocale, format, args); > | ^~~~~~ > common.c: In function 'PGTYPESsnprintf': > common.c:136:2: warning: function 'PGTYPESsnprintf' might be a candidate for 'gnu_printf' format attribute [-Wsuggest-attribute=format] > 136 | return vsnprintf_l(str, size, PGTYPESclocale, format, args); > | ^~~~~~ > I think this is telling us about an actual problem: these new > functions are based on libc's printf not what we have in snprintf.c, > and therefore we really shouldn't be assuming that they will support > any format specs beyond what POSIX requires for printf. Wait, I just realized that there's more to this. ecpglib *does* rely on our snprintf.c functions: $ nm --ext --undef src/interfaces/ecpg/ecpglib/*.o | grep printf U pg_snprintf U pg_fprintf U pg_snprintf U pg_printf U pg_snprintf U pg_sprintf U pg_fprintf U pg_snprintf U pg_vfprintf U pg_snprintf U pg_sprintf U pg_sprintf We are getting these warnings because vsprintf_l and vsnprintf_l don't have snprintf.c implementations, so the compiler sees the attributes attached to them by stdio.h. This raises the question of whether changing snprintf.c could be part of the solution. I'm not sure that we want to try to emulate vs[n]printf_l directly, but perhaps there's another way? In any case, my concern about ecpg_log() is misplaced. That is really using pg_vfprintf, so it's correctly marked. regards, tom lane ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2023-11-18 00:03 Andres Freund <[email protected]> parent: Thomas Munro <[email protected]> 1 sibling, 0 replies; 41+ messages in thread From: Andres Freund @ 2023-11-18 00:03 UTC (permalink / raw) To: Thomas Munro <[email protected]>; +Cc: Tom Lane <[email protected]>; Tristan Partin <[email protected]>; pgsql-hackers Hi, On 2023-11-17 08:57:47 +1300, Thomas Munro wrote: > I also had a go[3] at doing it with static inlined functions, to avoid > creating a load of new exported functions and associated function call > overheads. It worked fine, except on Windows: I needed a global > variable PGTYPESclocale that all the inlined functions can see when > called from ecpglib or pgtypeslib code, but if I put that in the > exports list then on that platform it seems to contain garbage; there > is probably some other magic needed to export non-function symbols > from the DLL or something like that, I didn't look into it. See CI > failure + crash dumps. I suspect you'd need __declspec(dllimport) on the variable to make that work. I.e. use PGDLLIMPORT and define BUILDING_DLL while building the libraries, so they see __declspec (dllexport). I luckily forgot the details, but functions just call into some thunk that does necessary magic, but that option doesn't exist for variables, so the compiler/linker have to do stuff, hence needing __declspec(dllimport). Greetings, Andres Freund ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2023-11-19 22:00 Thomas Munro <[email protected]> parent: Tom Lane <[email protected]> 0 siblings, 1 reply; 41+ messages in thread From: Thomas Munro @ 2023-11-19 22:00 UTC (permalink / raw) To: Tom Lane <[email protected]>; +Cc: Tristan Partin <[email protected]>; pgsql-hackers On Sat, Nov 18, 2023 at 11:58 AM Tom Lane <[email protected]> wrote: > I wrote: > > I've not reviewed this closely, but I did try it on mamba's host. > > It compiles and passes regression testing, but I see two warnings: > > > common.c: In function 'PGTYPESsprintf': > > common.c:120:2: warning: function 'PGTYPESsprintf' might be a candidate for 'gnu_printf' format attribute [-Wsuggest-attribute=format] > > 120 | return vsprintf_l(str, PGTYPESclocale, format, args); > > | ^~~~~~ > > common.c: In function 'PGTYPESsnprintf': > > common.c:136:2: warning: function 'PGTYPESsnprintf' might be a candidate for 'gnu_printf' format attribute [-Wsuggest-attribute=format] > > 136 | return vsnprintf_l(str, size, PGTYPESclocale, format, args); > > | ^~~~~~ > > > I think this is telling us about an actual problem: these new > > functions are based on libc's printf not what we have in snprintf.c, > > and therefore we really shouldn't be assuming that they will support > > any format specs beyond what POSIX requires for printf. Right, thanks. > We are getting these warnings because vsprintf_l and > vsnprintf_l don't have snprintf.c implementations, so the > compiler sees the attributes attached to them by stdio.h. > > This raises the question of whether changing snprintf.c > could be part of the solution. I'm not sure that we want > to try to emulate vs[n]printf_l directly, but perhaps there's > another way? Yeah, I have been wondering about that too. The stuff I posted so far was just about how to remove some gross and incorrect code from ecpg, a somewhat niche frontend part of PostgreSQL. I guess Tristan is thinking bigger: removing obstacles to going multi-threaded in the backend. Clearly locales are one of the places where global state will bite us, so we either need to replace setlocale() with uselocale() for the database default locale, or use explicit locale arguments with _l() functions everywhere and pass in the right locale. Due to incompleteness of (a) libc implementations and (b) the standard, we can't directly do either, so we'll need to cope with that. Thought experiment: If we supplied our own fallback _l() replacement functions where missing, and those did uselocale() save/restore, many systems wouldn't need them, for example glibc has strtod_l() as you noted, and several other systems have systematically added them for all sorts of stuff. The case of the *printf* family is quite interesting, because there we already have our own implement for other reasons, so it might make sense to add the _l() variants to our snprintf.c implementations. On glibc, snprintf.c would have to do a uselocale() save/restore where it punts %g to the system snprintf, but if that offends some instruction cycle bean counter, perhaps we could replace that bit with Ryu anyway (or is it not general enough to handle all the stuff %g et al can do? I haven't looked). I am not sure how you would ever figure out what other stuff is affected by the global locale in general, for example code hiding in extensions etc, but, I mean, that's what's wrong with global state in a nutshell and it has often been speculated that multi-threaded PostgreSQL might have a way to say 'I still want one process per session because my extensions don't all identify themselves as thread-safe yet'. BTW is this comment in snprintf.c true? * 1. No locale support: the radix character is always '.' and the ' * (single quote) format flag is ignored. It is in the backend but only because we nail down LC_NUMERIC early on, not because of any property of snprintf.c, no? ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2023-11-19 22:36 Tom Lane <[email protected]> parent: Thomas Munro <[email protected]> 0 siblings, 1 reply; 41+ messages in thread From: Tom Lane @ 2023-11-19 22:36 UTC (permalink / raw) To: Thomas Munro <[email protected]>; +Cc: Tristan Partin <[email protected]>; pgsql-hackers Thomas Munro <[email protected]> writes: > BTW is this comment in snprintf.c true? > * 1. No locale support: the radix character is always '.' and the ' > * (single quote) format flag is ignored. > It is in the backend but only because we nail down LC_NUMERIC early > on, not because of any property of snprintf.c, no? Hmm, the second part of it is true. But given that we punt float formatting to libc, I think you are right that the first part depends on LC_NUMERIC being frozen. regards, tom lane ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2023-11-20 00:00 Thomas Munro <[email protected]> parent: Tom Lane <[email protected]> 0 siblings, 1 reply; 41+ messages in thread From: Thomas Munro @ 2023-11-20 00:00 UTC (permalink / raw) To: Tom Lane <[email protected]>; +Cc: Tristan Partin <[email protected]>; pgsql-hackers On Mon, Nov 20, 2023 at 11:36 AM Tom Lane <[email protected]> wrote: > Thomas Munro <[email protected]> writes: > > BTW is this comment in snprintf.c true? > > > * 1. No locale support: the radix character is always '.' and the ' > > * (single quote) format flag is ignored. > > > It is in the backend but only because we nail down LC_NUMERIC early > > on, not because of any property of snprintf.c, no? > > Hmm, the second part of it is true. But given that we punt float > formatting to libc, I think you are right that the first part > depends on LC_NUMERIC being frozen. If we are sure that we'll *never* want locale-aware printf-family functions (ie we *always* want "C" locale), then in the thought experiment above where I suggested we supply replacement _l() functions, we could just skip that for the printf family, but make that above comment actually true. Perhaps with Ryu, but otherwise by punting to libc _l() or uselocale() save/restore. ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2023-11-20 16:40 Tom Lane <[email protected]> parent: Thomas Munro <[email protected]> 0 siblings, 1 reply; 41+ messages in thread From: Tom Lane @ 2023-11-20 16:40 UTC (permalink / raw) To: Thomas Munro <[email protected]>; +Cc: Tristan Partin <[email protected]>; pgsql-hackers Thomas Munro <[email protected]> writes: > If we are sure that we'll *never* want locale-aware printf-family > functions (ie we *always* want "C" locale), then in the thought > experiment above where I suggested we supply replacement _l() > functions, we could just skip that for the printf family, but make > that above comment actually true. Perhaps with Ryu, but otherwise by > punting to libc _l() or uselocale() save/restore. It is pretty annoying that we've got that shiny Ryu code and can't use it here. From memory, we did look into that and concluded that Ryu wasn't amenable to providing "exactly this many digits" as is required by most variants of printf's conversion specs. But maybe somebody should go try harder. (Worst case, you could do rounding off by hand on the produced digit string, but that's ugly...) regards, tom lane ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2024-08-10 01:29 Thomas Munro <[email protected]> parent: Tom Lane <[email protected]> 0 siblings, 1 reply; 41+ messages in thread From: Thomas Munro @ 2024-08-10 01:29 UTC (permalink / raw) To: Tom Lane <[email protected]>; +Cc: Tristan Partin <[email protected]>; pgsql-hackers On Tue, Nov 21, 2023 at 5:40 AM Tom Lane <[email protected]> wrote: > Thomas Munro <[email protected]> writes: > > If we are sure that we'll *never* want locale-aware printf-family > > functions (ie we *always* want "C" locale), then in the thought > > experiment above where I suggested we supply replacement _l() > > functions, we could just skip that for the printf family, but make > > that above comment actually true. Perhaps with Ryu, but otherwise by > > punting to libc _l() or uselocale() save/restore. Here is a new attempt at this can of portability worms. This time: * pg_get_c_locale() is available to anyone who needs a "C" locale_t * ECPG uses strtod_l(..., pg_get_c_locale()) for parsing * snprintf.c always uses "C" for floats, so it conforms to its own documented behaviour, and ECPG doesn't have to do anything special I'm not trying to offer a working *printf_l() family to the whole tree because it seems like really we only ever care about "C" for this purpose. So snprintf.c internally uses pg_get_c_locale() with snprintf_l(), _snprintf_l() or uselocale()/snprintf()/uselocale() depending on platform. > It is pretty annoying that we've got that shiny Ryu code and can't > use it here. From memory, we did look into that and concluded that > Ryu wasn't amenable to providing "exactly this many digits" as is > required by most variants of printf's conversion specs. But maybe > somebody should go try harder. (Worst case, you could do rounding > off by hand on the produced digit string, but that's ugly...) Yeah it does seem like a promising idea, but I haven't looked into it myself. Attachments: [text/x-patch] v2-0001-Improve-locale-thread-safety-of-ECPG.patch (29.6K, ../../CA+hUKG+Yv+ps=nS2T8SS1UDU=iySHSr4sGHYiYGkPTpZx6Ooww@mail.gmail.com/2-v2-0001-Improve-locale-thread-safety-of-ECPG.patch) download | inline diff: From bc64f2db61b3e2cf7a0cd00b1deaa260d91e2b3c Mon Sep 17 00:00:00 2001 From: Thomas Munro <[email protected]> Date: Sat, 10 Aug 2024 11:01:21 +1200 Subject: [PATCH v2] Improve locale thread safety of ECPG. Remove the use of setlocale() as a fallback strategy on systems that don't have uselocale(), where ECPG tries to control LC_NUMERIC formatting on input and output of floating point numbers. Instead, create a new function pg_get_c_locale() that can be used to get a locale_t object for use with thread-safe parsing and formatting functions. Then: 1. Use strtod_l() for parsing, and supply an implementation using uselocale() if it is missing. (All systems have one of strtod_l(), _strtod_l() or uselocale() for a replacement.) 2. Inside our own snprintf.c, use snprintf_l() or _snprintf_l() where we punt floating point numbers to the system snprintf(), or wrap it in uselocale() if those are not available. Since the non-standard _l() functions require <xlocale.h> on *BSD/macOS systems, simplify the configure probes: instead of XXX_IN_XLOCALE_H for several features XXX, let's just include <xlocale.h> if HAVE_XLOCALE_H. The reason for the extra complication was apparently that some old glibc systems also had an <xlocale.h>, and you weren't supposed to include it directly, but it's gone now (as far as I can tell it was harmless to do so anyway). Discussion: https://postgr.es/m/CWZBBRR6YA8D.8EHMDRGLCKCD%40neon.tech --- config/c-library.m4 | 55 --------- configure | 115 +------------------ configure.ac | 6 +- meson.build | 37 +----- src/include/pg_config.h.in | 15 +-- src/include/port.h | 29 +++++ src/include/utils/pg_locale.h | 2 +- src/interfaces/ecpg/ecpglib/connect.c | 37 ------ src/interfaces/ecpg/ecpglib/data.c | 2 +- src/interfaces/ecpg/ecpglib/descriptor.c | 37 ------ src/interfaces/ecpg/ecpglib/ecpglib_extern.h | 15 --- src/interfaces/ecpg/ecpglib/execute.c | 55 --------- src/interfaces/ecpg/pgtypeslib/dt_common.c | 6 +- src/interfaces/ecpg/pgtypeslib/interval.c | 4 +- src/interfaces/ecpg/pgtypeslib/numeric.c | 2 +- src/port/Makefile | 1 + src/port/locale.c | 73 ++++++++++++ src/port/meson.build | 1 + src/port/snprintf.c | 33 ++++++ 19 files changed, 157 insertions(+), 368 deletions(-) create mode 100644 src/port/locale.c diff --git a/config/c-library.m4 b/config/c-library.m4 index aa8223d2ef..421bc612b2 100644 --- a/config/c-library.m4 +++ b/config/c-library.m4 @@ -81,58 +81,3 @@ AC_DEFUN([PGAC_STRUCT_SOCKADDR_SA_LEN], [#include <sys/types.h> #include <sys/socket.h> ])])# PGAC_STRUCT_SOCKADDR_MEMBERS - - -# PGAC_TYPE_LOCALE_T -# ------------------ -# Check for the locale_t type and find the right header file. macOS -# needs xlocale.h; standard is locale.h, but glibc <= 2.25 also had an -# xlocale.h file that we should not use, so we check the standard -# header first. -AC_DEFUN([PGAC_TYPE_LOCALE_T], -[AC_CACHE_CHECK([for locale_t], pgac_cv_type_locale_t, -[AC_COMPILE_IFELSE([AC_LANG_PROGRAM( -[#include <locale.h> -locale_t x;], -[])], -[pgac_cv_type_locale_t=yes], -[AC_COMPILE_IFELSE([AC_LANG_PROGRAM( -[#include <xlocale.h> -locale_t x;], -[])], -[pgac_cv_type_locale_t='yes (in xlocale.h)'], -[pgac_cv_type_locale_t=no])])]) -if test "$pgac_cv_type_locale_t" = 'yes (in xlocale.h)'; then - AC_DEFINE(LOCALE_T_IN_XLOCALE, 1, - [Define to 1 if `locale_t' requires <xlocale.h>.]) -fi])# PGAC_TYPE_LOCALE_T - - -# PGAC_FUNC_WCSTOMBS_L -# -------------------- -# Try to find a declaration for wcstombs_l(). It might be in stdlib.h -# (following the POSIX requirement for wcstombs()), or in locale.h, or in -# xlocale.h. If it's in the latter, define WCSTOMBS_L_IN_XLOCALE. -# -AC_DEFUN([PGAC_FUNC_WCSTOMBS_L], -[AC_CACHE_CHECK([for wcstombs_l declaration], pgac_cv_func_wcstombs_l, -[AC_COMPILE_IFELSE([AC_LANG_PROGRAM( -[#include <stdlib.h> -#include <locale.h>], -[#ifndef wcstombs_l -(void) wcstombs_l; -#endif])], -[pgac_cv_func_wcstombs_l='yes'], -[AC_COMPILE_IFELSE([AC_LANG_PROGRAM( -[#include <stdlib.h> -#include <locale.h> -#include <xlocale.h>], -[#ifndef wcstombs_l -(void) wcstombs_l; -#endif])], -[pgac_cv_func_wcstombs_l='yes (in xlocale.h)'], -[pgac_cv_func_wcstombs_l='no'])])]) -if test "$pgac_cv_func_wcstombs_l" = 'yes (in xlocale.h)'; then - AC_DEFINE(WCSTOMBS_L_IN_XLOCALE, 1, - [Define to 1 if `wcstombs_l' requires <xlocale.h>.]) -fi])# PGAC_FUNC_WCSTOMBS_L diff --git a/configure b/configure index 4f3aa44756..298492ec25 100755 --- a/configure +++ b/configure @@ -14635,55 +14635,6 @@ _ACEOF fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for locale_t" >&5 -$as_echo_n "checking for locale_t... " >&6; } -if ${pgac_cv_type_locale_t+:} false; then : - $as_echo_n "(cached) " >&6 -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include <locale.h> -locale_t x; -int -main () -{ - - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - pgac_cv_type_locale_t=yes -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include <xlocale.h> -locale_t x; -int -main () -{ - - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - pgac_cv_type_locale_t='yes (in xlocale.h)' -else - pgac_cv_type_locale_t=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv_type_locale_t" >&5 -$as_echo "$pgac_cv_type_locale_t" >&6; } -if test "$pgac_cv_type_locale_t" = 'yes (in xlocale.h)'; then - -$as_echo "#define LOCALE_T_IN_XLOCALE 1" >>confdefs.h - -fi - # MSVC doesn't cope well with defining restrict to __restrict, the # spelling it understands, because it conflicts with # __declspec(restrict). Therefore we define pg_restrict to the @@ -15170,59 +15121,6 @@ if test x"$pgac_cv_var_int_timezone" = xyes ; then $as_echo "#define HAVE_INT_TIMEZONE 1" >>confdefs.h -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for wcstombs_l declaration" >&5 -$as_echo_n "checking for wcstombs_l declaration... " >&6; } -if ${pgac_cv_func_wcstombs_l+:} false; then : - $as_echo_n "(cached) " >&6 -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include <stdlib.h> -#include <locale.h> -int -main () -{ -#ifndef wcstombs_l -(void) wcstombs_l; -#endif - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - pgac_cv_func_wcstombs_l='yes' -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include <stdlib.h> -#include <locale.h> -#include <xlocale.h> -int -main () -{ -#ifndef wcstombs_l -(void) wcstombs_l; -#endif - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - pgac_cv_func_wcstombs_l='yes (in xlocale.h)' -else - pgac_cv_func_wcstombs_l='no' -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv_func_wcstombs_l" >&5 -$as_echo "$pgac_cv_func_wcstombs_l" >&6; } -if test "$pgac_cv_func_wcstombs_l" = 'yes (in xlocale.h)'; then - -$as_echo "#define WCSTOMBS_L_IN_XLOCALE 1" >>confdefs.h - fi # Some versions of libedit contain strlcpy(), setproctitle(), and other @@ -15232,7 +15130,7 @@ fi LIBS_including_readline="$LIBS" LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'` -for ac_func in backtrace_symbols copyfile copy_file_range getifaddrs getpeerucred inet_pton kqueue mbstowcs_l memset_s posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast strchrnul strsignal syncfs sync_file_range uselocale wcstombs_l +for ac_func in backtrace_symbols copyfile copy_file_range getifaddrs getpeerucred inet_pton kqueue mbstowcs_l memset_s posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast snprintf_l strtod_l strchrnul strsignal syncfs sync_file_range uselocale wcstombs_l do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" @@ -16005,17 +15903,6 @@ fi # Win32 (really MinGW) support if test "$PORTNAME" = "win32"; then - for ac_func in _configthreadlocale -do : - ac_fn_c_check_func "$LINENO" "_configthreadlocale" "ac_cv_func__configthreadlocale" -if test "x$ac_cv_func__configthreadlocale" = xyes; then : - cat >>confdefs.h <<_ACEOF -#define HAVE__CONFIGTHREADLOCALE 1 -_ACEOF - -fi -done - case " $LIBOBJS " in *" dirmod.$ac_objext "* ) ;; *) LIBOBJS="$LIBOBJS dirmod.$ac_objext" diff --git a/configure.ac b/configure.ac index 049bc01491..302aadd427 100644 --- a/configure.ac +++ b/configure.ac @@ -1620,8 +1620,6 @@ PGAC_UNION_SEMUN AC_CHECK_TYPES(socklen_t, [], [], [#include <sys/socket.h>]) PGAC_STRUCT_SOCKADDR_SA_LEN -PGAC_TYPE_LOCALE_T - # MSVC doesn't cope well with defining restrict to __restrict, the # spelling it understands, because it conflicts with # __declspec(restrict). Therefore we define pg_restrict to the @@ -1720,7 +1718,6 @@ fi ## PGAC_VAR_INT_TIMEZONE -PGAC_FUNC_WCSTOMBS_L # Some versions of libedit contain strlcpy(), setproctitle(), and other # symbols that that library has no business exposing to the world. Pending @@ -1744,6 +1741,8 @@ AC_CHECK_FUNCS(m4_normalize([ pthread_is_threaded_np setproctitle setproctitle_fast + snprintf_l + strtod_l strchrnul strsignal syncfs @@ -1833,7 +1832,6 @@ fi # Win32 (really MinGW) support if test "$PORTNAME" = "win32"; then - AC_CHECK_FUNCS(_configthreadlocale) AC_LIBOBJ(dirmod) AC_LIBOBJ(kill) AC_LIBOBJ(open) diff --git a/meson.build b/meson.build index cc176f11b5..932334ed63 100644 --- a/meson.build +++ b/meson.build @@ -2407,6 +2407,7 @@ header_checks = [ 'sys/ucred.h', 'termios.h', 'ucred.h', + 'xlocale.h', ] foreach header : header_checks @@ -2550,15 +2551,6 @@ else cdata.set('STRERROR_R_INT', false) endif -# Find the right header file for the locale_t type. macOS needs xlocale.h; -# standard is locale.h, but glibc <= 2.25 also had an xlocale.h file that -# we should not use so we check the standard header first. MSVC has a -# replacement defined in src/include/port/win32_port.h. -if not cc.has_type('locale_t', prefix: '#include <locale.h>') and \ - cc.has_type('locale_t', prefix: '#include <xlocale.h>') - cdata.set('LOCALE_T_IN_XLOCALE', 1) -endif - # Check if the C compiler understands typeof or a variant. Define # HAVE_TYPEOF if so, and define 'typeof' to the actual key word. foreach kw : ['typeof', '__typeof__', 'decltype'] @@ -2583,30 +2575,6 @@ int main(void) endif endforeach - -# Try to find a declaration for wcstombs_l(). It might be in stdlib.h -# (following the POSIX requirement for wcstombs()), or in locale.h, or in -# xlocale.h. If it's in the latter, define WCSTOMBS_L_IN_XLOCALE. -wcstombs_l_test = ''' -#include <stdlib.h> -#include <locale.h> -@0@ - -void main(void) -{ -#ifndef wcstombs_l - (void) wcstombs_l; -#endif -} -''' -if (not cc.compiles(wcstombs_l_test.format(''), - name: 'wcstombs_l') and - cc.compiles(wcstombs_l_test.format('#include <xlocale.h>'), - name: 'wcstombs_l in xlocale.h')) - cdata.set('WCSTOMBS_L_IN_XLOCALE', 1) -endif - - # MSVC doesn't cope well with defining restrict to __restrict, the spelling it # understands, because it conflicts with __declspec(restrict). Therefore we # define pg_restrict to the appropriate definition, which presumably won't @@ -2658,7 +2626,6 @@ endif # XXX: Might be worth conditioning some checks on the OS, to avoid doing # unnecessary checks over and over, particularly on windows. func_checks = [ - ['_configthreadlocale', {'skip': host_system != 'windows'}], ['backtrace_symbols', {'dependencies': [execinfo_dep]}], ['clock_gettime', {'dependencies': [rt_dep], 'define': false}], ['copyfile'], @@ -2690,6 +2657,8 @@ func_checks = [ ['shm_open', {'dependencies': [rt_dep], 'define': false}], ['shm_unlink', {'dependencies': [rt_dep], 'define': false}], ['shmget', {'dependencies': [cygipc_dep], 'define': false}], + ['snprintf_l'], + ['strtod_l'], ['socket', {'dependencies': [socket_dep], 'define': false}], ['strchrnul'], ['strerror_r', {'dependencies': [thread_dep]}], diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index 0e9b108e66..c0586edab1 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -376,6 +376,9 @@ /* Define to 1 if you have the `setproctitle_fast' function. */ #undef HAVE_SETPROCTITLE_FAST +/* Define to 1 if you have the `snprintf_l' function. */ +#undef HAVE_SNPRINTF_L + /* Define to 1 if the system has the type `socklen_t'. */ #undef HAVE_SOCKLEN_T @@ -421,6 +424,9 @@ /* Define to 1 if you have the `strsignal' function. */ #undef HAVE_STRSIGNAL +/* Define to 1 if you have the `strtod_l' function. */ +#undef HAVE_STRTOD_L + /* Define to 1 if the system has the type `struct option'. */ #undef HAVE_STRUCT_OPTION @@ -556,9 +562,6 @@ /* Define to 1 if your compiler understands __builtin_unreachable. */ #undef HAVE__BUILTIN_UNREACHABLE -/* Define to 1 if you have the `_configthreadlocale' function. */ -#undef HAVE__CONFIGTHREADLOCALE - /* Define to 1 if you have __cpuid. */ #undef HAVE__CPUID @@ -577,9 +580,6 @@ /* Define to the appropriate printf length modifier for 64-bit ints. */ #undef INT64_MODIFIER -/* Define to 1 if `locale_t' requires <xlocale.h>. */ -#undef LOCALE_T_IN_XLOCALE - /* Define as the maximum alignment requirement of any C data type. */ #undef MAXIMUM_ALIGNOF @@ -766,9 +766,6 @@ /* Define to 1 to build with ZSTD support. (--with-zstd) */ #undef USE_ZSTD -/* Define to 1 if `wcstombs_l' requires <xlocale.h>. */ -#undef WCSTOMBS_L_IN_XLOCALE - /* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most significant byte first (like Motorola and SPARC, unlike Intel). */ #if defined AC_APPLE_UNIVERSAL_BUILD diff --git a/src/include/port.h b/src/include/port.h index c740005267..26a78320f8 100644 --- a/src/include/port.h +++ b/src/include/port.h @@ -15,6 +15,11 @@ #include <ctype.h> +#include <locale.h> +#ifdef HAVE_XLOCALE_H +#include <xlocale.h> +#endif + /* * Windows has enough specialized port stuff that we push most of it off * into another file. @@ -217,6 +222,30 @@ extern int pg_fprintf(FILE *stream, const char *fmt,...) pg_attribute_printf(2, extern int pg_vprintf(const char *fmt, va_list args) pg_attribute_printf(1, 0); extern int pg_printf(const char *fmt,...) pg_attribute_printf(1, 2); +/* Acessor for a thread-safe process-lifetime "C" locale. */ +extern locale_t pg_get_c_locale(void); + +#ifndef HAVE_STRTOD_L +/* + * POSIX doesn't define this function, but we can implement it with thread-safe + * save-and-restore. Not all systems have uselocale(), but the ones that don't + * have strtod_l(). + */ +static inline double +strtod_l(const char *nptr, char **endptr, locale_t loc) +{ +#ifdef WIN32 + return _strtod_l(nptr, endptr, loc); +#else + locale_t save = uselocale(loc); + double result = strtod(nptr, endptr); + + uselocale(save); + return result; +#endif +} +#endif + #ifndef WIN32 /* * We add a pg_ prefix as a warning that the Windows implementations have the diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h index f41d33975b..9651f3c3ab 100644 --- a/src/include/utils/pg_locale.h +++ b/src/include/utils/pg_locale.h @@ -12,7 +12,7 @@ #ifndef _PG_LOCALE_ #define _PG_LOCALE_ -#if defined(LOCALE_T_IN_XLOCALE) || defined(WCSTOMBS_L_IN_XLOCALE) +#ifdef HAVE_XLOCALE_H #include <xlocale.h> #endif #ifdef USE_ICU diff --git a/src/interfaces/ecpg/ecpglib/connect.c b/src/interfaces/ecpg/ecpglib/connect.c index 8afb1f0a26..eac1a2e9b5 100644 --- a/src/interfaces/ecpg/ecpglib/connect.c +++ b/src/interfaces/ecpg/ecpglib/connect.c @@ -10,10 +10,6 @@ #include "ecpgtype.h" #include "sqlca.h" -#ifdef HAVE_USELOCALE -locale_t ecpg_clocale = (locale_t) 0; -#endif - static pthread_mutex_t connections_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_key_t actual_connection_key; static pthread_once_t actual_connection_key_once = PTHREAD_ONCE_INIT; @@ -483,39 +479,6 @@ ECPGconnect(int lineno, int c, const char *name, const char *user, const char *p /* add connection to our list */ pthread_mutex_lock(&connections_mutex); - /* - * ... but first, make certain we have created ecpg_clocale. Rely on - * holding connections_mutex to ensure this is done by only one thread. - */ -#ifdef HAVE_USELOCALE - if (!ecpg_clocale) - { - ecpg_clocale = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0); - if (!ecpg_clocale) - { - pthread_mutex_unlock(&connections_mutex); - 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/data.c b/src/interfaces/ecpg/ecpglib/data.c index fa56276758..228df49052 100644 --- a/src/interfaces/ecpg/ecpglib/data.c +++ b/src/interfaces/ecpg/ecpglib/data.c @@ -466,7 +466,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno, pval++; if (!check_special_value(pval, &dres, &scan_length)) - dres = strtod(pval, &scan_length); + dres = strtod_l(pval, &scan_length, pg_get_c_locale()); if (isarray && *scan_length == '"') scan_length++; diff --git a/src/interfaces/ecpg/ecpglib/descriptor.c b/src/interfaces/ecpg/ecpglib/descriptor.c index ad279e245c..855322e037 100644 --- a/src/interfaces/ecpg/ecpglib/descriptor.c +++ b/src/interfaces/ecpg/ecpglib/descriptor.c @@ -475,46 +475,9 @@ ECPGget_desc(int lineno, const char *desc_name, int index,...) memset(&stmt, 0, sizeof stmt); stmt.lineno = lineno; - /* Make sure we do NOT honor the locale for numeric input */ - /* since the database gives the standard decimal point */ - /* (see comments in execute.c) */ -#ifdef HAVE_USELOCALE - - /* - * 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); -#endif - stmt.oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno); - setlocale(LC_NUMERIC, "C"); -#endif - /* desperate try to guess something sensible */ stmt.connection = ecpg_get_connection(NULL); ecpg_store_result(ECPGresult, index, &stmt, &data_var); - -#ifdef HAVE_USELOCALE - if (stmt.oldlocale != (locale_t) 0) - uselocale(stmt.oldlocale); -#else - if (stmt.oldlocale) - { - setlocale(LC_NUMERIC, stmt.oldlocale); - ecpg_free(stmt.oldlocale); - } -#ifdef HAVE__CONFIGTHREADLOCALE - if (stmt.oldthreadlocale != -1) - (void) _configthreadlocale(stmt.oldthreadlocale); -#endif -#endif } else if (data_var.ind_type != ECPGt_NO_INDICATOR && data_var.ind_pointer != NULL) diff --git a/src/interfaces/ecpg/ecpglib/ecpglib_extern.h b/src/interfaces/ecpg/ecpglib/ecpglib_extern.h index 01b4309a71..40988d5357 100644 --- a/src/interfaces/ecpg/ecpglib/ecpglib_extern.h +++ b/src/interfaces/ecpg/ecpglib/ecpglib_extern.h @@ -13,9 +13,6 @@ #ifndef CHAR_BIT #include <limits.h> #endif -#ifdef LOCALE_T_IN_XLOCALE -#include <xlocale.h> -#endif enum COMPAT_MODE { @@ -59,10 +56,6 @@ 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 { @@ -76,14 +69,6 @@ struct statement bool questionmarks; struct variable *inlist; struct variable *outlist; -#ifdef HAVE_USELOCALE - locale_t oldlocale; -#else - char *oldlocale; -#ifdef HAVE__CONFIGTHREADLOCALE - int oldthreadlocale; -#endif -#endif int nparams; char **paramvalues; int *paramlengths; diff --git a/src/interfaces/ecpg/ecpglib/execute.c b/src/interfaces/ecpg/ecpglib/execute.c index 04d0b40c53..806d4779fa 100644 --- a/src/interfaces/ecpg/ecpglib/execute.c +++ b/src/interfaces/ecpg/ecpglib/execute.c @@ -101,9 +101,6 @@ free_statement(struct statement *stmt) free_variable(stmt->outlist); ecpg_free(stmt->command); ecpg_free(stmt->name); -#ifndef HAVE_USELOCALE - ecpg_free(stmt->oldlocale); -#endif ecpg_free(stmt); } @@ -1973,40 +1970,6 @@ ecpg_do_prologue(int lineno, const int compat, const int force_indicator, if (stmt == NULL) return false; - /* - * Make sure we do NOT honor the locale for numeric input/output since the - * database wants the standard decimal point. If available, use - * uselocale() for this because it's thread-safe. Windows doesn't have - * that, but it usually does have _configthreadlocale(). In some versions - * of MinGW, _configthreadlocale() exists but always returns -1 --- so - * treat that situation as if the function doesn't exist. - */ -#ifdef HAVE_USELOCALE - - /* - * 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); - return false; - } -#else -#ifdef HAVE__CONFIGTHREADLOCALE - stmt->oldthreadlocale = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); -#endif - stmt->oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno); - if (stmt->oldlocale == NULL) - { - ecpg_do_epilogue(stmt); - return false; - } - setlocale(LC_NUMERIC, "C"); -#endif - /* * If statement type is ECPGst_prepnormal we are supposed to prepare the * statement before executing them @@ -2213,24 +2176,6 @@ ecpg_do_epilogue(struct statement *stmt) if (stmt == NULL) return; -#ifdef HAVE_USELOCALE - if (stmt->oldlocale != (locale_t) 0) - uselocale(stmt->oldlocale); -#else - if (stmt->oldlocale) - setlocale(LC_NUMERIC, stmt->oldlocale); -#ifdef HAVE__CONFIGTHREADLOCALE - - /* - * This is a bit trickier than it looks: if we failed partway through - * statement initialization, oldthreadlocale could still be 0. But that's - * okay because a call with 0 is defined to be a no-op. - */ - if (stmt->oldthreadlocale != -1) - (void) _configthreadlocale(stmt->oldthreadlocale); -#endif -#endif - free_statement(stmt); } diff --git a/src/interfaces/ecpg/pgtypeslib/dt_common.c b/src/interfaces/ecpg/pgtypeslib/dt_common.c index ed08088bfe..6aff989e0f 100644 --- a/src/interfaces/ecpg/pgtypeslib/dt_common.c +++ b/src/interfaces/ecpg/pgtypeslib/dt_common.c @@ -1216,7 +1216,7 @@ DecodeNumber(int flen, char *str, int fmask, return DecodeNumberField(flen, str, (fmask | DTK_DATE_M), tmask, tm, fsec, is2digits); - *fsec = strtod(cp, &cp); + *fsec = strtod_l(cp, &cp, pg_get_c_locale()); if (*cp != '\0') return -1; } @@ -2028,7 +2028,7 @@ DecodeDateTime(char **field, int *ftype, int nf, { double frac; - frac = strtod(cp, &cp); + frac = strtod_l(cp, &cp, pg_get_c_locale()); if (*cp != '\0') return -1; *fsec = frac * 1000000; @@ -2052,7 +2052,7 @@ DecodeDateTime(char **field, int *ftype, int nf, { double time; - time = strtod(cp, &cp); + time = strtod_l(cp, &cp, pg_get_c_locale()); if (*cp != '\0') return -1; diff --git a/src/interfaces/ecpg/pgtypeslib/interval.c b/src/interfaces/ecpg/pgtypeslib/interval.c index 936a688381..8fc34acf68 100644 --- a/src/interfaces/ecpg/pgtypeslib/interval.c +++ b/src/interfaces/ecpg/pgtypeslib/interval.c @@ -60,7 +60,7 @@ ParseISO8601Number(const char *str, char **endptr, int *ipart, double *fpart) if (!(isdigit((unsigned char) *str) || *str == '-' || *str == '.')) return DTERR_BAD_FORMAT; errno = 0; - val = strtod(str, endptr); + val = strtod_l(str, endptr, pg_get_c_locale()); /* did we not see anything that looks like a double? */ if (*endptr == str || errno != 0) return DTERR_BAD_FORMAT; @@ -455,7 +455,7 @@ DecodeInterval(char **field, int *ftype, int nf, /* int range, */ else if (*cp == '.') { errno = 0; - fval = strtod(cp, &cp); + fval = strtod_l(cp, &cp, pg_get_c_locale()); if (*cp != '\0' || errno != 0) return DTERR_BAD_FORMAT; diff --git a/src/interfaces/ecpg/pgtypeslib/numeric.c b/src/interfaces/ecpg/pgtypeslib/numeric.c index 35e7b92da4..f53fdb9f10 100644 --- a/src/interfaces/ecpg/pgtypeslib/numeric.c +++ b/src/interfaces/ecpg/pgtypeslib/numeric.c @@ -1455,7 +1455,7 @@ numericvar_to_double(numeric *var, double *dp) * strtod does not reset errno to 0 in case of success. */ errno = 0; - val = strtod(tmp, &endptr); + val = strtod_l(tmp, &endptr, pg_get_c_locale()); if (errno == ERANGE) { free(tmp); diff --git a/src/port/Makefile b/src/port/Makefile index db7c02117b..03b55bd26b 100644 --- a/src/port/Makefile +++ b/src/port/Makefile @@ -42,6 +42,7 @@ OBJS = \ bsearch_arg.o \ chklocale.o \ inet_net_ntop.o \ + locale.o \ noblock.o \ path.o \ pg_bitutils.o \ diff --git a/src/port/locale.c b/src/port/locale.c new file mode 100644 index 0000000000..ca463257ef --- /dev/null +++ b/src/port/locale.c @@ -0,0 +1,73 @@ +/*------------------------------------------------------------------------- + * + * locale.c + * Helper routines for thread-safe system locale usage. + * + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/port/locale.c + * + *------------------------------------------------------------------------- + */ + +#include "c.h" + +#include <locale.h> +#ifdef HAVE_XLOCALE_H +#include <xlocale.h> +#endif + +#ifndef WIN32 +#include <pthread.h> +#else +#include <synchapi.h> +#endif + +static locale_t c_locale; + +#ifndef WIN32 +static void +init_c_locale_once(void) +{ + c_locale = newlocale(LC_ALL, "C", NULL); +} +#else +static BOOL +init_c_locale_once(PINIT_ONCE once, PVOID parameter, PVOID *context) +{ + c_locale = _create_locale(LC_ALL, "C"); + return true; +} +#endif + +locale_t +pg_get_c_locale(void) +{ + /* + * Fast path if already initialized. This assumes that we can read a + * locale_t (in practice, a pointer) without tearing in a multi-threaded + * program. + */ + if (c_locale != (locale_t) 0) + return c_locale; + + /* Make a locale_t. It will live until process exit. */ + { +#ifndef WIN32 + static pthread_once_t once = PTHREAD_ONCE_INIT; + + pthread_once(&once, init_c_locale_once); +#else + static INIT_ONCE once; + InitOnceExecuteOnce(&once, init_c_locale_once, NULL, NULL); +#endif + } + + Assert(c_locale != (locale_t) 0); + + return c_locale; +} diff --git a/src/port/meson.build b/src/port/meson.build index ff54b7b53e..e21961fd73 100644 --- a/src/port/meson.build +++ b/src/port/meson.build @@ -5,6 +5,7 @@ pgport_sources = [ 'chklocale.c', 'inet_net_ntop.c', 'noblock.c', + 'locale.c', 'path.c', 'pg_bitutils.c', 'pg_strong_random.c', diff --git a/src/port/snprintf.c b/src/port/snprintf.c index 884f0262dd..c122e0deb7 100644 --- a/src/port/snprintf.c +++ b/src/port/snprintf.c @@ -35,6 +35,7 @@ #include <math.h> + /* * We used to use the platform's NL_ARGMAX here, but that's a bad idea, * first because the point of this module is to remove platform dependencies @@ -109,6 +110,15 @@ #undef vprintf #undef printf +#ifdef WIN32 +/* + * This is only good enough to claim support for the usage we make in this + * module, so keep this claim of HAVE_SNPRINTF_L local. + */ +#define snprintf_l(str, size, loc, format, ...) _snprintf_l(str, size, format, loc, __VA_ARGS__) +#define HAVE_SNPRINTF_L +#endif + /* * Info about where the formatted output is going. * @@ -1184,6 +1194,9 @@ fmtfloat(double value, char type, int forcesign, int leftjust, * according to == but not according to memcmp. */ static const double dzero = 0.0; +#ifndef HAVE_SNPRINTF_L + locale_t save_locale = uselocale(pg_get_c_locale()); +#endif if (adjust_sign((value < 0.0 || (value == 0.0 && @@ -1205,7 +1218,11 @@ fmtfloat(double value, char type, int forcesign, int leftjust, fmt[2] = '*'; fmt[3] = type; fmt[4] = '\0'; +#ifdef HAVE_SNPRINTF_L + vallen = snprintf_l(convert, sizeof(convert), pg_get_c_locale(), fmt, prec, value); +#else vallen = snprintf(convert, sizeof(convert), fmt, prec, value); +#endif } else { @@ -1214,6 +1231,11 @@ fmtfloat(double value, char type, int forcesign, int leftjust, fmt[2] = '\0'; vallen = snprintf(convert, sizeof(convert), fmt, value); } + +#ifndef HAVE_SNPRINTF_L + uselocale(save_locale); +#endif + if (vallen < 0) goto fail; @@ -1336,12 +1358,23 @@ pg_strfromd(char *str, size_t count, int precision, double value) } else { +#ifndef HAVE_SNPRINTF_L + locale_t save_locale; +#endif + fmt[0] = '%'; fmt[1] = '.'; fmt[2] = '*'; fmt[3] = 'g'; fmt[4] = '\0'; +#ifdef HAVE_SNPRINTF_L + vallen = snprintf_l(convert, sizeof(convert), pg_get_c_locale(), fmt, precision, value); +#else + save_locale = uselocale(pg_get_c_locale()); vallen = snprintf(convert, sizeof(convert), fmt, precision, value); + uselocale(save_locale); +#endif + if (vallen < 0) { target.failed = true; -- 2.39.2 ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2024-08-10 03:48 Thomas Munro <[email protected]> parent: Thomas Munro <[email protected]> 0 siblings, 1 reply; 41+ messages in thread From: Thomas Munro @ 2024-08-10 03:48 UTC (permalink / raw) To: Tom Lane <[email protected]>; +Cc: Tristan Partin <[email protected]>; pgsql-hackers On Sat, Aug 10, 2024 at 1:29 PM Thomas Munro <[email protected]> wrote: > Here is a new attempt at this can of portability worms. Slightly better version: * it's OK to keep relying on the global locale in the backend; for now, we know that LC_NUMERIC is set in main(), and in the multi-threaded future calling setlocale() even transiently will be banned, so it seems it'll be OK to just keep doing that, right? * we could use LC_C_LOCALE to get a "C" locale slightly more efficiently on those; we could define it ourselves for other systems, using pg_get_c_locale() Attachments: [application/octet-stream] v3-0001-Improve-locale-thread-safety-of-ECPG.patch (30.5K, ../../CA+hUKGLs9ccA7pUfphZOuf_fcVq2FAai7Cm0pmbQk0Qcs4VoVQ@mail.gmail.com/2-v3-0001-Improve-locale-thread-safety-of-ECPG.patch) download | inline diff: From bf222d4a1261daccf499c24ba786b836092769ed Mon Sep 17 00:00:00 2001 From: Thomas Munro <[email protected]> Date: Sat, 10 Aug 2024 11:01:21 +1200 Subject: [PATCH v3] Improve locale thread safety of ECPG. Remove the use of setlocale() as a fallback strategy on systems that don't have uselocale(), where ECPG tries to control LC_NUMERIC formatting on input and output of floating point numbers. Instead, request the C locale explicitly. Use the special locale_t value LC_C_LOCALE, which is a non-standard extension in macOS and NetBSD, and provide our own fallback implementation for every other system that will allocate a reusable locale_t for the lifetime of the process. Then: 1. Use strtod_l() for parsing, and supply an implementation using uselocale() if it is missing. 2. Inside our own snprintf.c, when used in frontend code, use snprintf_l() or _snprintf_l() where we punt floating point numbers to the system snprintf(), or wrap it in uselocale() if those are not available. Since the non-standard _l() functions require <xlocale.h> on *BSD/macOS systems, simplify the configure probes: instead of XXX_IN_XLOCALE_H for several features XXX, let's just include <xlocale.h> if HAVE_XLOCALE_H. The reason for the extra complication was apparently that some old glibc systems also had an <xlocale.h>, and you weren't supposed to include it directly, but it's gone now (as far as I can tell it was harmless to do so anyway). Discussion: https://postgr.es/m/CWZBBRR6YA8D.8EHMDRGLCKCD%40neon.tech --- config/c-library.m4 | 55 --------- configure | 115 +------------------ configure.ac | 6 +- meson.build | 37 +----- src/include/pg_config.h.in | 15 +-- src/include/port.h | 37 ++++++ src/include/utils/pg_locale.h | 2 +- src/interfaces/ecpg/ecpglib/connect.c | 37 ------ src/interfaces/ecpg/ecpglib/data.c | 2 +- src/interfaces/ecpg/ecpglib/descriptor.c | 37 ------ src/interfaces/ecpg/ecpglib/ecpglib_extern.h | 15 --- src/interfaces/ecpg/ecpglib/execute.c | 55 --------- src/interfaces/ecpg/pgtypeslib/dt_common.c | 6 +- src/interfaces/ecpg/pgtypeslib/interval.c | 4 +- src/interfaces/ecpg/pgtypeslib/numeric.c | 2 +- src/port/Makefile | 1 + src/port/locale.c | 73 ++++++++++++ src/port/meson.build | 1 + src/port/snprintf.c | 53 +++++++++ 19 files changed, 185 insertions(+), 368 deletions(-) create mode 100644 src/port/locale.c diff --git a/config/c-library.m4 b/config/c-library.m4 index aa8223d2ef0..421bc612b27 100644 --- a/config/c-library.m4 +++ b/config/c-library.m4 @@ -81,58 +81,3 @@ AC_DEFUN([PGAC_STRUCT_SOCKADDR_SA_LEN], [#include <sys/types.h> #include <sys/socket.h> ])])# PGAC_STRUCT_SOCKADDR_MEMBERS - - -# PGAC_TYPE_LOCALE_T -# ------------------ -# Check for the locale_t type and find the right header file. macOS -# needs xlocale.h; standard is locale.h, but glibc <= 2.25 also had an -# xlocale.h file that we should not use, so we check the standard -# header first. -AC_DEFUN([PGAC_TYPE_LOCALE_T], -[AC_CACHE_CHECK([for locale_t], pgac_cv_type_locale_t, -[AC_COMPILE_IFELSE([AC_LANG_PROGRAM( -[#include <locale.h> -locale_t x;], -[])], -[pgac_cv_type_locale_t=yes], -[AC_COMPILE_IFELSE([AC_LANG_PROGRAM( -[#include <xlocale.h> -locale_t x;], -[])], -[pgac_cv_type_locale_t='yes (in xlocale.h)'], -[pgac_cv_type_locale_t=no])])]) -if test "$pgac_cv_type_locale_t" = 'yes (in xlocale.h)'; then - AC_DEFINE(LOCALE_T_IN_XLOCALE, 1, - [Define to 1 if `locale_t' requires <xlocale.h>.]) -fi])# PGAC_TYPE_LOCALE_T - - -# PGAC_FUNC_WCSTOMBS_L -# -------------------- -# Try to find a declaration for wcstombs_l(). It might be in stdlib.h -# (following the POSIX requirement for wcstombs()), or in locale.h, or in -# xlocale.h. If it's in the latter, define WCSTOMBS_L_IN_XLOCALE. -# -AC_DEFUN([PGAC_FUNC_WCSTOMBS_L], -[AC_CACHE_CHECK([for wcstombs_l declaration], pgac_cv_func_wcstombs_l, -[AC_COMPILE_IFELSE([AC_LANG_PROGRAM( -[#include <stdlib.h> -#include <locale.h>], -[#ifndef wcstombs_l -(void) wcstombs_l; -#endif])], -[pgac_cv_func_wcstombs_l='yes'], -[AC_COMPILE_IFELSE([AC_LANG_PROGRAM( -[#include <stdlib.h> -#include <locale.h> -#include <xlocale.h>], -[#ifndef wcstombs_l -(void) wcstombs_l; -#endif])], -[pgac_cv_func_wcstombs_l='yes (in xlocale.h)'], -[pgac_cv_func_wcstombs_l='no'])])]) -if test "$pgac_cv_func_wcstombs_l" = 'yes (in xlocale.h)'; then - AC_DEFINE(WCSTOMBS_L_IN_XLOCALE, 1, - [Define to 1 if `wcstombs_l' requires <xlocale.h>.]) -fi])# PGAC_FUNC_WCSTOMBS_L diff --git a/configure b/configure index 4f3aa447566..298492ec251 100755 --- a/configure +++ b/configure @@ -14635,55 +14635,6 @@ _ACEOF fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for locale_t" >&5 -$as_echo_n "checking for locale_t... " >&6; } -if ${pgac_cv_type_locale_t+:} false; then : - $as_echo_n "(cached) " >&6 -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include <locale.h> -locale_t x; -int -main () -{ - - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - pgac_cv_type_locale_t=yes -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include <xlocale.h> -locale_t x; -int -main () -{ - - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - pgac_cv_type_locale_t='yes (in xlocale.h)' -else - pgac_cv_type_locale_t=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv_type_locale_t" >&5 -$as_echo "$pgac_cv_type_locale_t" >&6; } -if test "$pgac_cv_type_locale_t" = 'yes (in xlocale.h)'; then - -$as_echo "#define LOCALE_T_IN_XLOCALE 1" >>confdefs.h - -fi - # MSVC doesn't cope well with defining restrict to __restrict, the # spelling it understands, because it conflicts with # __declspec(restrict). Therefore we define pg_restrict to the @@ -15170,59 +15121,6 @@ if test x"$pgac_cv_var_int_timezone" = xyes ; then $as_echo "#define HAVE_INT_TIMEZONE 1" >>confdefs.h -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for wcstombs_l declaration" >&5 -$as_echo_n "checking for wcstombs_l declaration... " >&6; } -if ${pgac_cv_func_wcstombs_l+:} false; then : - $as_echo_n "(cached) " >&6 -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include <stdlib.h> -#include <locale.h> -int -main () -{ -#ifndef wcstombs_l -(void) wcstombs_l; -#endif - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - pgac_cv_func_wcstombs_l='yes' -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include <stdlib.h> -#include <locale.h> -#include <xlocale.h> -int -main () -{ -#ifndef wcstombs_l -(void) wcstombs_l; -#endif - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - pgac_cv_func_wcstombs_l='yes (in xlocale.h)' -else - pgac_cv_func_wcstombs_l='no' -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv_func_wcstombs_l" >&5 -$as_echo "$pgac_cv_func_wcstombs_l" >&6; } -if test "$pgac_cv_func_wcstombs_l" = 'yes (in xlocale.h)'; then - -$as_echo "#define WCSTOMBS_L_IN_XLOCALE 1" >>confdefs.h - fi # Some versions of libedit contain strlcpy(), setproctitle(), and other @@ -15232,7 +15130,7 @@ fi LIBS_including_readline="$LIBS" LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'` -for ac_func in backtrace_symbols copyfile copy_file_range getifaddrs getpeerucred inet_pton kqueue mbstowcs_l memset_s posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast strchrnul strsignal syncfs sync_file_range uselocale wcstombs_l +for ac_func in backtrace_symbols copyfile copy_file_range getifaddrs getpeerucred inet_pton kqueue mbstowcs_l memset_s posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast snprintf_l strtod_l strchrnul strsignal syncfs sync_file_range uselocale wcstombs_l do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" @@ -16005,17 +15903,6 @@ fi # Win32 (really MinGW) support if test "$PORTNAME" = "win32"; then - for ac_func in _configthreadlocale -do : - ac_fn_c_check_func "$LINENO" "_configthreadlocale" "ac_cv_func__configthreadlocale" -if test "x$ac_cv_func__configthreadlocale" = xyes; then : - cat >>confdefs.h <<_ACEOF -#define HAVE__CONFIGTHREADLOCALE 1 -_ACEOF - -fi -done - case " $LIBOBJS " in *" dirmod.$ac_objext "* ) ;; *) LIBOBJS="$LIBOBJS dirmod.$ac_objext" diff --git a/configure.ac b/configure.ac index 049bc014911..302aadd427d 100644 --- a/configure.ac +++ b/configure.ac @@ -1620,8 +1620,6 @@ PGAC_UNION_SEMUN AC_CHECK_TYPES(socklen_t, [], [], [#include <sys/socket.h>]) PGAC_STRUCT_SOCKADDR_SA_LEN -PGAC_TYPE_LOCALE_T - # MSVC doesn't cope well with defining restrict to __restrict, the # spelling it understands, because it conflicts with # __declspec(restrict). Therefore we define pg_restrict to the @@ -1720,7 +1718,6 @@ fi ## PGAC_VAR_INT_TIMEZONE -PGAC_FUNC_WCSTOMBS_L # Some versions of libedit contain strlcpy(), setproctitle(), and other # symbols that that library has no business exposing to the world. Pending @@ -1744,6 +1741,8 @@ AC_CHECK_FUNCS(m4_normalize([ pthread_is_threaded_np setproctitle setproctitle_fast + snprintf_l + strtod_l strchrnul strsignal syncfs @@ -1833,7 +1832,6 @@ fi # Win32 (really MinGW) support if test "$PORTNAME" = "win32"; then - AC_CHECK_FUNCS(_configthreadlocale) AC_LIBOBJ(dirmod) AC_LIBOBJ(kill) AC_LIBOBJ(open) diff --git a/meson.build b/meson.build index cc176f11b5d..932334ed634 100644 --- a/meson.build +++ b/meson.build @@ -2407,6 +2407,7 @@ header_checks = [ 'sys/ucred.h', 'termios.h', 'ucred.h', + 'xlocale.h', ] foreach header : header_checks @@ -2550,15 +2551,6 @@ else cdata.set('STRERROR_R_INT', false) endif -# Find the right header file for the locale_t type. macOS needs xlocale.h; -# standard is locale.h, but glibc <= 2.25 also had an xlocale.h file that -# we should not use so we check the standard header first. MSVC has a -# replacement defined in src/include/port/win32_port.h. -if not cc.has_type('locale_t', prefix: '#include <locale.h>') and \ - cc.has_type('locale_t', prefix: '#include <xlocale.h>') - cdata.set('LOCALE_T_IN_XLOCALE', 1) -endif - # Check if the C compiler understands typeof or a variant. Define # HAVE_TYPEOF if so, and define 'typeof' to the actual key word. foreach kw : ['typeof', '__typeof__', 'decltype'] @@ -2583,30 +2575,6 @@ int main(void) endif endforeach - -# Try to find a declaration for wcstombs_l(). It might be in stdlib.h -# (following the POSIX requirement for wcstombs()), or in locale.h, or in -# xlocale.h. If it's in the latter, define WCSTOMBS_L_IN_XLOCALE. -wcstombs_l_test = ''' -#include <stdlib.h> -#include <locale.h> -@0@ - -void main(void) -{ -#ifndef wcstombs_l - (void) wcstombs_l; -#endif -} -''' -if (not cc.compiles(wcstombs_l_test.format(''), - name: 'wcstombs_l') and - cc.compiles(wcstombs_l_test.format('#include <xlocale.h>'), - name: 'wcstombs_l in xlocale.h')) - cdata.set('WCSTOMBS_L_IN_XLOCALE', 1) -endif - - # MSVC doesn't cope well with defining restrict to __restrict, the spelling it # understands, because it conflicts with __declspec(restrict). Therefore we # define pg_restrict to the appropriate definition, which presumably won't @@ -2658,7 +2626,6 @@ endif # XXX: Might be worth conditioning some checks on the OS, to avoid doing # unnecessary checks over and over, particularly on windows. func_checks = [ - ['_configthreadlocale', {'skip': host_system != 'windows'}], ['backtrace_symbols', {'dependencies': [execinfo_dep]}], ['clock_gettime', {'dependencies': [rt_dep], 'define': false}], ['copyfile'], @@ -2690,6 +2657,8 @@ func_checks = [ ['shm_open', {'dependencies': [rt_dep], 'define': false}], ['shm_unlink', {'dependencies': [rt_dep], 'define': false}], ['shmget', {'dependencies': [cygipc_dep], 'define': false}], + ['snprintf_l'], + ['strtod_l'], ['socket', {'dependencies': [socket_dep], 'define': false}], ['strchrnul'], ['strerror_r', {'dependencies': [thread_dep]}], diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index 0e9b108e667..c0586edab13 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -376,6 +376,9 @@ /* Define to 1 if you have the `setproctitle_fast' function. */ #undef HAVE_SETPROCTITLE_FAST +/* Define to 1 if you have the `snprintf_l' function. */ +#undef HAVE_SNPRINTF_L + /* Define to 1 if the system has the type `socklen_t'. */ #undef HAVE_SOCKLEN_T @@ -421,6 +424,9 @@ /* Define to 1 if you have the `strsignal' function. */ #undef HAVE_STRSIGNAL +/* Define to 1 if you have the `strtod_l' function. */ +#undef HAVE_STRTOD_L + /* Define to 1 if the system has the type `struct option'. */ #undef HAVE_STRUCT_OPTION @@ -556,9 +562,6 @@ /* Define to 1 if your compiler understands __builtin_unreachable. */ #undef HAVE__BUILTIN_UNREACHABLE -/* Define to 1 if you have the `_configthreadlocale' function. */ -#undef HAVE__CONFIGTHREADLOCALE - /* Define to 1 if you have __cpuid. */ #undef HAVE__CPUID @@ -577,9 +580,6 @@ /* Define to the appropriate printf length modifier for 64-bit ints. */ #undef INT64_MODIFIER -/* Define to 1 if `locale_t' requires <xlocale.h>. */ -#undef LOCALE_T_IN_XLOCALE - /* Define as the maximum alignment requirement of any C data type. */ #undef MAXIMUM_ALIGNOF @@ -766,9 +766,6 @@ /* Define to 1 to build with ZSTD support. (--with-zstd) */ #undef USE_ZSTD -/* Define to 1 if `wcstombs_l' requires <xlocale.h>. */ -#undef WCSTOMBS_L_IN_XLOCALE - /* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most significant byte first (like Motorola and SPARC, unlike Intel). */ #if defined AC_APPLE_UNIVERSAL_BUILD diff --git a/src/include/port.h b/src/include/port.h index c7400052675..5ff464a0609 100644 --- a/src/include/port.h +++ b/src/include/port.h @@ -15,6 +15,11 @@ #include <ctype.h> +#include <locale.h> +#ifdef HAVE_XLOCALE_H +#include <xlocale.h> +#endif + /* * Windows has enough specialized port stuff that we push most of it off * into another file. @@ -217,6 +222,38 @@ extern int pg_fprintf(FILE *stream, const char *fmt,...) pg_attribute_printf(2, extern int pg_vprintf(const char *fmt, va_list args) pg_attribute_printf(1, 0); extern int pg_printf(const char *fmt,...) pg_attribute_printf(1, 2); +/* Accessor for a thread-safe process-lifetime "C" locale. */ +extern locale_t pg_get_c_locale(void); + +/* + * A couple of systems offer a fast way to access a "C" locale. We can use + * that name in our tree, but fall back to the above function. + */ +#ifndef LC_C_LOCALE +#define LC_C_LOCALE pg_get_c_locale() +#endif + +#ifndef HAVE_STRTOD_L +/* + * POSIX doesn't define this function, but we can implement it with thread-safe + * save-and-restore. Not all systems have uselocale(), but the ones that don't + * have strtod_l(). + */ +static inline double +strtod_l(const char *nptr, char **endptr, locale_t loc) +{ +#ifdef WIN32 + return _strtod_l(nptr, endptr, loc); +#else + locale_t save = uselocale(loc); + double result = strtod(nptr, endptr); + + uselocale(save); + return result; +#endif +} +#endif + #ifndef WIN32 /* * We add a pg_ prefix as a warning that the Windows implementations have the diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h index f41d33975be..9651f3c3ab5 100644 --- a/src/include/utils/pg_locale.h +++ b/src/include/utils/pg_locale.h @@ -12,7 +12,7 @@ #ifndef _PG_LOCALE_ #define _PG_LOCALE_ -#if defined(LOCALE_T_IN_XLOCALE) || defined(WCSTOMBS_L_IN_XLOCALE) +#ifdef HAVE_XLOCALE_H #include <xlocale.h> #endif #ifdef USE_ICU diff --git a/src/interfaces/ecpg/ecpglib/connect.c b/src/interfaces/ecpg/ecpglib/connect.c index 8afb1f0a26f..eac1a2e9b53 100644 --- a/src/interfaces/ecpg/ecpglib/connect.c +++ b/src/interfaces/ecpg/ecpglib/connect.c @@ -10,10 +10,6 @@ #include "ecpgtype.h" #include "sqlca.h" -#ifdef HAVE_USELOCALE -locale_t ecpg_clocale = (locale_t) 0; -#endif - static pthread_mutex_t connections_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_key_t actual_connection_key; static pthread_once_t actual_connection_key_once = PTHREAD_ONCE_INIT; @@ -483,39 +479,6 @@ ECPGconnect(int lineno, int c, const char *name, const char *user, const char *p /* add connection to our list */ pthread_mutex_lock(&connections_mutex); - /* - * ... but first, make certain we have created ecpg_clocale. Rely on - * holding connections_mutex to ensure this is done by only one thread. - */ -#ifdef HAVE_USELOCALE - if (!ecpg_clocale) - { - ecpg_clocale = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0); - if (!ecpg_clocale) - { - pthread_mutex_unlock(&connections_mutex); - 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/data.c b/src/interfaces/ecpg/ecpglib/data.c index fa562767585..fbbfd0613fd 100644 --- a/src/interfaces/ecpg/ecpglib/data.c +++ b/src/interfaces/ecpg/ecpglib/data.c @@ -466,7 +466,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno, pval++; if (!check_special_value(pval, &dres, &scan_length)) - dres = strtod(pval, &scan_length); + dres = strtod_l(pval, &scan_length, LC_C_LOCALE); if (isarray && *scan_length == '"') scan_length++; diff --git a/src/interfaces/ecpg/ecpglib/descriptor.c b/src/interfaces/ecpg/ecpglib/descriptor.c index ad279e245c4..855322e037f 100644 --- a/src/interfaces/ecpg/ecpglib/descriptor.c +++ b/src/interfaces/ecpg/ecpglib/descriptor.c @@ -475,46 +475,9 @@ ECPGget_desc(int lineno, const char *desc_name, int index,...) memset(&stmt, 0, sizeof stmt); stmt.lineno = lineno; - /* Make sure we do NOT honor the locale for numeric input */ - /* since the database gives the standard decimal point */ - /* (see comments in execute.c) */ -#ifdef HAVE_USELOCALE - - /* - * 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); -#endif - stmt.oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno); - setlocale(LC_NUMERIC, "C"); -#endif - /* desperate try to guess something sensible */ stmt.connection = ecpg_get_connection(NULL); ecpg_store_result(ECPGresult, index, &stmt, &data_var); - -#ifdef HAVE_USELOCALE - if (stmt.oldlocale != (locale_t) 0) - uselocale(stmt.oldlocale); -#else - if (stmt.oldlocale) - { - setlocale(LC_NUMERIC, stmt.oldlocale); - ecpg_free(stmt.oldlocale); - } -#ifdef HAVE__CONFIGTHREADLOCALE - if (stmt.oldthreadlocale != -1) - (void) _configthreadlocale(stmt.oldthreadlocale); -#endif -#endif } else if (data_var.ind_type != ECPGt_NO_INDICATOR && data_var.ind_pointer != NULL) diff --git a/src/interfaces/ecpg/ecpglib/ecpglib_extern.h b/src/interfaces/ecpg/ecpglib/ecpglib_extern.h index 01b4309a710..40988d53575 100644 --- a/src/interfaces/ecpg/ecpglib/ecpglib_extern.h +++ b/src/interfaces/ecpg/ecpglib/ecpglib_extern.h @@ -13,9 +13,6 @@ #ifndef CHAR_BIT #include <limits.h> #endif -#ifdef LOCALE_T_IN_XLOCALE -#include <xlocale.h> -#endif enum COMPAT_MODE { @@ -59,10 +56,6 @@ 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 { @@ -76,14 +69,6 @@ struct statement bool questionmarks; struct variable *inlist; struct variable *outlist; -#ifdef HAVE_USELOCALE - locale_t oldlocale; -#else - char *oldlocale; -#ifdef HAVE__CONFIGTHREADLOCALE - int oldthreadlocale; -#endif -#endif int nparams; char **paramvalues; int *paramlengths; diff --git a/src/interfaces/ecpg/ecpglib/execute.c b/src/interfaces/ecpg/ecpglib/execute.c index 04d0b40c537..806d4779fa9 100644 --- a/src/interfaces/ecpg/ecpglib/execute.c +++ b/src/interfaces/ecpg/ecpglib/execute.c @@ -101,9 +101,6 @@ free_statement(struct statement *stmt) free_variable(stmt->outlist); ecpg_free(stmt->command); ecpg_free(stmt->name); -#ifndef HAVE_USELOCALE - ecpg_free(stmt->oldlocale); -#endif ecpg_free(stmt); } @@ -1973,40 +1970,6 @@ ecpg_do_prologue(int lineno, const int compat, const int force_indicator, if (stmt == NULL) return false; - /* - * Make sure we do NOT honor the locale for numeric input/output since the - * database wants the standard decimal point. If available, use - * uselocale() for this because it's thread-safe. Windows doesn't have - * that, but it usually does have _configthreadlocale(). In some versions - * of MinGW, _configthreadlocale() exists but always returns -1 --- so - * treat that situation as if the function doesn't exist. - */ -#ifdef HAVE_USELOCALE - - /* - * 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); - return false; - } -#else -#ifdef HAVE__CONFIGTHREADLOCALE - stmt->oldthreadlocale = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); -#endif - stmt->oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno); - if (stmt->oldlocale == NULL) - { - ecpg_do_epilogue(stmt); - return false; - } - setlocale(LC_NUMERIC, "C"); -#endif - /* * If statement type is ECPGst_prepnormal we are supposed to prepare the * statement before executing them @@ -2213,24 +2176,6 @@ ecpg_do_epilogue(struct statement *stmt) if (stmt == NULL) return; -#ifdef HAVE_USELOCALE - if (stmt->oldlocale != (locale_t) 0) - uselocale(stmt->oldlocale); -#else - if (stmt->oldlocale) - setlocale(LC_NUMERIC, stmt->oldlocale); -#ifdef HAVE__CONFIGTHREADLOCALE - - /* - * This is a bit trickier than it looks: if we failed partway through - * statement initialization, oldthreadlocale could still be 0. But that's - * okay because a call with 0 is defined to be a no-op. - */ - if (stmt->oldthreadlocale != -1) - (void) _configthreadlocale(stmt->oldthreadlocale); -#endif -#endif - free_statement(stmt); } diff --git a/src/interfaces/ecpg/pgtypeslib/dt_common.c b/src/interfaces/ecpg/pgtypeslib/dt_common.c index ed08088bfe1..f1f57f15a5e 100644 --- a/src/interfaces/ecpg/pgtypeslib/dt_common.c +++ b/src/interfaces/ecpg/pgtypeslib/dt_common.c @@ -1216,7 +1216,7 @@ DecodeNumber(int flen, char *str, int fmask, return DecodeNumberField(flen, str, (fmask | DTK_DATE_M), tmask, tm, fsec, is2digits); - *fsec = strtod(cp, &cp); + *fsec = strtod_l(cp, &cp, LC_C_LOCALE); if (*cp != '\0') return -1; } @@ -2028,7 +2028,7 @@ DecodeDateTime(char **field, int *ftype, int nf, { double frac; - frac = strtod(cp, &cp); + frac = strtod_l(cp, &cp, LC_C_LOCALE); if (*cp != '\0') return -1; *fsec = frac * 1000000; @@ -2052,7 +2052,7 @@ DecodeDateTime(char **field, int *ftype, int nf, { double time; - time = strtod(cp, &cp); + time = strtod_l(cp, &cp, LC_C_LOCALE); if (*cp != '\0') return -1; diff --git a/src/interfaces/ecpg/pgtypeslib/interval.c b/src/interfaces/ecpg/pgtypeslib/interval.c index 936a6883816..4d4d9a8f045 100644 --- a/src/interfaces/ecpg/pgtypeslib/interval.c +++ b/src/interfaces/ecpg/pgtypeslib/interval.c @@ -60,7 +60,7 @@ ParseISO8601Number(const char *str, char **endptr, int *ipart, double *fpart) if (!(isdigit((unsigned char) *str) || *str == '-' || *str == '.')) return DTERR_BAD_FORMAT; errno = 0; - val = strtod(str, endptr); + val = strtod_l(str, endptr, LC_C_LOCALE); /* did we not see anything that looks like a double? */ if (*endptr == str || errno != 0) return DTERR_BAD_FORMAT; @@ -455,7 +455,7 @@ DecodeInterval(char **field, int *ftype, int nf, /* int range, */ else if (*cp == '.') { errno = 0; - fval = strtod(cp, &cp); + fval = strtod_l(cp, &cp, LC_C_LOCALE); if (*cp != '\0' || errno != 0) return DTERR_BAD_FORMAT; diff --git a/src/interfaces/ecpg/pgtypeslib/numeric.c b/src/interfaces/ecpg/pgtypeslib/numeric.c index 35e7b92da40..4d39fb6c409 100644 --- a/src/interfaces/ecpg/pgtypeslib/numeric.c +++ b/src/interfaces/ecpg/pgtypeslib/numeric.c @@ -1455,7 +1455,7 @@ numericvar_to_double(numeric *var, double *dp) * strtod does not reset errno to 0 in case of success. */ errno = 0; - val = strtod(tmp, &endptr); + val = strtod_l(tmp, &endptr, LC_C_LOCALE); if (errno == ERANGE) { free(tmp); diff --git a/src/port/Makefile b/src/port/Makefile index db7c02117b0..03b55bd26b0 100644 --- a/src/port/Makefile +++ b/src/port/Makefile @@ -42,6 +42,7 @@ OBJS = \ bsearch_arg.o \ chklocale.o \ inet_net_ntop.o \ + locale.o \ noblock.o \ path.o \ pg_bitutils.o \ diff --git a/src/port/locale.c b/src/port/locale.c new file mode 100644 index 00000000000..ca463257ef6 --- /dev/null +++ b/src/port/locale.c @@ -0,0 +1,73 @@ +/*------------------------------------------------------------------------- + * + * locale.c + * Helper routines for thread-safe system locale usage. + * + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/port/locale.c + * + *------------------------------------------------------------------------- + */ + +#include "c.h" + +#include <locale.h> +#ifdef HAVE_XLOCALE_H +#include <xlocale.h> +#endif + +#ifndef WIN32 +#include <pthread.h> +#else +#include <synchapi.h> +#endif + +static locale_t c_locale; + +#ifndef WIN32 +static void +init_c_locale_once(void) +{ + c_locale = newlocale(LC_ALL, "C", NULL); +} +#else +static BOOL +init_c_locale_once(PINIT_ONCE once, PVOID parameter, PVOID *context) +{ + c_locale = _create_locale(LC_ALL, "C"); + return true; +} +#endif + +locale_t +pg_get_c_locale(void) +{ + /* + * Fast path if already initialized. This assumes that we can read a + * locale_t (in practice, a pointer) without tearing in a multi-threaded + * program. + */ + if (c_locale != (locale_t) 0) + return c_locale; + + /* Make a locale_t. It will live until process exit. */ + { +#ifndef WIN32 + static pthread_once_t once = PTHREAD_ONCE_INIT; + + pthread_once(&once, init_c_locale_once); +#else + static INIT_ONCE once; + InitOnceExecuteOnce(&once, init_c_locale_once, NULL, NULL); +#endif + } + + Assert(c_locale != (locale_t) 0); + + return c_locale; +} diff --git a/src/port/meson.build b/src/port/meson.build index ff54b7b53e9..e21961fd739 100644 --- a/src/port/meson.build +++ b/src/port/meson.build @@ -5,6 +5,7 @@ pgport_sources = [ 'chklocale.c', 'inet_net_ntop.c', 'noblock.c', + 'locale.c', 'path.c', 'pg_bitutils.c', 'pg_strong_random.c', diff --git a/src/port/snprintf.c b/src/port/snprintf.c index 884f0262dd1..fce619eb673 100644 --- a/src/port/snprintf.c +++ b/src/port/snprintf.c @@ -109,6 +109,34 @@ #undef vprintf #undef printf +#if defined(FRONTEND) +/* + * Frontend code might be multi-threaded. When calling the system snprintf() + * for floats, we have to use either the snprintf_l() variant, or + * save-and-restore the calling thread's locale using uselocale(). + */ +#if defined(HAVE_SNPRINTF_L) +/* At least macOS and the BSDs have the snprintf_l() extension. */ +#define USE_SNPRINTF_L +#elif defined(WIN32) +/* Windows has a version with a different name and argument order. */ +#define snprintf_l(str, size, loc, format, ...) _snprintf_l(str, size, format, loc, __VA_ARGS__) +#define USE_SNPRINTF_L +#else +/* We have to do a thread-safe save-and-restore around snprintf(). */ +#define USE_USE_LOCALE +#endif +#else +/* + * Backend code doesn't need to worry about the locales, because LC_NUMERIC is + * set to "C" in main() and doesn't change. Plain old snprintf() is always OK. + * + * XXX If the backend were multithreaded, it would have to be 100% certain that + * no one is calling setlocale() concurrently, even transiently, to be able to + * keep this small optimization. + */ +#endif + /* * Info about where the formatted output is going. * @@ -1184,6 +1212,9 @@ fmtfloat(double value, char type, int forcesign, int leftjust, * according to == but not according to memcmp. */ static const double dzero = 0.0; +#ifdef NEED_USE_LOCALE + locale_t save_locale = uselocale(LC_C_LOCALE); +#endif if (adjust_sign((value < 0.0 || (value == 0.0 && @@ -1205,7 +1236,11 @@ fmtfloat(double value, char type, int forcesign, int leftjust, fmt[2] = '*'; fmt[3] = type; fmt[4] = '\0'; +#ifdef USE_SNPRINTF_L + vallen = snprintf_l(convert, sizeof(convert), LC_C_LOCALE, fmt, prec, value); +#else vallen = snprintf(convert, sizeof(convert), fmt, prec, value); +#endif } else { @@ -1214,6 +1249,11 @@ fmtfloat(double value, char type, int forcesign, int leftjust, fmt[2] = '\0'; vallen = snprintf(convert, sizeof(convert), fmt, value); } + +#ifdef NEED_USE_LOCALE + uselocale(save_locale); +#endif + if (vallen < 0) goto fail; @@ -1336,12 +1376,25 @@ pg_strfromd(char *str, size_t count, int precision, double value) } else { +#ifdef NEED_USE_LOCALE + locale_t save_locale = uselocale(LC_C_LOCALE); +#endif + fmt[0] = '%'; fmt[1] = '.'; fmt[2] = '*'; fmt[3] = 'g'; fmt[4] = '\0'; +#ifdef USE_SNPRINTF_L + vallen = snprintf_l(convert, sizeof(convert), LC_C_LOCALE, fmt, precision, value); +#else vallen = snprintf(convert, sizeof(convert), fmt, precision, value); +#endif + +#ifdef NEED_USE_LOCALE + uselocale(save_locale); +#endif + if (vallen < 0) { target.failed = true; -- 2.39.3 (Apple Git-146) ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2024-08-10 03:52 Thomas Munro <[email protected]> parent: Thomas Munro <[email protected]> 0 siblings, 1 reply; 41+ messages in thread From: Thomas Munro @ 2024-08-10 03:52 UTC (permalink / raw) To: Tom Lane <[email protected]>; +Cc: Tristan Partin <[email protected]>; pgsql-hackers On Sat, Aug 10, 2024 at 3:48 PM Thomas Munro <[email protected]> wrote: > * we could use LC_C_LOCALE to get a "C" locale slightly more > efficiently on those Oops, lost some words, I meant "on those systems that have them (macOS and NetBSD AFAIK)" ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2024-08-10 22:11 Thomas Munro <[email protected]> parent: Thomas Munro <[email protected]> 0 siblings, 2 replies; 41+ messages in thread From: Thomas Munro @ 2024-08-10 22:11 UTC (permalink / raw) To: Tom Lane <[email protected]>; +Cc: Tristan Partin <[email protected]>; pgsql-hackers v4 adds error handling, in case newlocale("C") fails. I created CF entry #5166 for this. Attachments: [text/x-patch] v4-0001-Improve-locale-thread-safety-of-ECPG.patch (31.4K, ../../CA+hUKG+ue0KC5KSqMuL5zi7uCSuXCLP_B+sZSq5=yS84606ZsA@mail.gmail.com/2-v4-0001-Improve-locale-thread-safety-of-ECPG.patch) download | inline diff: From 3543cc04b0d66c7f83bdccbb247eedd93cdea4af Mon Sep 17 00:00:00 2001 From: Thomas Munro <[email protected]> Date: Sat, 10 Aug 2024 11:01:21 +1200 Subject: [PATCH v4] Improve locale thread safety of ECPG. Remove the use of setlocale() as a fallback strategy on systems that don't have uselocale(), where ECPG tries to control LC_NUMERIC formatting on input and output of floating point numbers. Instead, use the C locale explicitly. Provide the name PG_C_LOCALE for this purpose. On a couple of systems this maps to a system-provided LC_C_LOCALE (a non-standard extension to POSIX), and otherwise we provide a thread-safe singleton constructor that allocates a locale_t for (LC_ALL, "C") for the lifetime of the process. 1. Use strtod_l(..., PG_C_LOCALE) for parsing floats, and supply an implementation of that common but non-standard extension with standard uselocale() + strtod() if it is missing. 2. Inside our own snprintf.c, in front-end code only, use non-standard snprintf_l() where we punt floating point numbers to the system snprintf(), or wrap it in uselocale() if not available. Since the non-standard _l() functions require <xlocale.h> on *BSD/macOS systems, simplify the configure probes: instead of XXX_IN_XLOCALE_H for several features XXX, let's just include <xlocale.h> if HAVE_XLOCALE_H. The reason for the extra complication was apparently that some old glibc systems also had an <xlocale.h>, and you weren't supposed to include it directly, but it's gone now (as far as I can tell it was harmless to do so anyway). Discussion: https://postgr.es/m/CWZBBRR6YA8D.8EHMDRGLCKCD%40neon.tech --- config/c-library.m4 | 55 --------- configure | 115 +------------------ configure.ac | 6 +- meson.build | 37 +----- src/include/pg_config.h.in | 15 +-- src/include/port.h | 42 +++++++ src/include/utils/pg_locale.h | 2 +- src/interfaces/ecpg/ecpglib/connect.c | 39 +------ src/interfaces/ecpg/ecpglib/data.c | 2 +- src/interfaces/ecpg/ecpglib/descriptor.c | 37 ------ src/interfaces/ecpg/ecpglib/ecpglib_extern.h | 15 --- src/interfaces/ecpg/ecpglib/execute.c | 55 --------- src/interfaces/ecpg/pgtypeslib/dt_common.c | 6 +- src/interfaces/ecpg/pgtypeslib/interval.c | 4 +- src/interfaces/ecpg/pgtypeslib/numeric.c | 2 +- src/port/Makefile | 1 + src/port/locale.c | 78 +++++++++++++ src/port/meson.build | 1 + src/port/snprintf.c | 53 +++++++++ 19 files changed, 196 insertions(+), 369 deletions(-) create mode 100644 src/port/locale.c diff --git a/config/c-library.m4 b/config/c-library.m4 index aa8223d2ef0..421bc612b27 100644 --- a/config/c-library.m4 +++ b/config/c-library.m4 @@ -81,58 +81,3 @@ AC_DEFUN([PGAC_STRUCT_SOCKADDR_SA_LEN], [#include <sys/types.h> #include <sys/socket.h> ])])# PGAC_STRUCT_SOCKADDR_MEMBERS - - -# PGAC_TYPE_LOCALE_T -# ------------------ -# Check for the locale_t type and find the right header file. macOS -# needs xlocale.h; standard is locale.h, but glibc <= 2.25 also had an -# xlocale.h file that we should not use, so we check the standard -# header first. -AC_DEFUN([PGAC_TYPE_LOCALE_T], -[AC_CACHE_CHECK([for locale_t], pgac_cv_type_locale_t, -[AC_COMPILE_IFELSE([AC_LANG_PROGRAM( -[#include <locale.h> -locale_t x;], -[])], -[pgac_cv_type_locale_t=yes], -[AC_COMPILE_IFELSE([AC_LANG_PROGRAM( -[#include <xlocale.h> -locale_t x;], -[])], -[pgac_cv_type_locale_t='yes (in xlocale.h)'], -[pgac_cv_type_locale_t=no])])]) -if test "$pgac_cv_type_locale_t" = 'yes (in xlocale.h)'; then - AC_DEFINE(LOCALE_T_IN_XLOCALE, 1, - [Define to 1 if `locale_t' requires <xlocale.h>.]) -fi])# PGAC_TYPE_LOCALE_T - - -# PGAC_FUNC_WCSTOMBS_L -# -------------------- -# Try to find a declaration for wcstombs_l(). It might be in stdlib.h -# (following the POSIX requirement for wcstombs()), or in locale.h, or in -# xlocale.h. If it's in the latter, define WCSTOMBS_L_IN_XLOCALE. -# -AC_DEFUN([PGAC_FUNC_WCSTOMBS_L], -[AC_CACHE_CHECK([for wcstombs_l declaration], pgac_cv_func_wcstombs_l, -[AC_COMPILE_IFELSE([AC_LANG_PROGRAM( -[#include <stdlib.h> -#include <locale.h>], -[#ifndef wcstombs_l -(void) wcstombs_l; -#endif])], -[pgac_cv_func_wcstombs_l='yes'], -[AC_COMPILE_IFELSE([AC_LANG_PROGRAM( -[#include <stdlib.h> -#include <locale.h> -#include <xlocale.h>], -[#ifndef wcstombs_l -(void) wcstombs_l; -#endif])], -[pgac_cv_func_wcstombs_l='yes (in xlocale.h)'], -[pgac_cv_func_wcstombs_l='no'])])]) -if test "$pgac_cv_func_wcstombs_l" = 'yes (in xlocale.h)'; then - AC_DEFINE(WCSTOMBS_L_IN_XLOCALE, 1, - [Define to 1 if `wcstombs_l' requires <xlocale.h>.]) -fi])# PGAC_FUNC_WCSTOMBS_L diff --git a/configure b/configure index 4f3aa447566..298492ec251 100755 --- a/configure +++ b/configure @@ -14635,55 +14635,6 @@ _ACEOF fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for locale_t" >&5 -$as_echo_n "checking for locale_t... " >&6; } -if ${pgac_cv_type_locale_t+:} false; then : - $as_echo_n "(cached) " >&6 -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include <locale.h> -locale_t x; -int -main () -{ - - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - pgac_cv_type_locale_t=yes -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include <xlocale.h> -locale_t x; -int -main () -{ - - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - pgac_cv_type_locale_t='yes (in xlocale.h)' -else - pgac_cv_type_locale_t=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv_type_locale_t" >&5 -$as_echo "$pgac_cv_type_locale_t" >&6; } -if test "$pgac_cv_type_locale_t" = 'yes (in xlocale.h)'; then - -$as_echo "#define LOCALE_T_IN_XLOCALE 1" >>confdefs.h - -fi - # MSVC doesn't cope well with defining restrict to __restrict, the # spelling it understands, because it conflicts with # __declspec(restrict). Therefore we define pg_restrict to the @@ -15170,59 +15121,6 @@ if test x"$pgac_cv_var_int_timezone" = xyes ; then $as_echo "#define HAVE_INT_TIMEZONE 1" >>confdefs.h -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for wcstombs_l declaration" >&5 -$as_echo_n "checking for wcstombs_l declaration... " >&6; } -if ${pgac_cv_func_wcstombs_l+:} false; then : - $as_echo_n "(cached) " >&6 -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include <stdlib.h> -#include <locale.h> -int -main () -{ -#ifndef wcstombs_l -(void) wcstombs_l; -#endif - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - pgac_cv_func_wcstombs_l='yes' -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include <stdlib.h> -#include <locale.h> -#include <xlocale.h> -int -main () -{ -#ifndef wcstombs_l -(void) wcstombs_l; -#endif - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - pgac_cv_func_wcstombs_l='yes (in xlocale.h)' -else - pgac_cv_func_wcstombs_l='no' -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv_func_wcstombs_l" >&5 -$as_echo "$pgac_cv_func_wcstombs_l" >&6; } -if test "$pgac_cv_func_wcstombs_l" = 'yes (in xlocale.h)'; then - -$as_echo "#define WCSTOMBS_L_IN_XLOCALE 1" >>confdefs.h - fi # Some versions of libedit contain strlcpy(), setproctitle(), and other @@ -15232,7 +15130,7 @@ fi LIBS_including_readline="$LIBS" LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'` -for ac_func in backtrace_symbols copyfile copy_file_range getifaddrs getpeerucred inet_pton kqueue mbstowcs_l memset_s posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast strchrnul strsignal syncfs sync_file_range uselocale wcstombs_l +for ac_func in backtrace_symbols copyfile copy_file_range getifaddrs getpeerucred inet_pton kqueue mbstowcs_l memset_s posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast snprintf_l strtod_l strchrnul strsignal syncfs sync_file_range uselocale wcstombs_l do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" @@ -16005,17 +15903,6 @@ fi # Win32 (really MinGW) support if test "$PORTNAME" = "win32"; then - for ac_func in _configthreadlocale -do : - ac_fn_c_check_func "$LINENO" "_configthreadlocale" "ac_cv_func__configthreadlocale" -if test "x$ac_cv_func__configthreadlocale" = xyes; then : - cat >>confdefs.h <<_ACEOF -#define HAVE__CONFIGTHREADLOCALE 1 -_ACEOF - -fi -done - case " $LIBOBJS " in *" dirmod.$ac_objext "* ) ;; *) LIBOBJS="$LIBOBJS dirmod.$ac_objext" diff --git a/configure.ac b/configure.ac index 049bc014911..302aadd427d 100644 --- a/configure.ac +++ b/configure.ac @@ -1620,8 +1620,6 @@ PGAC_UNION_SEMUN AC_CHECK_TYPES(socklen_t, [], [], [#include <sys/socket.h>]) PGAC_STRUCT_SOCKADDR_SA_LEN -PGAC_TYPE_LOCALE_T - # MSVC doesn't cope well with defining restrict to __restrict, the # spelling it understands, because it conflicts with # __declspec(restrict). Therefore we define pg_restrict to the @@ -1720,7 +1718,6 @@ fi ## PGAC_VAR_INT_TIMEZONE -PGAC_FUNC_WCSTOMBS_L # Some versions of libedit contain strlcpy(), setproctitle(), and other # symbols that that library has no business exposing to the world. Pending @@ -1744,6 +1741,8 @@ AC_CHECK_FUNCS(m4_normalize([ pthread_is_threaded_np setproctitle setproctitle_fast + snprintf_l + strtod_l strchrnul strsignal syncfs @@ -1833,7 +1832,6 @@ fi # Win32 (really MinGW) support if test "$PORTNAME" = "win32"; then - AC_CHECK_FUNCS(_configthreadlocale) AC_LIBOBJ(dirmod) AC_LIBOBJ(kill) AC_LIBOBJ(open) diff --git a/meson.build b/meson.build index cc176f11b5d..932334ed634 100644 --- a/meson.build +++ b/meson.build @@ -2407,6 +2407,7 @@ header_checks = [ 'sys/ucred.h', 'termios.h', 'ucred.h', + 'xlocale.h', ] foreach header : header_checks @@ -2550,15 +2551,6 @@ else cdata.set('STRERROR_R_INT', false) endif -# Find the right header file for the locale_t type. macOS needs xlocale.h; -# standard is locale.h, but glibc <= 2.25 also had an xlocale.h file that -# we should not use so we check the standard header first. MSVC has a -# replacement defined in src/include/port/win32_port.h. -if not cc.has_type('locale_t', prefix: '#include <locale.h>') and \ - cc.has_type('locale_t', prefix: '#include <xlocale.h>') - cdata.set('LOCALE_T_IN_XLOCALE', 1) -endif - # Check if the C compiler understands typeof or a variant. Define # HAVE_TYPEOF if so, and define 'typeof' to the actual key word. foreach kw : ['typeof', '__typeof__', 'decltype'] @@ -2583,30 +2575,6 @@ int main(void) endif endforeach - -# Try to find a declaration for wcstombs_l(). It might be in stdlib.h -# (following the POSIX requirement for wcstombs()), or in locale.h, or in -# xlocale.h. If it's in the latter, define WCSTOMBS_L_IN_XLOCALE. -wcstombs_l_test = ''' -#include <stdlib.h> -#include <locale.h> -@0@ - -void main(void) -{ -#ifndef wcstombs_l - (void) wcstombs_l; -#endif -} -''' -if (not cc.compiles(wcstombs_l_test.format(''), - name: 'wcstombs_l') and - cc.compiles(wcstombs_l_test.format('#include <xlocale.h>'), - name: 'wcstombs_l in xlocale.h')) - cdata.set('WCSTOMBS_L_IN_XLOCALE', 1) -endif - - # MSVC doesn't cope well with defining restrict to __restrict, the spelling it # understands, because it conflicts with __declspec(restrict). Therefore we # define pg_restrict to the appropriate definition, which presumably won't @@ -2658,7 +2626,6 @@ endif # XXX: Might be worth conditioning some checks on the OS, to avoid doing # unnecessary checks over and over, particularly on windows. func_checks = [ - ['_configthreadlocale', {'skip': host_system != 'windows'}], ['backtrace_symbols', {'dependencies': [execinfo_dep]}], ['clock_gettime', {'dependencies': [rt_dep], 'define': false}], ['copyfile'], @@ -2690,6 +2657,8 @@ func_checks = [ ['shm_open', {'dependencies': [rt_dep], 'define': false}], ['shm_unlink', {'dependencies': [rt_dep], 'define': false}], ['shmget', {'dependencies': [cygipc_dep], 'define': false}], + ['snprintf_l'], + ['strtod_l'], ['socket', {'dependencies': [socket_dep], 'define': false}], ['strchrnul'], ['strerror_r', {'dependencies': [thread_dep]}], diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index 0e9b108e667..c0586edab13 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -376,6 +376,9 @@ /* Define to 1 if you have the `setproctitle_fast' function. */ #undef HAVE_SETPROCTITLE_FAST +/* Define to 1 if you have the `snprintf_l' function. */ +#undef HAVE_SNPRINTF_L + /* Define to 1 if the system has the type `socklen_t'. */ #undef HAVE_SOCKLEN_T @@ -421,6 +424,9 @@ /* Define to 1 if you have the `strsignal' function. */ #undef HAVE_STRSIGNAL +/* Define to 1 if you have the `strtod_l' function. */ +#undef HAVE_STRTOD_L + /* Define to 1 if the system has the type `struct option'. */ #undef HAVE_STRUCT_OPTION @@ -556,9 +562,6 @@ /* Define to 1 if your compiler understands __builtin_unreachable. */ #undef HAVE__BUILTIN_UNREACHABLE -/* Define to 1 if you have the `_configthreadlocale' function. */ -#undef HAVE__CONFIGTHREADLOCALE - /* Define to 1 if you have __cpuid. */ #undef HAVE__CPUID @@ -577,9 +580,6 @@ /* Define to the appropriate printf length modifier for 64-bit ints. */ #undef INT64_MODIFIER -/* Define to 1 if `locale_t' requires <xlocale.h>. */ -#undef LOCALE_T_IN_XLOCALE - /* Define as the maximum alignment requirement of any C data type. */ #undef MAXIMUM_ALIGNOF @@ -766,9 +766,6 @@ /* Define to 1 to build with ZSTD support. (--with-zstd) */ #undef USE_ZSTD -/* Define to 1 if `wcstombs_l' requires <xlocale.h>. */ -#undef WCSTOMBS_L_IN_XLOCALE - /* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most significant byte first (like Motorola and SPARC, unlike Intel). */ #if defined AC_APPLE_UNIVERSAL_BUILD diff --git a/src/include/port.h b/src/include/port.h index c7400052675..8e814700e7f 100644 --- a/src/include/port.h +++ b/src/include/port.h @@ -15,6 +15,11 @@ #include <ctype.h> +#include <locale.h> +#ifdef HAVE_XLOCALE_H +#include <xlocale.h> +#endif + /* * Windows has enough specialized port stuff that we push most of it off * into another file. @@ -217,6 +222,43 @@ extern int pg_fprintf(FILE *stream, const char *fmt,...) pg_attribute_printf(2, extern int pg_vprintf(const char *fmt, va_list args) pg_attribute_printf(1, 0); extern int pg_printf(const char *fmt,...) pg_attribute_printf(1, 2); +/* Thread-safe singleton constructor for process-lifetime "C" locale. */ +extern locale_t pg_get_c_locale(void); + +/* + * A couple of systems offer a pre-defined locale_t value for the "C" locale. + * Otherwise fall back to the above function. Provide a way to check that a + * "C" locale has been allocated at a convenient time for error reporting. + */ +#ifdef LC_C_LOCALE +#define PG_C_LOCALE LC_C_LOCALE +#define pg_ensure_c_locale() true +#else +#define PG_C_LOCALE pg_get_c_locale() +#define pg_ensure_c_locale() (pg_get_c_locale() != (locale_t) 0) +#endif + +#ifndef HAVE_STRTOD_L +/* + * POSIX doesn't define this function, but we can implement it with thread-safe + * save-and-restore. Not all systems have uselocale(), but the ones that don't + * have strtod_l(). + */ +static inline double +strtod_l(const char *nptr, char **endptr, locale_t loc) +{ +#ifdef WIN32 + return _strtod_l(nptr, endptr, loc); +#else + locale_t save = uselocale(loc); + double result = strtod(nptr, endptr); + + uselocale(save); + return result; +#endif +} +#endif + #ifndef WIN32 /* * We add a pg_ prefix as a warning that the Windows implementations have the diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h index f41d33975be..9651f3c3ab5 100644 --- a/src/include/utils/pg_locale.h +++ b/src/include/utils/pg_locale.h @@ -12,7 +12,7 @@ #ifndef _PG_LOCALE_ #define _PG_LOCALE_ -#if defined(LOCALE_T_IN_XLOCALE) || defined(WCSTOMBS_L_IN_XLOCALE) +#ifdef HAVE_XLOCALE_H #include <xlocale.h> #endif #ifdef USE_ICU diff --git a/src/interfaces/ecpg/ecpglib/connect.c b/src/interfaces/ecpg/ecpglib/connect.c index 8afb1f0a26f..f4ba5b722eb 100644 --- a/src/interfaces/ecpg/ecpglib/connect.c +++ b/src/interfaces/ecpg/ecpglib/connect.c @@ -10,10 +10,6 @@ #include "ecpgtype.h" #include "sqlca.h" -#ifdef HAVE_USELOCALE -locale_t ecpg_clocale = (locale_t) 0; -#endif - static pthread_mutex_t connections_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_key_t actual_connection_key; static pthread_once_t actual_connection_key_once = PTHREAD_ONCE_INIT; @@ -268,7 +264,7 @@ ECPGconnect(int lineno, int c, const char *name, const char *user, const char *p const char **conn_keywords; const char **conn_values; - if (sqlca == NULL) + if (sqlca == NULL || !pg_ensure_c_locale()) { ecpg_raise(lineno, ECPG_OUT_OF_MEMORY, ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL); @@ -483,39 +479,6 @@ ECPGconnect(int lineno, int c, const char *name, const char *user, const char *p /* add connection to our list */ pthread_mutex_lock(&connections_mutex); - /* - * ... but first, make certain we have created ecpg_clocale. Rely on - * holding connections_mutex to ensure this is done by only one thread. - */ -#ifdef HAVE_USELOCALE - if (!ecpg_clocale) - { - ecpg_clocale = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0); - if (!ecpg_clocale) - { - pthread_mutex_unlock(&connections_mutex); - 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/data.c b/src/interfaces/ecpg/ecpglib/data.c index fa562767585..856f4c9472d 100644 --- a/src/interfaces/ecpg/ecpglib/data.c +++ b/src/interfaces/ecpg/ecpglib/data.c @@ -466,7 +466,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno, pval++; if (!check_special_value(pval, &dres, &scan_length)) - dres = strtod(pval, &scan_length); + dres = strtod_l(pval, &scan_length, PG_C_LOCALE); if (isarray && *scan_length == '"') scan_length++; diff --git a/src/interfaces/ecpg/ecpglib/descriptor.c b/src/interfaces/ecpg/ecpglib/descriptor.c index ad279e245c4..855322e037f 100644 --- a/src/interfaces/ecpg/ecpglib/descriptor.c +++ b/src/interfaces/ecpg/ecpglib/descriptor.c @@ -475,46 +475,9 @@ ECPGget_desc(int lineno, const char *desc_name, int index,...) memset(&stmt, 0, sizeof stmt); stmt.lineno = lineno; - /* Make sure we do NOT honor the locale for numeric input */ - /* since the database gives the standard decimal point */ - /* (see comments in execute.c) */ -#ifdef HAVE_USELOCALE - - /* - * 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); -#endif - stmt.oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno); - setlocale(LC_NUMERIC, "C"); -#endif - /* desperate try to guess something sensible */ stmt.connection = ecpg_get_connection(NULL); ecpg_store_result(ECPGresult, index, &stmt, &data_var); - -#ifdef HAVE_USELOCALE - if (stmt.oldlocale != (locale_t) 0) - uselocale(stmt.oldlocale); -#else - if (stmt.oldlocale) - { - setlocale(LC_NUMERIC, stmt.oldlocale); - ecpg_free(stmt.oldlocale); - } -#ifdef HAVE__CONFIGTHREADLOCALE - if (stmt.oldthreadlocale != -1) - (void) _configthreadlocale(stmt.oldthreadlocale); -#endif -#endif } else if (data_var.ind_type != ECPGt_NO_INDICATOR && data_var.ind_pointer != NULL) diff --git a/src/interfaces/ecpg/ecpglib/ecpglib_extern.h b/src/interfaces/ecpg/ecpglib/ecpglib_extern.h index 01b4309a710..40988d53575 100644 --- a/src/interfaces/ecpg/ecpglib/ecpglib_extern.h +++ b/src/interfaces/ecpg/ecpglib/ecpglib_extern.h @@ -13,9 +13,6 @@ #ifndef CHAR_BIT #include <limits.h> #endif -#ifdef LOCALE_T_IN_XLOCALE -#include <xlocale.h> -#endif enum COMPAT_MODE { @@ -59,10 +56,6 @@ 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 { @@ -76,14 +69,6 @@ struct statement bool questionmarks; struct variable *inlist; struct variable *outlist; -#ifdef HAVE_USELOCALE - locale_t oldlocale; -#else - char *oldlocale; -#ifdef HAVE__CONFIGTHREADLOCALE - int oldthreadlocale; -#endif -#endif int nparams; char **paramvalues; int *paramlengths; diff --git a/src/interfaces/ecpg/ecpglib/execute.c b/src/interfaces/ecpg/ecpglib/execute.c index 04d0b40c537..806d4779fa9 100644 --- a/src/interfaces/ecpg/ecpglib/execute.c +++ b/src/interfaces/ecpg/ecpglib/execute.c @@ -101,9 +101,6 @@ free_statement(struct statement *stmt) free_variable(stmt->outlist); ecpg_free(stmt->command); ecpg_free(stmt->name); -#ifndef HAVE_USELOCALE - ecpg_free(stmt->oldlocale); -#endif ecpg_free(stmt); } @@ -1973,40 +1970,6 @@ ecpg_do_prologue(int lineno, const int compat, const int force_indicator, if (stmt == NULL) return false; - /* - * Make sure we do NOT honor the locale for numeric input/output since the - * database wants the standard decimal point. If available, use - * uselocale() for this because it's thread-safe. Windows doesn't have - * that, but it usually does have _configthreadlocale(). In some versions - * of MinGW, _configthreadlocale() exists but always returns -1 --- so - * treat that situation as if the function doesn't exist. - */ -#ifdef HAVE_USELOCALE - - /* - * 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); - return false; - } -#else -#ifdef HAVE__CONFIGTHREADLOCALE - stmt->oldthreadlocale = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); -#endif - stmt->oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno); - if (stmt->oldlocale == NULL) - { - ecpg_do_epilogue(stmt); - return false; - } - setlocale(LC_NUMERIC, "C"); -#endif - /* * If statement type is ECPGst_prepnormal we are supposed to prepare the * statement before executing them @@ -2213,24 +2176,6 @@ ecpg_do_epilogue(struct statement *stmt) if (stmt == NULL) return; -#ifdef HAVE_USELOCALE - if (stmt->oldlocale != (locale_t) 0) - uselocale(stmt->oldlocale); -#else - if (stmt->oldlocale) - setlocale(LC_NUMERIC, stmt->oldlocale); -#ifdef HAVE__CONFIGTHREADLOCALE - - /* - * This is a bit trickier than it looks: if we failed partway through - * statement initialization, oldthreadlocale could still be 0. But that's - * okay because a call with 0 is defined to be a no-op. - */ - if (stmt->oldthreadlocale != -1) - (void) _configthreadlocale(stmt->oldthreadlocale); -#endif -#endif - free_statement(stmt); } diff --git a/src/interfaces/ecpg/pgtypeslib/dt_common.c b/src/interfaces/ecpg/pgtypeslib/dt_common.c index ed08088bfe1..92459728bf4 100644 --- a/src/interfaces/ecpg/pgtypeslib/dt_common.c +++ b/src/interfaces/ecpg/pgtypeslib/dt_common.c @@ -1216,7 +1216,7 @@ DecodeNumber(int flen, char *str, int fmask, return DecodeNumberField(flen, str, (fmask | DTK_DATE_M), tmask, tm, fsec, is2digits); - *fsec = strtod(cp, &cp); + *fsec = strtod_l(cp, &cp, PG_C_LOCALE); if (*cp != '\0') return -1; } @@ -2028,7 +2028,7 @@ DecodeDateTime(char **field, int *ftype, int nf, { double frac; - frac = strtod(cp, &cp); + frac = strtod_l(cp, &cp, PG_C_LOCALE); if (*cp != '\0') return -1; *fsec = frac * 1000000; @@ -2052,7 +2052,7 @@ DecodeDateTime(char **field, int *ftype, int nf, { double time; - time = strtod(cp, &cp); + time = strtod_l(cp, &cp, PG_C_LOCALE); if (*cp != '\0') return -1; diff --git a/src/interfaces/ecpg/pgtypeslib/interval.c b/src/interfaces/ecpg/pgtypeslib/interval.c index 936a6883816..155c6cc7770 100644 --- a/src/interfaces/ecpg/pgtypeslib/interval.c +++ b/src/interfaces/ecpg/pgtypeslib/interval.c @@ -60,7 +60,7 @@ ParseISO8601Number(const char *str, char **endptr, int *ipart, double *fpart) if (!(isdigit((unsigned char) *str) || *str == '-' || *str == '.')) return DTERR_BAD_FORMAT; errno = 0; - val = strtod(str, endptr); + val = strtod_l(str, endptr, PG_C_LOCALE); /* did we not see anything that looks like a double? */ if (*endptr == str || errno != 0) return DTERR_BAD_FORMAT; @@ -455,7 +455,7 @@ DecodeInterval(char **field, int *ftype, int nf, /* int range, */ else if (*cp == '.') { errno = 0; - fval = strtod(cp, &cp); + fval = strtod_l(cp, &cp, PG_C_LOCALE); if (*cp != '\0' || errno != 0) return DTERR_BAD_FORMAT; diff --git a/src/interfaces/ecpg/pgtypeslib/numeric.c b/src/interfaces/ecpg/pgtypeslib/numeric.c index 35e7b92da40..49938543d03 100644 --- a/src/interfaces/ecpg/pgtypeslib/numeric.c +++ b/src/interfaces/ecpg/pgtypeslib/numeric.c @@ -1455,7 +1455,7 @@ numericvar_to_double(numeric *var, double *dp) * strtod does not reset errno to 0 in case of success. */ errno = 0; - val = strtod(tmp, &endptr); + val = strtod_l(tmp, &endptr, PG_C_LOCALE); if (errno == ERANGE) { free(tmp); diff --git a/src/port/Makefile b/src/port/Makefile index db7c02117b0..03b55bd26b0 100644 --- a/src/port/Makefile +++ b/src/port/Makefile @@ -42,6 +42,7 @@ OBJS = \ bsearch_arg.o \ chklocale.o \ inet_net_ntop.o \ + locale.o \ noblock.o \ path.o \ pg_bitutils.o \ diff --git a/src/port/locale.c b/src/port/locale.c new file mode 100644 index 00000000000..a5b0648bb8b --- /dev/null +++ b/src/port/locale.c @@ -0,0 +1,78 @@ +/*------------------------------------------------------------------------- + * + * locale.c + * Helper routines for thread-safe system locale usage. + * + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/port/locale.c + * + *------------------------------------------------------------------------- + */ + +#include "c.h" + +#include <locale.h> +#ifdef HAVE_XLOCALE_H +#include <xlocale.h> +#endif + +#ifndef WIN32 +#include <pthread.h> +#else +#include <synchapi.h> +#endif + +static locale_t c_locale; + +#ifndef WIN32 +static void +init_c_locale_once(void) +{ + c_locale = newlocale(LC_ALL, "C", NULL); +} +#else +static BOOL +init_c_locale_once(PINIT_ONCE once, PVOID parameter, PVOID *context) +{ + c_locale = _create_locale(LC_ALL, "C"); + return true; +} +#endif + +locale_t +pg_get_c_locale(void) +{ + /* + * Fast path if already initialized. This assumes that we can read a + * locale_t (in practice, a pointer) without tearing in a multi-threaded + * program. + */ + if (c_locale != (locale_t) 0) + return c_locale; + + /* Make a locale_t. It will live until process exit. */ + { +#ifndef WIN32 + static pthread_once_t once = PTHREAD_ONCE_INIT; + + pthread_once(&once, init_c_locale_once); +#else + static INIT_ONCE once; + InitOnceExecuteOnce(&once, init_c_locale_once, NULL, NULL); +#endif + } + + /* + * It is remotely possible that the locale couldn't be allocated, due to + * lack of memory. In that case (locale_t) 0 will be returned. A caller + * can test for that condition with pg_ensure_c_locale() at a convenient + * time for error reporting. + */ + + return c_locale; +} diff --git a/src/port/meson.build b/src/port/meson.build index ff54b7b53e9..e21961fd739 100644 --- a/src/port/meson.build +++ b/src/port/meson.build @@ -5,6 +5,7 @@ pgport_sources = [ 'chklocale.c', 'inet_net_ntop.c', 'noblock.c', + 'locale.c', 'path.c', 'pg_bitutils.c', 'pg_strong_random.c', diff --git a/src/port/snprintf.c b/src/port/snprintf.c index 884f0262dd1..983d39a7729 100644 --- a/src/port/snprintf.c +++ b/src/port/snprintf.c @@ -109,6 +109,34 @@ #undef vprintf #undef printf +#if defined(FRONTEND) +/* + * Frontend code might be multi-threaded. When calling the system snprintf() + * for floats, we have to use either the snprintf_l() variant, or + * save-and-restore the calling thread's locale using uselocale(). + */ +#if defined(HAVE_SNPRINTF_L) +/* At least macOS and the BSDs have the snprintf_l() extension. */ +#define USE_SNPRINTF_L +#elif defined(WIN32) +/* Windows has a version with a different name and argument order. */ +#define snprintf_l(str, size, loc, format, ...) _snprintf_l(str, size, format, loc, __VA_ARGS__) +#define USE_SNPRINTF_L +#else +/* We have to do a thread-safe save-and-restore around snprintf(). */ +#define NEED_USE_LOCALE +#endif +#else +/* + * Backend code doesn't need to worry about the locales, because LC_NUMERIC is + * set to "C" in main() and doesn't change. Plain old snprintf() is always OK. + * + * XXX If the backend were multithreaded, it would have to be 100% certain that + * no one is calling setlocale() concurrently, even transiently, to be able to + * keep this small optimization. + */ +#endif + /* * Info about where the formatted output is going. * @@ -1184,6 +1212,9 @@ fmtfloat(double value, char type, int forcesign, int leftjust, * according to == but not according to memcmp. */ static const double dzero = 0.0; +#ifdef NEED_USE_LOCALE + locale_t save_locale = uselocale(PG_C_LOCALE); +#endif if (adjust_sign((value < 0.0 || (value == 0.0 && @@ -1205,7 +1236,11 @@ fmtfloat(double value, char type, int forcesign, int leftjust, fmt[2] = '*'; fmt[3] = type; fmt[4] = '\0'; +#ifdef USE_SNPRINTF_L + vallen = snprintf_l(convert, sizeof(convert), PG_C_LOCALE, fmt, prec, value); +#else vallen = snprintf(convert, sizeof(convert), fmt, prec, value); +#endif } else { @@ -1214,6 +1249,11 @@ fmtfloat(double value, char type, int forcesign, int leftjust, fmt[2] = '\0'; vallen = snprintf(convert, sizeof(convert), fmt, value); } + +#ifdef NEED_USE_LOCALE + uselocale(save_locale); +#endif + if (vallen < 0) goto fail; @@ -1336,12 +1376,25 @@ pg_strfromd(char *str, size_t count, int precision, double value) } else { +#ifdef NEED_USE_LOCALE + locale_t save_locale = uselocale(PG_C_LOCALE); +#endif + fmt[0] = '%'; fmt[1] = '.'; fmt[2] = '*'; fmt[3] = 'g'; fmt[4] = '\0'; +#ifdef USE_SNPRINTF_L + vallen = snprintf_l(convert, sizeof(convert), PG_C_LOCALE, fmt, precision, value); +#else vallen = snprintf(convert, sizeof(convert), fmt, precision, value); +#endif + +#ifdef NEED_USE_LOCALE + uselocale(save_locale); +#endif + if (vallen < 0) { target.failed = true; -- 2.46.0 ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2024-08-13 23:17 Tristan Partin <[email protected]> parent: Thomas Munro <[email protected]> 1 sibling, 1 reply; 41+ messages in thread From: Tristan Partin @ 2024-08-13 23:17 UTC (permalink / raw) To: Thomas Munro <[email protected]>; Tom Lane <[email protected]>; +Cc: pgsql-hackers Hey Thomas, Thanks for picking this up. I think your patch looks really good. Are you familiar with gcc's function poisoning? #include <stdio.h> #pragma GCC poison puts int main(){ #pragma GCC bless begin puts puts("a"); #pragma GCC bless end puts } I wonder if we could use function poisoning to our advantage. For instance in ecpg, it looks like you got all of the strtod() invocations and replaced them with strtod_l(). Here is a patch with an example of what I'm talking about. -- Tristan Partin Neon (https://neon.tech) Attachments: [text/x-patch] v1-0001-Poison-strtod-in-ecpg.patch (2.5K, ../../[email protected]/2-v1-0001-Poison-strtod-in-ecpg.patch) download | inline diff: From a76a3f2577d098f7ce5a3ed1768aca4293aedfcc Mon Sep 17 00:00:00 2001 From: Tristan Partin <[email protected]> Date: Tue, 13 Aug 2024 18:06:20 -0500 Subject: [PATCH v1] Poison strtod in ecpg ecpg exclusively uses strtod_l now. Signed-off-by: Tristan Partin <[email protected]> --- src/interfaces/ecpg/ecpglib/data.c | 1 + src/interfaces/ecpg/include/ecpg_poison.h | 6 ++++++ src/interfaces/ecpg/pgtypeslib/dt_common.c | 1 + src/interfaces/ecpg/pgtypeslib/interval.c | 1 + src/interfaces/ecpg/pgtypeslib/numeric.c | 1 + 5 files changed, 10 insertions(+) create mode 100644 src/interfaces/ecpg/include/ecpg_poison.h diff --git a/src/interfaces/ecpg/ecpglib/data.c b/src/interfaces/ecpg/ecpglib/data.c index 856f4c9472d..13023fc21eb 100644 --- a/src/interfaces/ecpg/ecpglib/data.c +++ b/src/interfaces/ecpg/ecpglib/data.c @@ -6,6 +6,7 @@ #include <math.h> #include "ecpgerrno.h" +#include "ecpg_poison.h" #include "ecpglib.h" #include "ecpglib_extern.h" #include "ecpgtype.h" diff --git a/src/interfaces/ecpg/include/ecpg_poison.h b/src/interfaces/ecpg/include/ecpg_poison.h new file mode 100644 index 00000000000..97ae9d7a143 --- /dev/null +++ b/src/interfaces/ecpg/include/ecpg_poison.h @@ -0,0 +1,6 @@ +#ifndef _ECPG_POISON_H +#define _ECPG_POISON_H + +#pragma GCC poison strtod + +#endif /* !_ECPG_POISON_H */ diff --git a/src/interfaces/ecpg/pgtypeslib/dt_common.c b/src/interfaces/ecpg/pgtypeslib/dt_common.c index 92459728bf4..2a9c5ec32c0 100644 --- a/src/interfaces/ecpg/pgtypeslib/dt_common.c +++ b/src/interfaces/ecpg/pgtypeslib/dt_common.c @@ -8,6 +8,7 @@ #include "common/string.h" #include "dt.h" +#include "ecpg_poison.h" #include "pgtypes_timestamp.h" #include "pgtypeslib_extern.h" diff --git a/src/interfaces/ecpg/pgtypeslib/interval.c b/src/interfaces/ecpg/pgtypeslib/interval.c index 155c6cc7770..bfcc7233999 100644 --- a/src/interfaces/ecpg/pgtypeslib/interval.c +++ b/src/interfaces/ecpg/pgtypeslib/interval.c @@ -12,6 +12,7 @@ #include "common/string.h" #include "dt.h" +#include "ecpg_poison.h" #include "pgtypes_error.h" #include "pgtypes_interval.h" #include "pgtypeslib_extern.h" diff --git a/src/interfaces/ecpg/pgtypeslib/numeric.c b/src/interfaces/ecpg/pgtypeslib/numeric.c index 49938543d03..2623f362565 100644 --- a/src/interfaces/ecpg/pgtypeslib/numeric.c +++ b/src/interfaces/ecpg/pgtypeslib/numeric.c @@ -6,6 +6,7 @@ #include <float.h> #include <limits.h> +#include "ecpg_poison.h" #include "pgtypes_error.h" #include "pgtypes_numeric.h" #include "pgtypeslib_extern.h" -- Tristan Partin https://tristan.partin.io ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2024-08-15 08:49 Thomas Munro <[email protected]> parent: Tristan Partin <[email protected]> 0 siblings, 0 replies; 41+ messages in thread From: Thomas Munro @ 2024-08-15 08:49 UTC (permalink / raw) To: Tristan Partin <[email protected]>; +Cc: Tom Lane <[email protected]>; pgsql-hackers On Wed, Aug 14, 2024 at 11:17 AM Tristan Partin <[email protected]> wrote: > Thanks for picking this up. I think your patch looks really good. Thanks for looking! > Are > you familiar with gcc's function poisoning? > > #include <stdio.h> > #pragma GCC poison puts > > int main(){ > #pragma GCC bless begin puts > puts("a"); > #pragma GCC bless end puts > } > > I wonder if we could use function poisoning to our advantage. For > instance in ecpg, it looks like you got all of the strtod() invocations > and replaced them with strtod_l(). Here is a patch with an example of > what I'm talking about. Thanks, this looks very useful. ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2024-08-28 18:50 Peter Eisentraut <[email protected]> parent: Thomas Munro <[email protected]> 1 sibling, 3 replies; 41+ messages in thread From: Peter Eisentraut @ 2024-08-28 18:50 UTC (permalink / raw) To: Thomas Munro <[email protected]>; +Cc: Tristan Partin <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers On 11.08.24 00:11, Thomas Munro wrote: > v4 adds error handling, in case newlocale("C") fails. I created CF > entry #5166 for this. I took a look at this. It was quite a complicated discussion that led to this, but I agree with the solution that was arrived at. I suggest that the simplification of the xlocale.h configure tests could be committed separately. This would also be useful independent of this, and it's a sizeable chunk of this patch. Also, you're removing the configure test for _configthreadlocale(). Presumably because you're removing all the uses. But wouldn't we need that back later in the backend maybe? Or is that test even relevant anymore, that is, are there Windows versions that don't have it? Adding global includes to port.h doesn't seem great. That's not a place one would normally look. We already include <locale.h> in c.h anyway, so it would probably be even better overall if you just added a conditional #include <xlocale.h> to c.h as well. For Windows, we already have things like #define strcoll_l _strcoll_l in src/include/port/win32_port.h, so it would seem more sensible to add strtod_l to that list, instead of in port.h. The error handling with pg_ensure_c_locale(), that's the sort of thing I'm afraid will be hard to test or even know how it will behave. And it creates this weird coupling between pgtypeslib and ecpglib that you mentioned earlier. And if there are other users of PG_C_LOCALE in the future, there will be similar questions about the proper initialization and error handling sequence. I would consider instead making a local static variable in each function that needs this. For example, numericvar_to_double() might do { static locale_t c_locale; if (!c_locale) { c_locale = pg_get_c_locale(); if (!c_locale) return -1; /* local error reporting convention */ } ... } This is a bit more code in total, but then you only initialize what you need and you can handle errors locally. ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2024-10-01 12:04 Peter Eisentraut <[email protected]> parent: Peter Eisentraut <[email protected]> 2 siblings, 1 reply; 41+ messages in thread From: Peter Eisentraut @ 2024-10-01 12:04 UTC (permalink / raw) To: Thomas Munro <[email protected]>; +Cc: Tristan Partin <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers On 28.08.24 20:50, Peter Eisentraut wrote: > I suggest that the simplification of the xlocale.h configure tests could > be committed separately. This would also be useful independent of this, > and it's a sizeable chunk of this patch. To keep this moving along a bit, I have extracted this part and committed it separately. I had to make a few small tweaks, e.g., there was no check for xlocale.h in configure.ac, and the old xlocale.h-including stanza could be removed from chklocale.h. Let's see how this goes. ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2024-11-14 01:54 Thomas Munro <[email protected]> parent: Peter Eisentraut <[email protected]> 0 siblings, 0 replies; 41+ messages in thread From: Thomas Munro @ 2024-11-14 01:54 UTC (permalink / raw) To: Peter Eisentraut <[email protected]>; +Cc: Tristan Partin <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers On Wed, Oct 2, 2024 at 1:04 AM Peter Eisentraut <[email protected]> wrote: > On 28.08.24 20:50, Peter Eisentraut wrote: > > I suggest that the simplification of the xlocale.h configure tests could > > be committed separately. This would also be useful independent of this, > > and it's a sizeable chunk of this patch. > > To keep this moving along a bit, I have extracted this part and > committed it separately. I had to make a few small tweaks, e.g., there > was no check for xlocale.h in configure.ac, and the old > xlocale.h-including stanza could be removed from chklocale.h. Let's see > how this goes. Thanks, Peter. That seemed to work fine, and we shouldn't have to think about xlocale.h as a special case too much again. I am back to working on all these cleanup patches. Here is a rebase of this one, with a few minor style adjustments and a new attempt to explain the overall approach in the commit message. Attachments: [text/x-patch] v5-0001-Tidy-up-locale-thread-safety-in-ECPG-library.patch (24.0K, ../../CA+hUKGKwyRWONAkHURQJSSW3g8XeXrb-JU=PXq=AiPNGmaeQ9A@mail.gmail.com/2-v5-0001-Tidy-up-locale-thread-safety-in-ECPG-library.patch) download | inline diff: From a5eebfdd0b946f64310f60fab68e67b84bb474ae Mon Sep 17 00:00:00 2001 From: Thomas Munro <[email protected]> Date: Sat, 10 Aug 2024 11:01:21 +1200 Subject: [PATCH v5] Tidy up locale thread safety in ECPG library. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove setlocale() and _configthreadlocal() as fallback strategy on systems that don't have uselocale(), where ECPG tries to control LC_NUMERIC formatting on input and output of floating point numbers. It was probably broken on some systems (NetBSD), and the code was also quite messy and complicated, with obsolete configure tests (Windows). It was also arguably broken, or at least had unstated environmental requirements, if pgtypeslib code was called directly. Instead, introduce PG_C_LOCALE to refer to the "C" locale as a locale_t value. It maps to the special constant LC_C_LOCALE when defined by libc (macOS, NetBSD), or otherwise uses a process-lifetime locale_t that is allocated on first use, just as ECPG previously did itself. The new replacement might be more widely useful. Then change the float parsing and printing code to pass that to _l() functions where appropriate. Unfortunately the portability of those functions is a bit complicated. First, many obvious and useful _l() functions are missing from POSIX, though most standard libraries define some of them anyway. Second, although the thread-safe save/restore technique can be used to replace the missing ones, Windows and NetBSD refused to implement standard uselocale(). They might have a point: "wide scope" uselocale() is hard to combine with other code and error-prone, especially in library code. Luckily they have the _l() functions we want so far anyway. So we have to be prepared for both ways of doing things: 1. In ECPG, use strtod_l() for parsing, and supply a port.h replacement using uselocale() over a limited scope if missing. 2. Inside our own snprintf.c, use three different approaches to format floats. For frontend code, call libc's snprintf_l(), or wrap libc's snprintf() in uselocale() if it's missing. For backend code, snprintf.c can keep assuming that the global locale's LC_NUMERIC is "C" and call libc's snprintf() without change, for now. (It might eventually be possible to call our in-tree Ryū routines to display floats in snprintf.c, given the C-locale-always remit of our in-tree snprintf(), but this patch doesn't risk changing anything that complicated.) Reviewed-by: Peter Eisentraut <[email protected]> Reviewed-by: Tristan Partin <[email protected]> Discussion: https://postgr.es/m/CWZBBRR6YA8D.8EHMDRGLCKCD%40neon.tech --- configure | 13 +--- configure.ac | 3 +- meson.build | 3 +- src/include/pg_config.h.in | 9 ++- src/include/port.h | 36 +++++++++ src/interfaces/ecpg/ecpglib/connect.c | 39 +--------- src/interfaces/ecpg/ecpglib/data.c | 2 +- src/interfaces/ecpg/ecpglib/descriptor.c | 37 --------- src/interfaces/ecpg/ecpglib/ecpglib_extern.h | 12 --- src/interfaces/ecpg/ecpglib/execute.c | 55 -------------- src/interfaces/ecpg/pgtypeslib/dt_common.c | 6 +- src/interfaces/ecpg/pgtypeslib/interval.c | 4 +- src/interfaces/ecpg/pgtypeslib/numeric.c | 2 +- src/port/Makefile | 1 + src/port/locale.c | 80 ++++++++++++++++++++ src/port/meson.build | 1 + src/port/snprintf.c | 55 ++++++++++++++ 17 files changed, 192 insertions(+), 166 deletions(-) create mode 100644 src/port/locale.c diff --git a/configure b/configure index f58eae1baa8..6a133c09d3b 100755 --- a/configure +++ b/configure @@ -15039,7 +15039,7 @@ fi LIBS_including_readline="$LIBS" LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'` -for ac_func in backtrace_symbols copyfile copy_file_range getifaddrs getpeerucred inet_pton kqueue mbstowcs_l memset_s posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast strchrnul strsignal syncfs sync_file_range uselocale wcstombs_l +for ac_func in backtrace_symbols copyfile copy_file_range getifaddrs getpeerucred inet_pton kqueue mbstowcs_l memset_s posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast snprintf_l strtod_l strchrnul strsignal syncfs sync_file_range uselocale wcstombs_l do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" @@ -15812,17 +15812,6 @@ fi # Win32 (really MinGW) support if test "$PORTNAME" = "win32"; then - for ac_func in _configthreadlocale -do : - ac_fn_c_check_func "$LINENO" "_configthreadlocale" "ac_cv_func__configthreadlocale" -if test "x$ac_cv_func__configthreadlocale" = xyes; then : - cat >>confdefs.h <<_ACEOF -#define HAVE__CONFIGTHREADLOCALE 1 -_ACEOF - -fi -done - case " $LIBOBJS " in *" dirmod.$ac_objext "* ) ;; *) LIBOBJS="$LIBOBJS dirmod.$ac_objext" diff --git a/configure.ac b/configure.ac index 82c5009e3e8..e73e0985862 100644 --- a/configure.ac +++ b/configure.ac @@ -1727,6 +1727,8 @@ AC_CHECK_FUNCS(m4_normalize([ pthread_is_threaded_np setproctitle setproctitle_fast + snprintf_l + strtod_l strchrnul strsignal syncfs @@ -1816,7 +1818,6 @@ fi # Win32 (really MinGW) support if test "$PORTNAME" = "win32"; then - AC_CHECK_FUNCS(_configthreadlocale) AC_LIBOBJ(dirmod) AC_LIBOBJ(kill) AC_LIBOBJ(open) diff --git a/meson.build b/meson.build index 5b0510cef78..dc557a317df 100644 --- a/meson.build +++ b/meson.build @@ -2614,7 +2614,6 @@ endif # XXX: Might be worth conditioning some checks on the OS, to avoid doing # unnecessary checks over and over, particularly on windows. func_checks = [ - ['_configthreadlocale', {'skip': host_system != 'windows'}], ['backtrace_symbols', {'dependencies': [execinfo_dep]}], ['clock_gettime', {'dependencies': [rt_dep], 'define': false}], ['copyfile'], @@ -2646,6 +2645,8 @@ func_checks = [ ['shm_open', {'dependencies': [rt_dep], 'define': false}], ['shm_unlink', {'dependencies': [rt_dep], 'define': false}], ['shmget', {'dependencies': [cygipc_dep], 'define': false}], + ['snprintf_l'], + ['strtod_l'], ['socket', {'dependencies': [socket_dep], 'define': false}], ['strchrnul'], ['strerror_r', {'dependencies': [thread_dep]}], diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index cdd9a6e9355..10c05775ff2 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -349,6 +349,9 @@ /* Define to 1 if you have the `setproctitle_fast' function. */ #undef HAVE_SETPROCTITLE_FAST +/* Define to 1 if you have the `snprintf_l' function. */ +#undef HAVE_SNPRINTF_L + /* Define to 1 if the system has the type `socklen_t'. */ #undef HAVE_SOCKLEN_T @@ -397,6 +400,9 @@ /* Define to 1 if you have the `strsignal' function. */ #undef HAVE_STRSIGNAL +/* Define to 1 if you have the `strtod_l' function. */ +#undef HAVE_STRTOD_L + /* Define to 1 if the system has the type `struct option'. */ #undef HAVE_STRUCT_OPTION @@ -529,9 +535,6 @@ /* Define to 1 if your compiler understands __builtin_unreachable. */ #undef HAVE__BUILTIN_UNREACHABLE -/* Define to 1 if you have the `_configthreadlocale' function. */ -#undef HAVE__CONFIGTHREADLOCALE - /* Define to 1 if you have __cpuid. */ #undef HAVE__CPUID diff --git a/src/include/port.h b/src/include/port.h index ba9ab0d34f4..f8344a1eb3d 100644 --- a/src/include/port.h +++ b/src/include/port.h @@ -217,6 +217,42 @@ extern int pg_fprintf(FILE *stream, const char *fmt,...) pg_attribute_printf(2, extern int pg_vprintf(const char *fmt, va_list args) pg_attribute_printf(1, 0); extern int pg_printf(const char *fmt,...) pg_attribute_printf(1, 2); +/* + * A couple of systems offer a fast constant locale_t value representing the + * "C" locale. We use that if possible, but fall back to creating a singleton + * object otherwise. To check that it is available, call pg_ensure_c_locale() + * and assume out of memory if it returns false. + */ +#ifdef LC_C_LOCALE +#define PG_C_LOCALE LC_C_LOCALE +#define pg_ensure_c_locale() true +#else +extern locale_t pg_get_c_locale(void); +#define PG_C_LOCALE pg_get_c_locale() +#define pg_ensure_c_locale() (PG_C_LOCALE != 0) +#endif + +#ifndef HAVE_STRTOD_L +/* + * POSIX doesn't define this function, but we can implement it with thread-safe + * save-and-restore. Not all systems have uselocale(), but the ones that don't + * have strtod_l(). + */ +static inline double +strtod_l(const char *nptr, char **endptr, locale_t loc) +{ +#ifdef WIN32 + return _strtod_l(nptr, endptr, loc); +#else + locale_t save = uselocale(loc); + double result = strtod(nptr, endptr); + + uselocale(save); + return result; +#endif +} +#endif + #ifndef WIN32 /* * We add a pg_ prefix as a warning that the Windows implementations have the diff --git a/src/interfaces/ecpg/ecpglib/connect.c b/src/interfaces/ecpg/ecpglib/connect.c index b24b310ce59..78e501a2656 100644 --- a/src/interfaces/ecpg/ecpglib/connect.c +++ b/src/interfaces/ecpg/ecpglib/connect.c @@ -10,10 +10,6 @@ #include "ecpgtype.h" #include "sqlca.h" -#ifdef HAVE_USELOCALE -locale_t ecpg_clocale = (locale_t) 0; -#endif - static pthread_mutex_t connections_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_key_t actual_connection_key; static pthread_once_t actual_connection_key_once = PTHREAD_ONCE_INIT; @@ -268,7 +264,7 @@ ECPGconnect(int lineno, int c, const char *name, const char *user, const char *p const char **conn_keywords; const char **conn_values; - if (sqlca == NULL) + if (sqlca == NULL || !pg_ensure_c_locale()) { ecpg_raise(lineno, ECPG_OUT_OF_MEMORY, ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL); @@ -483,39 +479,6 @@ ECPGconnect(int lineno, int c, const char *name, const char *user, const char *p /* add connection to our list */ pthread_mutex_lock(&connections_mutex); - /* - * ... but first, make certain we have created ecpg_clocale. Rely on - * holding connections_mutex to ensure this is done by only one thread. - */ -#ifdef HAVE_USELOCALE - if (!ecpg_clocale) - { - ecpg_clocale = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0); - if (!ecpg_clocale) - { - pthread_mutex_unlock(&connections_mutex); - 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/data.c b/src/interfaces/ecpg/ecpglib/data.c index fa562767585..856f4c9472d 100644 --- a/src/interfaces/ecpg/ecpglib/data.c +++ b/src/interfaces/ecpg/ecpglib/data.c @@ -466,7 +466,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno, pval++; if (!check_special_value(pval, &dres, &scan_length)) - dres = strtod(pval, &scan_length); + dres = strtod_l(pval, &scan_length, PG_C_LOCALE); if (isarray && *scan_length == '"') scan_length++; diff --git a/src/interfaces/ecpg/ecpglib/descriptor.c b/src/interfaces/ecpg/ecpglib/descriptor.c index ad279e245c4..855322e037f 100644 --- a/src/interfaces/ecpg/ecpglib/descriptor.c +++ b/src/interfaces/ecpg/ecpglib/descriptor.c @@ -475,46 +475,9 @@ ECPGget_desc(int lineno, const char *desc_name, int index,...) memset(&stmt, 0, sizeof stmt); stmt.lineno = lineno; - /* Make sure we do NOT honor the locale for numeric input */ - /* since the database gives the standard decimal point */ - /* (see comments in execute.c) */ -#ifdef HAVE_USELOCALE - - /* - * 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); -#endif - stmt.oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno); - setlocale(LC_NUMERIC, "C"); -#endif - /* desperate try to guess something sensible */ stmt.connection = ecpg_get_connection(NULL); ecpg_store_result(ECPGresult, index, &stmt, &data_var); - -#ifdef HAVE_USELOCALE - if (stmt.oldlocale != (locale_t) 0) - uselocale(stmt.oldlocale); -#else - if (stmt.oldlocale) - { - setlocale(LC_NUMERIC, stmt.oldlocale); - ecpg_free(stmt.oldlocale); - } -#ifdef HAVE__CONFIGTHREADLOCALE - if (stmt.oldthreadlocale != -1) - (void) _configthreadlocale(stmt.oldthreadlocale); -#endif -#endif } else if (data_var.ind_type != ECPGt_NO_INDICATOR && data_var.ind_pointer != NULL) diff --git a/src/interfaces/ecpg/ecpglib/ecpglib_extern.h b/src/interfaces/ecpg/ecpglib/ecpglib_extern.h index bad3cd9920b..40988d53575 100644 --- a/src/interfaces/ecpg/ecpglib/ecpglib_extern.h +++ b/src/interfaces/ecpg/ecpglib/ecpglib_extern.h @@ -56,10 +56,6 @@ 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,14 +69,6 @@ struct statement bool questionmarks; struct variable *inlist; struct variable *outlist; -#ifdef HAVE_USELOCALE - locale_t oldlocale; -#else - char *oldlocale; -#ifdef HAVE__CONFIGTHREADLOCALE - int oldthreadlocale; -#endif -#endif int nparams; char **paramvalues; int *paramlengths; diff --git a/src/interfaces/ecpg/ecpglib/execute.c b/src/interfaces/ecpg/ecpglib/execute.c index c578c21cf66..ec939d0f63a 100644 --- a/src/interfaces/ecpg/ecpglib/execute.c +++ b/src/interfaces/ecpg/ecpglib/execute.c @@ -101,9 +101,6 @@ free_statement(struct statement *stmt) free_variable(stmt->outlist); ecpg_free(stmt->command); ecpg_free(stmt->name); -#ifndef HAVE_USELOCALE - ecpg_free(stmt->oldlocale); -#endif ecpg_free(stmt); } @@ -1973,40 +1970,6 @@ ecpg_do_prologue(int lineno, const int compat, const int force_indicator, if (stmt == NULL) return false; - /* - * Make sure we do NOT honor the locale for numeric input/output since the - * database wants the standard decimal point. If available, use - * uselocale() for this because it's thread-safe. Windows doesn't have - * that, but it usually does have _configthreadlocale(). In some versions - * of MinGW, _configthreadlocale() exists but always returns -1 --- so - * treat that situation as if the function doesn't exist. - */ -#ifdef HAVE_USELOCALE - - /* - * 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); - return false; - } -#else -#ifdef HAVE__CONFIGTHREADLOCALE - stmt->oldthreadlocale = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); -#endif - stmt->oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno); - if (stmt->oldlocale == NULL) - { - ecpg_do_epilogue(stmt); - return false; - } - setlocale(LC_NUMERIC, "C"); -#endif - /* * If statement type is ECPGst_prepnormal we are supposed to prepare the * statement before executing them @@ -2213,24 +2176,6 @@ ecpg_do_epilogue(struct statement *stmt) if (stmt == NULL) return; -#ifdef HAVE_USELOCALE - if (stmt->oldlocale != (locale_t) 0) - uselocale(stmt->oldlocale); -#else - if (stmt->oldlocale) - setlocale(LC_NUMERIC, stmt->oldlocale); -#ifdef HAVE__CONFIGTHREADLOCALE - - /* - * This is a bit trickier than it looks: if we failed partway through - * statement initialization, oldthreadlocale could still be 0. But that's - * okay because a call with 0 is defined to be a no-op. - */ - if (stmt->oldthreadlocale != -1) - (void) _configthreadlocale(stmt->oldthreadlocale); -#endif -#endif - free_statement(stmt); } diff --git a/src/interfaces/ecpg/pgtypeslib/dt_common.c b/src/interfaces/ecpg/pgtypeslib/dt_common.c index c4119ab7932..f8349b66ce9 100644 --- a/src/interfaces/ecpg/pgtypeslib/dt_common.c +++ b/src/interfaces/ecpg/pgtypeslib/dt_common.c @@ -1218,7 +1218,7 @@ DecodeNumber(int flen, char *str, int fmask, return DecodeNumberField(flen, str, (fmask | DTK_DATE_M), tmask, tm, fsec, is2digits); - *fsec = strtod(cp, &cp); + *fsec = strtod_l(cp, &cp, PG_C_LOCALE); if (*cp != '\0') return -1; } @@ -2030,7 +2030,7 @@ DecodeDateTime(char **field, int *ftype, int nf, { double frac; - frac = strtod(cp, &cp); + frac = strtod_l(cp, &cp, PG_C_LOCALE); if (*cp != '\0') return -1; *fsec = frac * 1000000; @@ -2054,7 +2054,7 @@ DecodeDateTime(char **field, int *ftype, int nf, { double time; - time = strtod(cp, &cp); + time = strtod_l(cp, &cp, PG_C_LOCALE); if (*cp != '\0') return -1; diff --git a/src/interfaces/ecpg/pgtypeslib/interval.c b/src/interfaces/ecpg/pgtypeslib/interval.c index 936a6883816..155c6cc7770 100644 --- a/src/interfaces/ecpg/pgtypeslib/interval.c +++ b/src/interfaces/ecpg/pgtypeslib/interval.c @@ -60,7 +60,7 @@ ParseISO8601Number(const char *str, char **endptr, int *ipart, double *fpart) if (!(isdigit((unsigned char) *str) || *str == '-' || *str == '.')) return DTERR_BAD_FORMAT; errno = 0; - val = strtod(str, endptr); + val = strtod_l(str, endptr, PG_C_LOCALE); /* did we not see anything that looks like a double? */ if (*endptr == str || errno != 0) return DTERR_BAD_FORMAT; @@ -455,7 +455,7 @@ DecodeInterval(char **field, int *ftype, int nf, /* int range, */ else if (*cp == '.') { errno = 0; - fval = strtod(cp, &cp); + fval = strtod_l(cp, &cp, PG_C_LOCALE); if (*cp != '\0' || errno != 0) return DTERR_BAD_FORMAT; diff --git a/src/interfaces/ecpg/pgtypeslib/numeric.c b/src/interfaces/ecpg/pgtypeslib/numeric.c index 35e7b92da40..49938543d03 100644 --- a/src/interfaces/ecpg/pgtypeslib/numeric.c +++ b/src/interfaces/ecpg/pgtypeslib/numeric.c @@ -1455,7 +1455,7 @@ numericvar_to_double(numeric *var, double *dp) * strtod does not reset errno to 0 in case of success. */ errno = 0; - val = strtod(tmp, &endptr); + val = strtod_l(tmp, &endptr, PG_C_LOCALE); if (errno == ERANGE) { free(tmp); diff --git a/src/port/Makefile b/src/port/Makefile index 366c814bd92..70d63963611 100644 --- a/src/port/Makefile +++ b/src/port/Makefile @@ -41,6 +41,7 @@ OBJS = \ bsearch_arg.o \ chklocale.o \ inet_net_ntop.o \ + locale.o \ noblock.o \ path.o \ pg_bitutils.o \ diff --git a/src/port/locale.c b/src/port/locale.c new file mode 100644 index 00000000000..387fb4a107a --- /dev/null +++ b/src/port/locale.c @@ -0,0 +1,80 @@ +/*------------------------------------------------------------------------- + * + * locale.c + * Helper routines for thread-safe system locale usage. + * + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/port/locale.c + * + *------------------------------------------------------------------------- + */ + +#include "c.h" + +#ifndef WIN32 +#include <pthread.h> +#else +#include <synchapi.h> +#endif + +/* A process-lifetime singleton, allocated on first need. */ +static locale_t c_locale; + +#ifndef WIN32 +static void +init_c_locale_once(void) +{ + c_locale = newlocale(LC_ALL, "C", NULL); +} +#else +static BOOL +init_c_locale_once(PINIT_ONCE once, PVOID parameter, PVOID *context) +{ + c_locale = _create_locale(LC_ALL, "C"); + return true; +} +#endif + +/* + * Access a process-lifetime singleton locale_t object. Use the macro + * PG_C_LOCALE instead of calling this directly, as it can skip the function + * call on some systems. + */ +locale_t +pg_get_c_locale(void) +{ + /* + * Fast path if already initialized. This assumes that we can read a + * locale_t (in practice, a pointer) without tearing in a multi-threaded + * program. + */ + if (c_locale != (locale_t) 0) + return c_locale; + + /* Make a locale_t. It will live until process exit. */ + { +#ifndef WIN32 + static pthread_once_t once = PTHREAD_ONCE_INIT; + + pthread_once(&once, init_c_locale_once); +#else + static INIT_ONCE once; + InitOnceExecuteOnce(&once, init_c_locale_once, NULL, NULL); +#endif + } + + /* + * It's possible that the allocation of the locale failed due to low + * memory, and then (locale_t) 0 will be returned. Users of PG_C_LOCALE + * should defend against that by checking pg_ensure_c_locale() at a + * convenient time, so that they can treat it as a simple constant after + * that. + */ + + return c_locale; +} diff --git a/src/port/meson.build b/src/port/meson.build index 83a06325209..93f6158c307 100644 --- a/src/port/meson.build +++ b/src/port/meson.build @@ -5,6 +5,7 @@ pgport_sources = [ 'chklocale.c', 'inet_net_ntop.c', 'noblock.c', + 'locale.c', 'path.c', 'pg_bitutils.c', 'pg_popcount_avx512.c', diff --git a/src/port/snprintf.c b/src/port/snprintf.c index 884f0262dd1..3cc49f01ea6 100644 --- a/src/port/snprintf.c +++ b/src/port/snprintf.c @@ -109,6 +109,36 @@ #undef vprintf #undef printf +#if defined(FRONTEND) +/* + * Frontend code might be multi-threaded. When calling the system snprintf() + * for floats, we have to use either the non-standard snprintf_l() variant, or + * save-and-restore the calling thread's locale using uselocale(), depending on + * availability. + */ +#if defined(HAVE_SNPRINTF_L) +/* At least macOS and the BSDs have the snprintf_l() extension. */ +#define USE_SNPRINTF_L +#elif defined(WIN32) +/* Windows has a version with a different name and argument order. */ +#define snprintf_l(str, size, loc, format, ...) _snprintf_l(str, size, format, loc, __VA_ARGS__) +#define USE_SNPRINTF_L +#else +/* We have to do a thread-safe save-and-restore around snprintf(). */ +#define NEED_USE_LOCALE +#endif +#else +/* + * Backend code doesn't need to worry about locales here, because LC_NUMERIC is + * set to "C" in main() and doesn't change. Plain old snprintf() is always OK + * without uselocale(). + * + * XXX If the backend were multithreaded, we would have to be 100% certain that + * no one is calling setlocale() concurrently, even transiently, to be able to + * keep this small optimization. + */ +#endif + /* * Info about where the formatted output is going. * @@ -1184,6 +1214,9 @@ fmtfloat(double value, char type, int forcesign, int leftjust, * according to == but not according to memcmp. */ static const double dzero = 0.0; +#ifdef NEED_USE_LOCALE + locale_t save_locale = uselocale(PG_C_LOCALE); +#endif if (adjust_sign((value < 0.0 || (value == 0.0 && @@ -1205,7 +1238,11 @@ fmtfloat(double value, char type, int forcesign, int leftjust, fmt[2] = '*'; fmt[3] = type; fmt[4] = '\0'; +#ifdef USE_SNPRINTF_L + vallen = snprintf_l(convert, sizeof(convert), PG_C_LOCALE, fmt, prec, value); +#else vallen = snprintf(convert, sizeof(convert), fmt, prec, value); +#endif } else { @@ -1214,6 +1251,11 @@ fmtfloat(double value, char type, int forcesign, int leftjust, fmt[2] = '\0'; vallen = snprintf(convert, sizeof(convert), fmt, value); } + +#ifdef NEED_USE_LOCALE + uselocale(save_locale); +#endif + if (vallen < 0) goto fail; @@ -1336,12 +1378,25 @@ pg_strfromd(char *str, size_t count, int precision, double value) } else { +#ifdef NEED_USE_LOCALE + locale_t save_locale = uselocale(PG_C_LOCALE); +#endif + fmt[0] = '%'; fmt[1] = '.'; fmt[2] = '*'; fmt[3] = 'g'; fmt[4] = '\0'; +#ifdef USE_SNPRINTF_L + vallen = snprintf_l(convert, sizeof(convert), PG_C_LOCALE, fmt, precision, value); +#else vallen = snprintf(convert, sizeof(convert), fmt, precision, value); +#endif + +#ifdef NEED_USE_LOCALE + uselocale(save_locale); +#endif + if (vallen < 0) { target.failed = true; -- 2.47.0 ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2024-11-14 07:48 Thomas Munro <[email protected]> parent: Peter Eisentraut <[email protected]> 2 siblings, 2 replies; 41+ messages in thread From: Thomas Munro @ 2024-11-14 07:48 UTC (permalink / raw) To: Peter Eisentraut <[email protected]>; +Cc: Tristan Partin <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers On Thu, Aug 29, 2024 at 6:50 AM Peter Eisentraut <[email protected]> wrote: > I took a look at this. It was quite a complicated discussion that led > to this, but I agree with the solution that was arrived at. Sorry I missed/forgot this prior feedback when posting earlier today. Responses to the points you raised here: > Also, you're removing the configure test for _configthreadlocale(). > Presumably because you're removing all the uses. But wouldn't we need > that back later in the backend maybe? Or is that test even relevant > anymore, that is, are there Windows versions that don't have it? Yeah, this removes the uses. As it happens, in the thread about localeconv() I am also proposing to add a new user of it[1], but even then I don't think we'd need the configure test, because all relevant systems have it. It's in ucrt, the modern C runtime that replaced the old msvcrt, as used by Visual Studio 2015+ and Windows 10+, and also by MinGW in recent years. I think it was also in some versions of msvcrt, but MinGW had issues with that apparently. It's pretty confusing, so I went looking for proof and clues about the history. Here's a thread about 2019 animal jacana lacking the function: https://www.postgresql.org/message-id/flat/31420.1547783697%40sss.pgh.pa.us Once jacana was upgraded, it started finding the function, but next failed with -1 at runtime, cf 2cf91ccb. Grovelling through MinGW-w64's github, it looks like they added a dummy function that fails with -1 in 2016: https://github.com/mingw-w64/mingw-w64/blob/master/mingw-w64-crt/misc/_configthreadlocale.c#L11 They only seem to interpose the dummy function when building against msvcrt. https://github.com/mingw-w64/mingw-w64/blob/master/mingw-w64-crt/Makefile.am#L298 The default and recommended runtime library as of a few years ago is ucrt: https://www.msys2.org/docs/environments/#msvcrt-vs-ucrt The three MinGW environments we test today are using ucrt, and configure detects the symbol on all. Namely: fairwren (msys2/mingw64), the CI mingw64 task and the mingw cross-build that runs on Linux in the CI CompilerWarnings task. As far as I know these are the reasons for, and mechanism by which, we keep MinGW support working. We have no policy requiring arbitrary old MinGW systems work, and we wouldn't know anyway. I was paranoid that it might still be a dummy function, so I tried running a simple program on our CI with the MinGW compiler to confirm it's reaching the real library: int main() { printf("hello world\n"); printf("got %d\n", _configthreadlocale(_ENABLE_PER_THREAD_LOCALE)); printf("got %d\n", _configthreadlocale(_DISABLE_PER_THREAD_LOCALE)); } [06:16:39.580] c:\cirrus>call C:\msys64\usr\bin\bash.exe -l -c "gcc doit.c -o c:/doit" ... [06:16:41.474] c:\cirrus>call c:\doit.exe [06:16:41.482] hello world [06:16:41.482] got 2 [06:16:41.482] got 1 It's not clear to me whether or when we might have made any changes already that prevented PostgreSQL from working with msvcrt, of course, but I'm pretty sure if someone proposed that we *should* support it, we'd reject the proposal. That'd effectively be a different, obsolete one corresponding to Windows versions we've dropped, which wouldn't make any sense. Other mentions of msvcrt in win32env.c and pg_locale.c may also be effectively obsolete now too. > For Windows, we already have things like > > #define strcoll_l _strcoll_l > > in src/include/port/win32_port.h, so it would seem more sensible to add > strtod_l to that list, instead of in port.h. OK, I moved that line into that list. I left the implementation for Unixen (at a guess: probably just Solaris?) there. > The error handling with pg_ensure_c_locale(), that's the sort of thing > I'm afraid will be hard to test or even know how it will behave. And it > creates this weird coupling between pgtypeslib and ecpglib that you > mentioned earlier. And if there are other users of PG_C_LOCALE in the > future, there will be similar questions about the proper initialization > and error handling sequence. Ack. The coupling does become at least less weird (currently it must be capable of giving the wrong answers reliably if called directly I think, no?) and weaker, but I acknowledge that it's still non-ideal that out-of-memory would be handled nicely only if you used ecpg first, and subtle misbehaviour would ensure otherwise, and future users face the same sort of problem unless they design in a reasonable initialisation place that could check pg_ensure_c_locale() nicely. Classic retro-fitting problem, though. > I would consider instead making a local static variable in each function > that needs this. For example, numericvar_to_double() might do > > { > static locale_t c_locale; > > if (!c_locale) > { > c_locale = pg_get_c_locale(); > if (!c_locale) > return -1; /* local error reporting convention */ > } > > ... > } > > This is a bit more code in total, but then you only initialize what you > need and you can handle errors locally. Hmm. Seems like quite a lot of duplication, and also assumes that we can actually unwind and handle the error in a reasonable way at all callsites. Let me think about that some more... [1] https://www.postgresql.org/message-id/flat/CA+hUKGJqVe0+Pv9dvC9dSums_PXxGo9SWcxYAMBguWJUGbWz-A@mail.... Attachments: [text/x-patch] v6-0001-Tidy-up-locale-thread-safety-in-ECPG-library.patch (24.4K, ../../CA+hUKG+fApBC7Ziq2HP6K-6ymD=AafyvYQq9HGfyRCoBw-PGtQ@mail.gmail.com/2-v6-0001-Tidy-up-locale-thread-safety-in-ECPG-library.patch) download | inline diff: From 2965979a7180265a8df91adb45c1ce32f05b190f Mon Sep 17 00:00:00 2001 From: Thomas Munro <[email protected]> Date: Sat, 10 Aug 2024 11:01:21 +1200 Subject: [PATCH v6] Tidy up locale thread safety in ECPG library. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove setlocale() and _configthreadlocal() as fallback strategy on systems that don't have uselocale(), where ECPG tries to control LC_NUMERIC formatting on input and output of floating point numbers. It was probably broken on some systems (NetBSD), and the code was also quite messy and complicated, with obsolete configure tests (Windows). It was also arguably broken, or at least had unstated environmental requirements, if pgtypeslib code was called directly. Instead, introduce PG_C_LOCALE to refer to the "C" locale as a locale_t value. It maps to the special constant LC_C_LOCALE when defined by libc (macOS, NetBSD), or otherwise uses a process-lifetime locale_t that is allocated on first use, just as ECPG previously did itself. The new replacement might be more widely useful. Then change the float parsing and printing code to pass that to _l() functions where appropriate. Unfortunately the portability of those functions is a bit complicated. First, many obvious and useful _l() functions are missing from POSIX, though most standard libraries define some of them anyway. Second, although the thread-safe save/restore technique can be used to replace the missing ones, Windows and NetBSD refused to implement standard uselocale(). They might have a point: "wide scope" uselocale() is hard to combine with other code and error-prone, especially in library code. Luckily they have the _l() functions we want so far anyway. So we have to be prepared for both ways of doing things: 1. In ECPG, use strtod_l() for parsing, and supply a port.h replacement using uselocale() over a limited scope if missing. 2. Inside our own snprintf.c, use three different approaches to format floats. For frontend code, call libc's snprintf_l(), or wrap libc's snprintf() in uselocale() if it's missing. For backend code, snprintf.c can keep assuming that the global locale's LC_NUMERIC is "C" and call libc's snprintf() without change, for now. (It might eventually be possible to call our in-tree Ryū routines to display floats in snprintf.c, given the C-locale-always remit of our in-tree snprintf(), but this patch doesn't risk changing anything that complicated.) Reviewed-by: Peter Eisentraut <[email protected]> Reviewed-by: Tristan Partin <[email protected]> Discussion: https://postgr.es/m/CWZBBRR6YA8D.8EHMDRGLCKCD%40neon.tech --- configure | 13 +--- configure.ac | 3 +- meson.build | 3 +- src/include/pg_config.h.in | 9 ++- src/include/port.h | 31 ++++++++ src/include/port/win32_port.h | 1 + src/interfaces/ecpg/ecpglib/connect.c | 39 +--------- src/interfaces/ecpg/ecpglib/data.c | 2 +- src/interfaces/ecpg/ecpglib/descriptor.c | 37 --------- src/interfaces/ecpg/ecpglib/ecpglib_extern.h | 12 --- src/interfaces/ecpg/ecpglib/execute.c | 55 -------------- src/interfaces/ecpg/pgtypeslib/dt_common.c | 6 +- src/interfaces/ecpg/pgtypeslib/interval.c | 4 +- src/interfaces/ecpg/pgtypeslib/numeric.c | 2 +- src/port/Makefile | 1 + src/port/locale.c | 80 ++++++++++++++++++++ src/port/meson.build | 1 + src/port/snprintf.c | 55 ++++++++++++++ 18 files changed, 188 insertions(+), 166 deletions(-) create mode 100644 src/port/locale.c diff --git a/configure b/configure index f58eae1baa8..6a133c09d3b 100755 --- a/configure +++ b/configure @@ -15039,7 +15039,7 @@ fi LIBS_including_readline="$LIBS" LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'` -for ac_func in backtrace_symbols copyfile copy_file_range getifaddrs getpeerucred inet_pton kqueue mbstowcs_l memset_s posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast strchrnul strsignal syncfs sync_file_range uselocale wcstombs_l +for ac_func in backtrace_symbols copyfile copy_file_range getifaddrs getpeerucred inet_pton kqueue mbstowcs_l memset_s posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast snprintf_l strtod_l strchrnul strsignal syncfs sync_file_range uselocale wcstombs_l do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" @@ -15812,17 +15812,6 @@ fi # Win32 (really MinGW) support if test "$PORTNAME" = "win32"; then - for ac_func in _configthreadlocale -do : - ac_fn_c_check_func "$LINENO" "_configthreadlocale" "ac_cv_func__configthreadlocale" -if test "x$ac_cv_func__configthreadlocale" = xyes; then : - cat >>confdefs.h <<_ACEOF -#define HAVE__CONFIGTHREADLOCALE 1 -_ACEOF - -fi -done - case " $LIBOBJS " in *" dirmod.$ac_objext "* ) ;; *) LIBOBJS="$LIBOBJS dirmod.$ac_objext" diff --git a/configure.ac b/configure.ac index 82c5009e3e8..e73e0985862 100644 --- a/configure.ac +++ b/configure.ac @@ -1727,6 +1727,8 @@ AC_CHECK_FUNCS(m4_normalize([ pthread_is_threaded_np setproctitle setproctitle_fast + snprintf_l + strtod_l strchrnul strsignal syncfs @@ -1816,7 +1818,6 @@ fi # Win32 (really MinGW) support if test "$PORTNAME" = "win32"; then - AC_CHECK_FUNCS(_configthreadlocale) AC_LIBOBJ(dirmod) AC_LIBOBJ(kill) AC_LIBOBJ(open) diff --git a/meson.build b/meson.build index 5b0510cef78..dc557a317df 100644 --- a/meson.build +++ b/meson.build @@ -2614,7 +2614,6 @@ endif # XXX: Might be worth conditioning some checks on the OS, to avoid doing # unnecessary checks over and over, particularly on windows. func_checks = [ - ['_configthreadlocale', {'skip': host_system != 'windows'}], ['backtrace_symbols', {'dependencies': [execinfo_dep]}], ['clock_gettime', {'dependencies': [rt_dep], 'define': false}], ['copyfile'], @@ -2646,6 +2645,8 @@ func_checks = [ ['shm_open', {'dependencies': [rt_dep], 'define': false}], ['shm_unlink', {'dependencies': [rt_dep], 'define': false}], ['shmget', {'dependencies': [cygipc_dep], 'define': false}], + ['snprintf_l'], + ['strtod_l'], ['socket', {'dependencies': [socket_dep], 'define': false}], ['strchrnul'], ['strerror_r', {'dependencies': [thread_dep]}], diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index cdd9a6e9355..10c05775ff2 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -349,6 +349,9 @@ /* Define to 1 if you have the `setproctitle_fast' function. */ #undef HAVE_SETPROCTITLE_FAST +/* Define to 1 if you have the `snprintf_l' function. */ +#undef HAVE_SNPRINTF_L + /* Define to 1 if the system has the type `socklen_t'. */ #undef HAVE_SOCKLEN_T @@ -397,6 +400,9 @@ /* Define to 1 if you have the `strsignal' function. */ #undef HAVE_STRSIGNAL +/* Define to 1 if you have the `strtod_l' function. */ +#undef HAVE_STRTOD_L + /* Define to 1 if the system has the type `struct option'. */ #undef HAVE_STRUCT_OPTION @@ -529,9 +535,6 @@ /* Define to 1 if your compiler understands __builtin_unreachable. */ #undef HAVE__BUILTIN_UNREACHABLE -/* Define to 1 if you have the `_configthreadlocale' function. */ -#undef HAVE__CONFIGTHREADLOCALE - /* Define to 1 if you have __cpuid. */ #undef HAVE__CPUID diff --git a/src/include/port.h b/src/include/port.h index ba9ab0d34f4..48292cb28f8 100644 --- a/src/include/port.h +++ b/src/include/port.h @@ -217,6 +217,37 @@ extern int pg_fprintf(FILE *stream, const char *fmt,...) pg_attribute_printf(2, extern int pg_vprintf(const char *fmt, va_list args) pg_attribute_printf(1, 0); extern int pg_printf(const char *fmt,...) pg_attribute_printf(1, 2); +/* + * A couple of systems offer a fast constant locale_t value representing the + * "C" locale. We use that if possible, but fall back to creating a singleton + * object otherwise. To check that it is available, call pg_ensure_c_locale() + * and assume out of memory if it returns false. + */ +#ifdef LC_C_LOCALE +#define PG_C_LOCALE LC_C_LOCALE +#define pg_ensure_c_locale() true +#else +extern locale_t pg_get_c_locale(void); +#define PG_C_LOCALE pg_get_c_locale() +#define pg_ensure_c_locale() (PG_C_LOCALE != 0) +#endif + +#if !defined(HAVE_STRTOD_L) && !defined(WIN32) +/* + * POSIX doesn't define this function, but we can implement it with thread-safe + * save-and-restore. + */ +static inline double +strtod_l(const char *nptr, char **endptr, locale_t loc) +{ + locale_t save = uselocale(loc); + double result = strtod(nptr, endptr); + + uselocale(save); + return result; +} +#endif + #ifndef WIN32 /* * We add a pg_ prefix as a warning that the Windows implementations have the diff --git a/src/include/port/win32_port.h b/src/include/port/win32_port.h index 7789e0431aa..8dbde7da954 100644 --- a/src/include/port/win32_port.h +++ b/src/include/port/win32_port.h @@ -463,6 +463,7 @@ extern int _pglstat64(const char *name, struct stat *buf); #define isspace_l _isspace_l #define iswspace_l _iswspace_l #define strcoll_l _strcoll_l +#define strtod_l _strtod_l #define strxfrm_l _strxfrm_l #define wcscoll_l _wcscoll_l diff --git a/src/interfaces/ecpg/ecpglib/connect.c b/src/interfaces/ecpg/ecpglib/connect.c index b24b310ce59..78e501a2656 100644 --- a/src/interfaces/ecpg/ecpglib/connect.c +++ b/src/interfaces/ecpg/ecpglib/connect.c @@ -10,10 +10,6 @@ #include "ecpgtype.h" #include "sqlca.h" -#ifdef HAVE_USELOCALE -locale_t ecpg_clocale = (locale_t) 0; -#endif - static pthread_mutex_t connections_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_key_t actual_connection_key; static pthread_once_t actual_connection_key_once = PTHREAD_ONCE_INIT; @@ -268,7 +264,7 @@ ECPGconnect(int lineno, int c, const char *name, const char *user, const char *p const char **conn_keywords; const char **conn_values; - if (sqlca == NULL) + if (sqlca == NULL || !pg_ensure_c_locale()) { ecpg_raise(lineno, ECPG_OUT_OF_MEMORY, ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL); @@ -483,39 +479,6 @@ ECPGconnect(int lineno, int c, const char *name, const char *user, const char *p /* add connection to our list */ pthread_mutex_lock(&connections_mutex); - /* - * ... but first, make certain we have created ecpg_clocale. Rely on - * holding connections_mutex to ensure this is done by only one thread. - */ -#ifdef HAVE_USELOCALE - if (!ecpg_clocale) - { - ecpg_clocale = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0); - if (!ecpg_clocale) - { - pthread_mutex_unlock(&connections_mutex); - 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/data.c b/src/interfaces/ecpg/ecpglib/data.c index fa562767585..856f4c9472d 100644 --- a/src/interfaces/ecpg/ecpglib/data.c +++ b/src/interfaces/ecpg/ecpglib/data.c @@ -466,7 +466,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno, pval++; if (!check_special_value(pval, &dres, &scan_length)) - dres = strtod(pval, &scan_length); + dres = strtod_l(pval, &scan_length, PG_C_LOCALE); if (isarray && *scan_length == '"') scan_length++; diff --git a/src/interfaces/ecpg/ecpglib/descriptor.c b/src/interfaces/ecpg/ecpglib/descriptor.c index ad279e245c4..855322e037f 100644 --- a/src/interfaces/ecpg/ecpglib/descriptor.c +++ b/src/interfaces/ecpg/ecpglib/descriptor.c @@ -475,46 +475,9 @@ ECPGget_desc(int lineno, const char *desc_name, int index,...) memset(&stmt, 0, sizeof stmt); stmt.lineno = lineno; - /* Make sure we do NOT honor the locale for numeric input */ - /* since the database gives the standard decimal point */ - /* (see comments in execute.c) */ -#ifdef HAVE_USELOCALE - - /* - * 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); -#endif - stmt.oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno); - setlocale(LC_NUMERIC, "C"); -#endif - /* desperate try to guess something sensible */ stmt.connection = ecpg_get_connection(NULL); ecpg_store_result(ECPGresult, index, &stmt, &data_var); - -#ifdef HAVE_USELOCALE - if (stmt.oldlocale != (locale_t) 0) - uselocale(stmt.oldlocale); -#else - if (stmt.oldlocale) - { - setlocale(LC_NUMERIC, stmt.oldlocale); - ecpg_free(stmt.oldlocale); - } -#ifdef HAVE__CONFIGTHREADLOCALE - if (stmt.oldthreadlocale != -1) - (void) _configthreadlocale(stmt.oldthreadlocale); -#endif -#endif } else if (data_var.ind_type != ECPGt_NO_INDICATOR && data_var.ind_pointer != NULL) diff --git a/src/interfaces/ecpg/ecpglib/ecpglib_extern.h b/src/interfaces/ecpg/ecpglib/ecpglib_extern.h index bad3cd9920b..40988d53575 100644 --- a/src/interfaces/ecpg/ecpglib/ecpglib_extern.h +++ b/src/interfaces/ecpg/ecpglib/ecpglib_extern.h @@ -56,10 +56,6 @@ 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,14 +69,6 @@ struct statement bool questionmarks; struct variable *inlist; struct variable *outlist; -#ifdef HAVE_USELOCALE - locale_t oldlocale; -#else - char *oldlocale; -#ifdef HAVE__CONFIGTHREADLOCALE - int oldthreadlocale; -#endif -#endif int nparams; char **paramvalues; int *paramlengths; diff --git a/src/interfaces/ecpg/ecpglib/execute.c b/src/interfaces/ecpg/ecpglib/execute.c index c578c21cf66..ec939d0f63a 100644 --- a/src/interfaces/ecpg/ecpglib/execute.c +++ b/src/interfaces/ecpg/ecpglib/execute.c @@ -101,9 +101,6 @@ free_statement(struct statement *stmt) free_variable(stmt->outlist); ecpg_free(stmt->command); ecpg_free(stmt->name); -#ifndef HAVE_USELOCALE - ecpg_free(stmt->oldlocale); -#endif ecpg_free(stmt); } @@ -1973,40 +1970,6 @@ ecpg_do_prologue(int lineno, const int compat, const int force_indicator, if (stmt == NULL) return false; - /* - * Make sure we do NOT honor the locale for numeric input/output since the - * database wants the standard decimal point. If available, use - * uselocale() for this because it's thread-safe. Windows doesn't have - * that, but it usually does have _configthreadlocale(). In some versions - * of MinGW, _configthreadlocale() exists but always returns -1 --- so - * treat that situation as if the function doesn't exist. - */ -#ifdef HAVE_USELOCALE - - /* - * 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); - return false; - } -#else -#ifdef HAVE__CONFIGTHREADLOCALE - stmt->oldthreadlocale = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); -#endif - stmt->oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno); - if (stmt->oldlocale == NULL) - { - ecpg_do_epilogue(stmt); - return false; - } - setlocale(LC_NUMERIC, "C"); -#endif - /* * If statement type is ECPGst_prepnormal we are supposed to prepare the * statement before executing them @@ -2213,24 +2176,6 @@ ecpg_do_epilogue(struct statement *stmt) if (stmt == NULL) return; -#ifdef HAVE_USELOCALE - if (stmt->oldlocale != (locale_t) 0) - uselocale(stmt->oldlocale); -#else - if (stmt->oldlocale) - setlocale(LC_NUMERIC, stmt->oldlocale); -#ifdef HAVE__CONFIGTHREADLOCALE - - /* - * This is a bit trickier than it looks: if we failed partway through - * statement initialization, oldthreadlocale could still be 0. But that's - * okay because a call with 0 is defined to be a no-op. - */ - if (stmt->oldthreadlocale != -1) - (void) _configthreadlocale(stmt->oldthreadlocale); -#endif -#endif - free_statement(stmt); } diff --git a/src/interfaces/ecpg/pgtypeslib/dt_common.c b/src/interfaces/ecpg/pgtypeslib/dt_common.c index c4119ab7932..f8349b66ce9 100644 --- a/src/interfaces/ecpg/pgtypeslib/dt_common.c +++ b/src/interfaces/ecpg/pgtypeslib/dt_common.c @@ -1218,7 +1218,7 @@ DecodeNumber(int flen, char *str, int fmask, return DecodeNumberField(flen, str, (fmask | DTK_DATE_M), tmask, tm, fsec, is2digits); - *fsec = strtod(cp, &cp); + *fsec = strtod_l(cp, &cp, PG_C_LOCALE); if (*cp != '\0') return -1; } @@ -2030,7 +2030,7 @@ DecodeDateTime(char **field, int *ftype, int nf, { double frac; - frac = strtod(cp, &cp); + frac = strtod_l(cp, &cp, PG_C_LOCALE); if (*cp != '\0') return -1; *fsec = frac * 1000000; @@ -2054,7 +2054,7 @@ DecodeDateTime(char **field, int *ftype, int nf, { double time; - time = strtod(cp, &cp); + time = strtod_l(cp, &cp, PG_C_LOCALE); if (*cp != '\0') return -1; diff --git a/src/interfaces/ecpg/pgtypeslib/interval.c b/src/interfaces/ecpg/pgtypeslib/interval.c index 936a6883816..155c6cc7770 100644 --- a/src/interfaces/ecpg/pgtypeslib/interval.c +++ b/src/interfaces/ecpg/pgtypeslib/interval.c @@ -60,7 +60,7 @@ ParseISO8601Number(const char *str, char **endptr, int *ipart, double *fpart) if (!(isdigit((unsigned char) *str) || *str == '-' || *str == '.')) return DTERR_BAD_FORMAT; errno = 0; - val = strtod(str, endptr); + val = strtod_l(str, endptr, PG_C_LOCALE); /* did we not see anything that looks like a double? */ if (*endptr == str || errno != 0) return DTERR_BAD_FORMAT; @@ -455,7 +455,7 @@ DecodeInterval(char **field, int *ftype, int nf, /* int range, */ else if (*cp == '.') { errno = 0; - fval = strtod(cp, &cp); + fval = strtod_l(cp, &cp, PG_C_LOCALE); if (*cp != '\0' || errno != 0) return DTERR_BAD_FORMAT; diff --git a/src/interfaces/ecpg/pgtypeslib/numeric.c b/src/interfaces/ecpg/pgtypeslib/numeric.c index 35e7b92da40..49938543d03 100644 --- a/src/interfaces/ecpg/pgtypeslib/numeric.c +++ b/src/interfaces/ecpg/pgtypeslib/numeric.c @@ -1455,7 +1455,7 @@ numericvar_to_double(numeric *var, double *dp) * strtod does not reset errno to 0 in case of success. */ errno = 0; - val = strtod(tmp, &endptr); + val = strtod_l(tmp, &endptr, PG_C_LOCALE); if (errno == ERANGE) { free(tmp); diff --git a/src/port/Makefile b/src/port/Makefile index 366c814bd92..70d63963611 100644 --- a/src/port/Makefile +++ b/src/port/Makefile @@ -41,6 +41,7 @@ OBJS = \ bsearch_arg.o \ chklocale.o \ inet_net_ntop.o \ + locale.o \ noblock.o \ path.o \ pg_bitutils.o \ diff --git a/src/port/locale.c b/src/port/locale.c new file mode 100644 index 00000000000..387fb4a107a --- /dev/null +++ b/src/port/locale.c @@ -0,0 +1,80 @@ +/*------------------------------------------------------------------------- + * + * locale.c + * Helper routines for thread-safe system locale usage. + * + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/port/locale.c + * + *------------------------------------------------------------------------- + */ + +#include "c.h" + +#ifndef WIN32 +#include <pthread.h> +#else +#include <synchapi.h> +#endif + +/* A process-lifetime singleton, allocated on first need. */ +static locale_t c_locale; + +#ifndef WIN32 +static void +init_c_locale_once(void) +{ + c_locale = newlocale(LC_ALL, "C", NULL); +} +#else +static BOOL +init_c_locale_once(PINIT_ONCE once, PVOID parameter, PVOID *context) +{ + c_locale = _create_locale(LC_ALL, "C"); + return true; +} +#endif + +/* + * Access a process-lifetime singleton locale_t object. Use the macro + * PG_C_LOCALE instead of calling this directly, as it can skip the function + * call on some systems. + */ +locale_t +pg_get_c_locale(void) +{ + /* + * Fast path if already initialized. This assumes that we can read a + * locale_t (in practice, a pointer) without tearing in a multi-threaded + * program. + */ + if (c_locale != (locale_t) 0) + return c_locale; + + /* Make a locale_t. It will live until process exit. */ + { +#ifndef WIN32 + static pthread_once_t once = PTHREAD_ONCE_INIT; + + pthread_once(&once, init_c_locale_once); +#else + static INIT_ONCE once; + InitOnceExecuteOnce(&once, init_c_locale_once, NULL, NULL); +#endif + } + + /* + * It's possible that the allocation of the locale failed due to low + * memory, and then (locale_t) 0 will be returned. Users of PG_C_LOCALE + * should defend against that by checking pg_ensure_c_locale() at a + * convenient time, so that they can treat it as a simple constant after + * that. + */ + + return c_locale; +} diff --git a/src/port/meson.build b/src/port/meson.build index 83a06325209..93f6158c307 100644 --- a/src/port/meson.build +++ b/src/port/meson.build @@ -5,6 +5,7 @@ pgport_sources = [ 'chklocale.c', 'inet_net_ntop.c', 'noblock.c', + 'locale.c', 'path.c', 'pg_bitutils.c', 'pg_popcount_avx512.c', diff --git a/src/port/snprintf.c b/src/port/snprintf.c index 884f0262dd1..3cc49f01ea6 100644 --- a/src/port/snprintf.c +++ b/src/port/snprintf.c @@ -109,6 +109,36 @@ #undef vprintf #undef printf +#if defined(FRONTEND) +/* + * Frontend code might be multi-threaded. When calling the system snprintf() + * for floats, we have to use either the non-standard snprintf_l() variant, or + * save-and-restore the calling thread's locale using uselocale(), depending on + * availability. + */ +#if defined(HAVE_SNPRINTF_L) +/* At least macOS and the BSDs have the snprintf_l() extension. */ +#define USE_SNPRINTF_L +#elif defined(WIN32) +/* Windows has a version with a different name and argument order. */ +#define snprintf_l(str, size, loc, format, ...) _snprintf_l(str, size, format, loc, __VA_ARGS__) +#define USE_SNPRINTF_L +#else +/* We have to do a thread-safe save-and-restore around snprintf(). */ +#define NEED_USE_LOCALE +#endif +#else +/* + * Backend code doesn't need to worry about locales here, because LC_NUMERIC is + * set to "C" in main() and doesn't change. Plain old snprintf() is always OK + * without uselocale(). + * + * XXX If the backend were multithreaded, we would have to be 100% certain that + * no one is calling setlocale() concurrently, even transiently, to be able to + * keep this small optimization. + */ +#endif + /* * Info about where the formatted output is going. * @@ -1184,6 +1214,9 @@ fmtfloat(double value, char type, int forcesign, int leftjust, * according to == but not according to memcmp. */ static const double dzero = 0.0; +#ifdef NEED_USE_LOCALE + locale_t save_locale = uselocale(PG_C_LOCALE); +#endif if (adjust_sign((value < 0.0 || (value == 0.0 && @@ -1205,7 +1238,11 @@ fmtfloat(double value, char type, int forcesign, int leftjust, fmt[2] = '*'; fmt[3] = type; fmt[4] = '\0'; +#ifdef USE_SNPRINTF_L + vallen = snprintf_l(convert, sizeof(convert), PG_C_LOCALE, fmt, prec, value); +#else vallen = snprintf(convert, sizeof(convert), fmt, prec, value); +#endif } else { @@ -1214,6 +1251,11 @@ fmtfloat(double value, char type, int forcesign, int leftjust, fmt[2] = '\0'; vallen = snprintf(convert, sizeof(convert), fmt, value); } + +#ifdef NEED_USE_LOCALE + uselocale(save_locale); +#endif + if (vallen < 0) goto fail; @@ -1336,12 +1378,25 @@ pg_strfromd(char *str, size_t count, int precision, double value) } else { +#ifdef NEED_USE_LOCALE + locale_t save_locale = uselocale(PG_C_LOCALE); +#endif + fmt[0] = '%'; fmt[1] = '.'; fmt[2] = '*'; fmt[3] = 'g'; fmt[4] = '\0'; +#ifdef USE_SNPRINTF_L + vallen = snprintf_l(convert, sizeof(convert), PG_C_LOCALE, fmt, precision, value); +#else vallen = snprintf(convert, sizeof(convert), fmt, precision, value); +#endif + +#ifdef NEED_USE_LOCALE + uselocale(save_locale); +#endif + if (vallen < 0) { target.failed = true; -- 2.47.0 ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2024-11-14 09:54 Heikki Linnakangas <[email protected]> parent: Thomas Munro <[email protected]> 1 sibling, 0 replies; 41+ messages in thread From: Heikki Linnakangas @ 2024-11-14 09:54 UTC (permalink / raw) To: Thomas Munro <[email protected]>; Peter Eisentraut <[email protected]>; +Cc: Tristan Partin <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers On 14/11/2024 09:48, Thomas Munro wrote: > On Thu, Aug 29, 2024 at 6:50 AM Peter Eisentraut <[email protected]> wrote: >> The error handling with pg_ensure_c_locale(), that's the sort of thing >> I'm afraid will be hard to test or even know how it will behave. And it >> creates this weird coupling between pgtypeslib and ecpglib that you >> mentioned earlier. And if there are other users of PG_C_LOCALE in the >> future, there will be similar questions about the proper initialization >> and error handling sequence. > > Ack. The coupling does become at least less weird (currently it must > be capable of giving the wrong answers reliably if called directly I > think, no?) and weaker, but I acknowledge that it's still non-ideal > that out-of-memory would be handled nicely only if you used ecpg > first, and subtle misbehaviour would ensure otherwise, and future > users face the same sort of problem unless they design in a reasonable > initialisation place that could check pg_ensure_c_locale() nicely. > Classic retro-fitting problem, though. Hmm, so should we add calls to pg_ensure_c_locale() in pgtypeslib too, before each call to strtod_l()? Looking at the callers of strtod() in ecpg, all but one of them actually readily convert the returned value to integer, with some multiplication or division with constants. It would be nice to replace those with a function that would avoid going through double in the first place. That still leaves one caller in numericvar_to_double() which really wants a double, though Overall, the patch looks good to me. -- Heikki Linnakangas Neon (https://neon.tech) ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2024-11-14 12:53 Peter Eisentraut <[email protected]> parent: Thomas Munro <[email protected]> 1 sibling, 1 reply; 41+ messages in thread From: Peter Eisentraut @ 2024-11-14 12:53 UTC (permalink / raw) To: Thomas Munro <[email protected]>; +Cc: Tristan Partin <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers On 14.11.24 08:48, Thomas Munro wrote: > The three MinGW environments we test today are using ucrt, and > configure detects the symbol on all. Namely: fairwren > (msys2/mingw64), the CI mingw64 task and the mingw cross-build that > runs on Linux in the CI CompilerWarnings task. As far as I know these > are the reasons for, and mechanism by which, we keep MinGW support > working. We have no policy requiring arbitrary old MinGW systems > work, and we wouldn't know anyway. Right. So I think we could unwind this in steps. First, remove the configure test for _configthreadlocale() and all the associated #ifdefs in the existing ecpg code. This seems totally safe, it would just leave behind MinGW older than 2016 and MSVC older than 2015, the latter of which is already the current threshold. Then the question whether we want to re-enable the error checking on _configthreadlocale() that was reverted by 2cf91ccb, or at least something similar. This should also be okay based on your description of the different Windows runtimes. I think it would also be good to do this to make sure this works before we employ _configthreadlocale() in higher-stakes situations. I suggest doing these two steps as separate patches, so this doesn't get confused between the various thread-related threads that want to variously add or remove uses of this function. ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2025-03-28 15:30 Peter Eisentraut <[email protected]> parent: Peter Eisentraut <[email protected]> 0 siblings, 1 reply; 41+ messages in thread From: Peter Eisentraut @ 2025-03-28 15:30 UTC (permalink / raw) To: Thomas Munro <[email protected]>; +Cc: Michael Paquier <[email protected]>; Andrew Dunstan <[email protected]>; Tristan Partin <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers On 09.02.25 08:32, Peter Eisentraut wrote: > Checking the status of this thread ... > > The patches that removed the configure checks for _configthreadlocale(), > and related cleanup, have been committed. > > The original patch to "Tidy up locale thread safety in ECPG library" is > still outstanding. > > Attached is a rebased version, based on the posted v6, with a couple of > small fixups from me. > > I haven't re-reviewed it yet, but from scanning the discussion, it looks > close to done. After staring at this a few more times, I figured it was ready enough and I committed it. ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2025-03-28 16:14 Masahiko Sawada <[email protected]> parent: Peter Eisentraut <[email protected]> 0 siblings, 1 reply; 41+ messages in thread From: Masahiko Sawada @ 2025-03-28 16:14 UTC (permalink / raw) To: Peter Eisentraut <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; Andrew Dunstan <[email protected]>; Tristan Partin <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers On Fri, Mar 28, 2025 at 8:30 AM Peter Eisentraut <[email protected]> wrote: > > On 09.02.25 08:32, Peter Eisentraut wrote: > > Checking the status of this thread ... > > > > The patches that removed the configure checks for _configthreadlocale(), > > and related cleanup, have been committed. > > > > The original patch to "Tidy up locale thread safety in ECPG library" is > > still outstanding. > > > > Attached is a rebased version, based on the posted v6, with a couple of > > small fixups from me. > > > > I haven't re-reviewed it yet, but from scanning the discussion, it looks > > close to done. > > After staring at this a few more times, I figured it was ready enough > and I committed it. It seems that some bf animals such as jackdaw are unhappy with this commit[0][1]. I also got the same 'undefined reference to symbol error' locally when building test_json_parser. Regards, [0] https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=snakefly&dt=2025-03-28%2015%3A29%3A04 [1] https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=jackdaw&dt=2025-03-28%2015%3A30%3A44 -- Masahiko Sawada Amazon Web Services: https://aws.amazon.com ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2025-03-28 16:32 Peter Eisentraut <[email protected]> parent: Masahiko Sawada <[email protected]> 0 siblings, 2 replies; 41+ messages in thread From: Peter Eisentraut @ 2025-03-28 16:32 UTC (permalink / raw) To: Masahiko Sawada <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; Andrew Dunstan <[email protected]>; Tristan Partin <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers On 28.03.25 17:14, Masahiko Sawada wrote: > On Fri, Mar 28, 2025 at 8:30 AM Peter Eisentraut <[email protected]> wrote: >> >> On 09.02.25 08:32, Peter Eisentraut wrote: >>> Checking the status of this thread ... >>> >>> The patches that removed the configure checks for _configthreadlocale(), >>> and related cleanup, have been committed. >>> >>> The original patch to "Tidy up locale thread safety in ECPG library" is >>> still outstanding. >>> >>> Attached is a rebased version, based on the posted v6, with a couple of >>> small fixups from me. >>> >>> I haven't re-reviewed it yet, but from scanning the discussion, it looks >>> close to done. >> >> After staring at this a few more times, I figured it was ready enough >> and I committed it. > > It seems that some bf animals such as jackdaw are unhappy with this > commit[0][1]. I also got the same 'undefined reference to symbol > error' locally when building test_json_parser. Yeah, looks like we'll have to revert this for now. But I'm confused, because I don't see any clear pattern for which platforms or configurations it's failing and for which not. ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2025-03-28 20:34 Peter Eisentraut <[email protected]> parent: Peter Eisentraut <[email protected]> 1 sibling, 0 replies; 41+ messages in thread From: Peter Eisentraut @ 2025-03-28 20:34 UTC (permalink / raw) To: Masahiko Sawada <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; Andrew Dunstan <[email protected]>; Tristan Partin <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers On 28.03.25 17:32, Peter Eisentraut wrote: > On 28.03.25 17:14, Masahiko Sawada wrote: >> On Fri, Mar 28, 2025 at 8:30 AM Peter Eisentraut >> <[email protected]> wrote: >>> >>> On 09.02.25 08:32, Peter Eisentraut wrote: >>>> Checking the status of this thread ... >>>> >>>> The patches that removed the configure checks for >>>> _configthreadlocale(), >>>> and related cleanup, have been committed. >>>> >>>> The original patch to "Tidy up locale thread safety in ECPG library" is >>>> still outstanding. >>>> >>>> Attached is a rebased version, based on the posted v6, with a couple of >>>> small fixups from me. >>>> >>>> I haven't re-reviewed it yet, but from scanning the discussion, it >>>> looks >>>> close to done. >>> >>> After staring at this a few more times, I figured it was ready enough >>> and I committed it. >> >> It seems that some bf animals such as jackdaw are unhappy with this >> commit[0][1]. I also got the same 'undefined reference to symbol >> error' locally when building test_json_parser. > > Yeah, looks like we'll have to revert this for now. But I'm confused, > because I don't see any clear pattern for which platforms or > configurations it's failing and for which not. reverted ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2025-03-29 00:01 Masahiko Sawada <[email protected]> parent: Peter Eisentraut <[email protected]> 1 sibling, 0 replies; 41+ messages in thread From: Masahiko Sawada @ 2025-03-29 00:01 UTC (permalink / raw) To: Peter Eisentraut <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; Andrew Dunstan <[email protected]>; Tristan Partin <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers On Fri, Mar 28, 2025 at 9:32 AM Peter Eisentraut <[email protected]> wrote: > > On 28.03.25 17:14, Masahiko Sawada wrote: > > On Fri, Mar 28, 2025 at 8:30 AM Peter Eisentraut <[email protected]> wrote: > >> > >> On 09.02.25 08:32, Peter Eisentraut wrote: > >>> Checking the status of this thread ... > >>> > >>> The patches that removed the configure checks for _configthreadlocale(), > >>> and related cleanup, have been committed. > >>> > >>> The original patch to "Tidy up locale thread safety in ECPG library" is > >>> still outstanding. > >>> > >>> Attached is a rebased version, based on the posted v6, with a couple of > >>> small fixups from me. > >>> > >>> I haven't re-reviewed it yet, but from scanning the discussion, it looks > >>> close to done. > >> > >> After staring at this a few more times, I figured it was ready enough > >> and I committed it. > > > > It seems that some bf animals such as jackdaw are unhappy with this > > commit[0][1]. I also got the same 'undefined reference to symbol > > error' locally when building test_json_parser. > > Yeah, looks like we'll have to revert this for now. But I'm confused, > because I don't see any clear pattern for which platforms or > configurations it's failing and for which not. > Not sure it would help the investigation but I got the linker error when building with 'make' but not with 'meson'. Looking at the build logs, when building test_json_parser with meson, it adds -lpthread as follows: % /home/masahiko/work/gcc/12.2.0/bin/gcc -v -o src/test/modules/test_json_parser/test_json_parser_incremental_shlib src/test/modules/test_json_parser/test_json_parser_incremental_shlib.p/test_json_parser_incremental.c.o -Wl,--as-needed -Wl,--no-undefined '-Wl,-rpath,$ORIGIN/../../../interfaces/libpq:/lib/../lib64' -Wl,-rpath-link,/lib/../lib64 -Wl,-rpath-link,/home/masahiko/pgsql/source/dev_master/build/src/interfaces/libpq -Wl,--start-group src/common/libpgcommon_excluded_shlib.a src/common/libpgcommon_shlib.a src/common/libpgcommon_shlib_config_info.a src/common/libpgcommon_shlib_ryu.a src/port/libpgport_shlib.a src/interfaces/libpq/libpq.so.5.18 -lm -ldl -pthread -lrt /usr/lib64/libz.so /lib/../lib64/libzstd.so /usr/lib64/liblz4.so /usr/lib64/libssl.so /usr/lib64/libcrypto.so -Wl,--end-group whereas with 'make' it doesn't: % gcc -v -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wshadow=compatible-local -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -Wno-stringop-truncation -g -g -O0 test_json_parser_incremental.o -L../../../../src/port -L../../../../src/common -Wl,--as-needed -Wl,-rpath,'/home/masahiko/pgsql/master/lib',--enable-new-dtags -lpgcommon_excluded_shlib -L../../../../src/common -lpgcommon_shlib -L../../../../src/port -lpgport_shlib -L../../../../src/interfaces/libpq -lpq -o test_json_parser_incremental_shlib FYI the following change fixed the issue in my local env: --- a/src/test/modules/test_json_parser/Makefile +++ b/src/test/modules/test_json_parser/Makefile @@ -27,7 +27,7 @@ test_json_parser_incremental$(X): test_json_parser_incremental.o $(WIN32RES) $(CC) $(CFLAGS) $^ $(PG_LIBS_INTERNAL) $(LDFLAGS) $(LDFLAGS_EX) $(PG_LIBS) $(LIBS) -o $@ test_json_parser_incremental_shlib$(X): test_json_parser_incremental.o $(WIN32RES) - $(CC) $(CFLAGS) $^ $(LDFLAGS) -lpgcommon_excluded_shlib $(libpq_pgport_shlib) $(filter -lintl, $(LIBS)) -o $@ + $(CC) $(CFLAGS) $^ $(LDFLAGS) -lpgcommon_excluded_shlib $(libpq_pgport_shlib) $(filter -lintl, $(LIBS)) $(LIBS) -o $@ test_json_parser_perf$(X): test_json_parser_perf.o $(WIN32RES) $(CC) $(CFLAGS) $^ $(PG_LIBS_INTERNAL) $(LDFLAGS) $(LDFLAGS_EX) $(PG_LIBS) $(LIBS) -o $@ It seems that no MacOS and NetBSD animals failed due to this error because they have LC_C_LOCALE. But I'm not sure why some animals successfully built test_json_parser() even without -lpthread or -pthread[1][2]. Regards, [1] https://buildfarm.postgresql.org/cgi-bin/show_stage_log.pl?nm=prion&dt=2025-03-28%2015%3A33%3A03... [2] https://buildfarm.postgresql.org/cgi-bin/show_stage_log.pl?nm=bushmaster&dt=2025-03-28%2015%3A32... -- Masahiko Sawada Amazon Web Services: https://aws.amazon.com ^ permalink raw reply [nested|flat] 41+ messages in thread
* Re: On non-Windows, hard depend on uselocale(3) @ 2025-12-12 18:07 Tom Lane <[email protected]> parent: Peter Eisentraut <[email protected]> 2 siblings, 0 replies; 41+ messages in thread From: Tom Lane @ 2025-12-12 18:07 UTC (permalink / raw) To: Peter Eisentraut <[email protected]>; +Cc: Thomas Munro <[email protected]>; Tristan Partin <[email protected]>; pgsql-hackers Peter Eisentraut <[email protected]> writes: > On 28.08.24 20:50, Peter Eisentraut wrote: >> I suggest that the simplification of the xlocale.h configure tests could >> be committed separately. This would also be useful independent of this, >> and it's a sizeable chunk of this patch. > To keep this moving along a bit, I have extracted this part and > committed it separately. I had to make a few small tweaks, e.g., there > was no check for xlocale.h in configure.ac, and the old > xlocale.h-including stanza could be removed from chklocale.h. Let's see > how this goes. For the archives' sake --- I discovered today during a "git bisect" session that commits between 35eeea623 ("Use thread-safe nl_langinfo_l(), not nl_langinfo()") and 9c2a6c5a5 ("Simplify checking for xlocale.h", the commit Peter refers to here) fail to build on current macOS (26/Tahoe): chklocale.c:330:8: error: call to undeclared function 'nl_langinfo_l'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration] 330 | sys = nl_langinfo_l(CODESET, loc); | ^ chklocale.c:330:8: note: did you mean 'nl_langinfo'? /Library/Developer/CommandLineTools/SDKs/MacOSX26.1.sdk/usr/include/_langinfo.h:116:20: note: 'nl_langinfo' declared here 116 | char *_LIBC_CSTR nl_langinfo(nl_item); | ^ chklocale.c:330:6: error: incompatible integer to pointer conversion assigning to 'char *' from 'int' [-Wint-conversion] 330 | sys = nl_langinfo_l(CODESET, loc); | ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 2 errors generated. This happens because nl_langinfo_l() is declared in xlocale.h, but chklocale.c elects not to #include that, evidently because it's no longer needed to obtain typedef locale_t. AFAICS there's nothing we can do about this retroactively; it'll be a more or less permanent landmine for bisecting on macOS. Fortunately it's not too difficult to work around, you can just do diff --git a/src/port/chklocale.c b/src/port/chklocale.c index 9506cd87ed8..0e35e0cf55f 100644 --- a/src/port/chklocale.c +++ b/src/port/chklocale.c @@ -23,9 +23,7 @@ #include <langinfo.h> #endif -#ifdef LOCALE_T_IN_XLOCALE #include <xlocale.h> -#endif #include "mb/pg_wchar.h" when trying to build one of the affected commits. Another option that might fit into a bisecting workflow more easily is to inject -DLOCALE_T_IN_XLOCALE into CPPFLAGS. regards, tom lane ^ permalink raw reply [nested|flat] 41+ messages in thread
* [PATCH v51 10/10] CheckLogicalDecodingRequirements: be specific about which GUC is limiting @ 2026-04-03 19:01 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 41+ messages in thread From: Álvaro Herrera @ 2026-04-03 19:01 UTC (permalink / raw) --- src/backend/commands/repack_worker.c | 3 ++- src/backend/replication/logical/logical.c | 8 +++++--- src/backend/replication/logical/logicalfuncs.c | 2 +- src/backend/replication/slot.c | 18 ++++++++++++------ src/backend/replication/slotfuncs.c | 11 ++++++----- src/backend/replication/walsender.c | 5 +++-- src/include/replication/logical.h | 3 ++- src/include/replication/slot.h | 2 +- 8 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c index 610592a05b0..ca827223845 100644 --- a/src/backend/commands/repack_worker.c +++ b/src/backend/commands/repack_worker.c @@ -224,7 +224,7 @@ repack_setup_logical_decoding(Oid relid) * Make sure we can use logical decoding. */ CheckSlotPermissions(); - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(true); /* * A single backend should not execute multiple REPACK commands at a time, @@ -252,6 +252,7 @@ repack_setup_logical_decoding(Oid relid) ctx = CreateInitDecodingContext(REPL_PLUGIN_NAME, NIL, true, + true, InvalidXLogRecPtr, XL_ROUTINE(.page_read = read_local_xlog_page, .segment_open = wal_segment_open, diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c index f20a0fe70ad..a08aece5731 100644 --- a/src/backend/replication/logical/logical.c +++ b/src/backend/replication/logical/logical.c @@ -108,9 +108,9 @@ static void LoadOutputPlugin(OutputPluginCallbacks *callbacks, const char *plugi * decoding. */ void -CheckLogicalDecodingRequirements(void) +CheckLogicalDecodingRequirements(bool repack) { - CheckSlotRequirements(); + CheckSlotRequirements(repack); /* * NB: Adding a new requirement likely means that RestoreSlotFromDisk() @@ -305,6 +305,7 @@ StartupDecodingContext(List *output_plugin_options, * output_plugin_options -- contains options passed to the output plugin * need_full_snapshot -- if true, must obtain a snapshot able to read all * tables; if false, one that can read only catalogs is acceptable. + * for_repack -- if true, we're going to be decoding for REPACK. * restart_lsn -- if given as invalid, it's this routine's responsibility to * mark WAL as reserved by setting a convenient restart_lsn for the slot. * Otherwise, we set for decoding to start from the given LSN without @@ -325,6 +326,7 @@ LogicalDecodingContext * CreateInitDecodingContext(const char *plugin, List *output_plugin_options, bool need_full_snapshot, + bool for_repack, XLogRecPtr restart_lsn, XLogReaderRoutine *xl_routine, LogicalOutputPluginWriterPrepareWrite prepare_write, @@ -341,7 +343,7 @@ CreateInitDecodingContext(const char *plugin, * On a standby, this check is also required while creating the slot. * Check the comments in the function. */ - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(for_repack); /* shorter lines... */ slot = MyReplicationSlot; diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c index 9760818941d..512013b0ef0 100644 --- a/src/backend/replication/logical/logicalfuncs.c +++ b/src/backend/replication/logical/logicalfuncs.c @@ -115,7 +115,7 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin CheckSlotPermissions(); - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); if (PG_ARGISNULL(0)) ereport(ERROR, diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c index 2c6c6773ad2..13004ed547a 100644 --- a/src/backend/replication/slot.c +++ b/src/backend/replication/slot.c @@ -1669,19 +1669,25 @@ CheckLogicalSlotExists(void) * slots. */ void -CheckSlotRequirements(void) +CheckSlotRequirements(bool repack) { + int limit; + /* * NB: Adding a new requirement likely means that RestoreSlotFromDisk() * needs the same check. */ - /* XXX we should be able to check exactly which type of slot we need */ - if (max_replication_slots + max_repack_replication_slots == 0) + if (repack) + limit = max_repack_replication_slots; + else + limit = max_replication_slots; + + if (limit == 0) ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("replication slots can only be used if \"%s\" > 0 or \"%s\" > 0", - "max_replication_slots", "max_repack_replication_slots"))); + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("replication slots can only be used if \"%s\" > 0", + repack ? "max_repack_replication_slots" : "max_replication_slots")); if (wal_level < WAL_LEVEL_REPLICA) ereport(ERROR, diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 78dd3c4ea66..16fbd383735 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -90,7 +90,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS) CheckSlotPermissions(); - CheckSlotRequirements(); + CheckSlotRequirements(false); create_physical_replication_slot(NameStr(*name), immediately_reserve, @@ -164,6 +164,7 @@ create_logical_replication_slot(char *name, char *plugin, */ ctx = CreateInitDecodingContext(plugin, NIL, false, /* just catalogs is OK */ + false, /* not repack */ restart_lsn, XL_ROUTINE(.page_read = read_local_xlog_page, .segment_open = wal_segment_open, @@ -203,7 +204,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS) CheckSlotPermissions(); - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); create_logical_replication_slot(NameStr(*name), NameStr(*plugin), @@ -240,7 +241,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS) CheckSlotPermissions(); - CheckSlotRequirements(); + CheckSlotRequirements(false); ReplicationSlotDrop(NameStr(*name), true); @@ -648,9 +649,9 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot) CheckSlotPermissions(); if (logical_slot) - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); else - CheckSlotRequirements(); + CheckSlotRequirements(false); LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index 75ef3419a15..9d7d675fa96 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -1240,7 +1240,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd) Assert(cmd->kind == REPLICATION_KIND_LOGICAL); - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); /* * Initially create persistent slot as ephemeral - that allows us to @@ -1309,6 +1309,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd) Assert(IsLogicalDecodingEnabled()); ctx = CreateInitDecodingContext(cmd->plugin, NIL, need_full_snapshot, + false, InvalidXLogRecPtr, XL_ROUTINE(.page_read = logical_read_xlog_page, .segment_open = WalSndSegmentOpen, @@ -1466,7 +1467,7 @@ StartLogicalReplication(StartReplicationCmd *cmd) QueryCompletion qc; /* make sure that our requirements are still fulfilled */ - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); Assert(!MyReplicationSlot); diff --git a/src/include/replication/logical.h b/src/include/replication/logical.h index bc9d4ece672..bc075b16741 100644 --- a/src/include/replication/logical.h +++ b/src/include/replication/logical.h @@ -115,11 +115,12 @@ typedef struct LogicalDecodingContext } LogicalDecodingContext; -extern void CheckLogicalDecodingRequirements(void); +extern void CheckLogicalDecodingRequirements(bool repack); extern LogicalDecodingContext *CreateInitDecodingContext(const char *plugin, List *output_plugin_options, bool need_full_snapshot, + bool for_repack, XLogRecPtr restart_lsn, XLogReaderRoutine *xl_routine, LogicalOutputPluginWriterPrepareWrite prepare_write, diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h index c316a01a807..489af7d8d6c 100644 --- a/src/include/replication/slot.h +++ b/src/include/replication/slot.h @@ -378,7 +378,7 @@ extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn, char *slotname extern void StartupReplicationSlots(void); extern void CheckPointReplicationSlots(bool is_shutdown); -extern void CheckSlotRequirements(void); +extern void CheckSlotRequirements(bool repack); extern void CheckSlotPermissions(void); extern ReplicationSlotInvalidationCause GetSlotInvalidationCause(const char *cause_name); -- 2.47.3 --r2slln3zpmilwu22-- ^ permalink raw reply [nested|flat] 41+ messages in thread
* [PATCH v52 10/10] CheckLogicalDecodingRequirements: be specific about which GUC is limiting @ 2026-04-03 19:01 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 41+ messages in thread From: Álvaro Herrera @ 2026-04-03 19:01 UTC (permalink / raw) --- src/backend/commands/repack_worker.c | 3 ++- src/backend/replication/logical/logical.c | 8 +++++--- src/backend/replication/logical/logicalfuncs.c | 2 +- src/backend/replication/slot.c | 18 ++++++++++++------ src/backend/replication/slotfuncs.c | 11 ++++++----- src/backend/replication/walsender.c | 5 +++-- src/include/replication/logical.h | 3 ++- src/include/replication/slot.h | 2 +- 8 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c index 610592a05b0..ca827223845 100644 --- a/src/backend/commands/repack_worker.c +++ b/src/backend/commands/repack_worker.c @@ -224,7 +224,7 @@ repack_setup_logical_decoding(Oid relid) * Make sure we can use logical decoding. */ CheckSlotPermissions(); - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(true); /* * A single backend should not execute multiple REPACK commands at a time, @@ -252,6 +252,7 @@ repack_setup_logical_decoding(Oid relid) ctx = CreateInitDecodingContext(REPL_PLUGIN_NAME, NIL, true, + true, InvalidXLogRecPtr, XL_ROUTINE(.page_read = read_local_xlog_page, .segment_open = wal_segment_open, diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c index f20a0fe70ad..a08aece5731 100644 --- a/src/backend/replication/logical/logical.c +++ b/src/backend/replication/logical/logical.c @@ -108,9 +108,9 @@ static void LoadOutputPlugin(OutputPluginCallbacks *callbacks, const char *plugi * decoding. */ void -CheckLogicalDecodingRequirements(void) +CheckLogicalDecodingRequirements(bool repack) { - CheckSlotRequirements(); + CheckSlotRequirements(repack); /* * NB: Adding a new requirement likely means that RestoreSlotFromDisk() @@ -305,6 +305,7 @@ StartupDecodingContext(List *output_plugin_options, * output_plugin_options -- contains options passed to the output plugin * need_full_snapshot -- if true, must obtain a snapshot able to read all * tables; if false, one that can read only catalogs is acceptable. + * for_repack -- if true, we're going to be decoding for REPACK. * restart_lsn -- if given as invalid, it's this routine's responsibility to * mark WAL as reserved by setting a convenient restart_lsn for the slot. * Otherwise, we set for decoding to start from the given LSN without @@ -325,6 +326,7 @@ LogicalDecodingContext * CreateInitDecodingContext(const char *plugin, List *output_plugin_options, bool need_full_snapshot, + bool for_repack, XLogRecPtr restart_lsn, XLogReaderRoutine *xl_routine, LogicalOutputPluginWriterPrepareWrite prepare_write, @@ -341,7 +343,7 @@ CreateInitDecodingContext(const char *plugin, * On a standby, this check is also required while creating the slot. * Check the comments in the function. */ - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(for_repack); /* shorter lines... */ slot = MyReplicationSlot; diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c index 9760818941d..512013b0ef0 100644 --- a/src/backend/replication/logical/logicalfuncs.c +++ b/src/backend/replication/logical/logicalfuncs.c @@ -115,7 +115,7 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin CheckSlotPermissions(); - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); if (PG_ARGISNULL(0)) ereport(ERROR, diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c index 2c6c6773ad2..13004ed547a 100644 --- a/src/backend/replication/slot.c +++ b/src/backend/replication/slot.c @@ -1669,19 +1669,25 @@ CheckLogicalSlotExists(void) * slots. */ void -CheckSlotRequirements(void) +CheckSlotRequirements(bool repack) { + int limit; + /* * NB: Adding a new requirement likely means that RestoreSlotFromDisk() * needs the same check. */ - /* XXX we should be able to check exactly which type of slot we need */ - if (max_replication_slots + max_repack_replication_slots == 0) + if (repack) + limit = max_repack_replication_slots; + else + limit = max_replication_slots; + + if (limit == 0) ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("replication slots can only be used if \"%s\" > 0 or \"%s\" > 0", - "max_replication_slots", "max_repack_replication_slots"))); + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("replication slots can only be used if \"%s\" > 0", + repack ? "max_repack_replication_slots" : "max_replication_slots")); if (wal_level < WAL_LEVEL_REPLICA) ereport(ERROR, diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 78dd3c4ea66..16fbd383735 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -90,7 +90,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS) CheckSlotPermissions(); - CheckSlotRequirements(); + CheckSlotRequirements(false); create_physical_replication_slot(NameStr(*name), immediately_reserve, @@ -164,6 +164,7 @@ create_logical_replication_slot(char *name, char *plugin, */ ctx = CreateInitDecodingContext(plugin, NIL, false, /* just catalogs is OK */ + false, /* not repack */ restart_lsn, XL_ROUTINE(.page_read = read_local_xlog_page, .segment_open = wal_segment_open, @@ -203,7 +204,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS) CheckSlotPermissions(); - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); create_logical_replication_slot(NameStr(*name), NameStr(*plugin), @@ -240,7 +241,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS) CheckSlotPermissions(); - CheckSlotRequirements(); + CheckSlotRequirements(false); ReplicationSlotDrop(NameStr(*name), true); @@ -648,9 +649,9 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot) CheckSlotPermissions(); if (logical_slot) - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); else - CheckSlotRequirements(); + CheckSlotRequirements(false); LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index 75ef3419a15..9d7d675fa96 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -1240,7 +1240,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd) Assert(cmd->kind == REPLICATION_KIND_LOGICAL); - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); /* * Initially create persistent slot as ephemeral - that allows us to @@ -1309,6 +1309,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd) Assert(IsLogicalDecodingEnabled()); ctx = CreateInitDecodingContext(cmd->plugin, NIL, need_full_snapshot, + false, InvalidXLogRecPtr, XL_ROUTINE(.page_read = logical_read_xlog_page, .segment_open = WalSndSegmentOpen, @@ -1466,7 +1467,7 @@ StartLogicalReplication(StartReplicationCmd *cmd) QueryCompletion qc; /* make sure that our requirements are still fulfilled */ - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); Assert(!MyReplicationSlot); diff --git a/src/include/replication/logical.h b/src/include/replication/logical.h index bc9d4ece672..bc075b16741 100644 --- a/src/include/replication/logical.h +++ b/src/include/replication/logical.h @@ -115,11 +115,12 @@ typedef struct LogicalDecodingContext } LogicalDecodingContext; -extern void CheckLogicalDecodingRequirements(void); +extern void CheckLogicalDecodingRequirements(bool repack); extern LogicalDecodingContext *CreateInitDecodingContext(const char *plugin, List *output_plugin_options, bool need_full_snapshot, + bool for_repack, XLogRecPtr restart_lsn, XLogReaderRoutine *xl_routine, LogicalOutputPluginWriterPrepareWrite prepare_write, diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h index c316a01a807..489af7d8d6c 100644 --- a/src/include/replication/slot.h +++ b/src/include/replication/slot.h @@ -378,7 +378,7 @@ extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn, char *slotname extern void StartupReplicationSlots(void); extern void CheckPointReplicationSlots(bool is_shutdown); -extern void CheckSlotRequirements(void); +extern void CheckSlotRequirements(bool repack); extern void CheckSlotPermissions(void); extern ReplicationSlotInvalidationCause GetSlotInvalidationCause(const char *cause_name); -- 2.47.3 --gp2pyozrd5pweboh-- ^ permalink raw reply [nested|flat] 41+ messages in thread
* [PATCH v51 10/10] CheckLogicalDecodingRequirements: be specific about which GUC is limiting @ 2026-04-03 19:01 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 41+ messages in thread From: Álvaro Herrera @ 2026-04-03 19:01 UTC (permalink / raw) --- src/backend/commands/repack_worker.c | 3 ++- src/backend/replication/logical/logical.c | 8 +++++--- src/backend/replication/logical/logicalfuncs.c | 2 +- src/backend/replication/slot.c | 18 ++++++++++++------ src/backend/replication/slotfuncs.c | 11 ++++++----- src/backend/replication/walsender.c | 5 +++-- src/include/replication/logical.h | 3 ++- src/include/replication/slot.h | 2 +- 8 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c index 610592a05b0..ca827223845 100644 --- a/src/backend/commands/repack_worker.c +++ b/src/backend/commands/repack_worker.c @@ -224,7 +224,7 @@ repack_setup_logical_decoding(Oid relid) * Make sure we can use logical decoding. */ CheckSlotPermissions(); - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(true); /* * A single backend should not execute multiple REPACK commands at a time, @@ -252,6 +252,7 @@ repack_setup_logical_decoding(Oid relid) ctx = CreateInitDecodingContext(REPL_PLUGIN_NAME, NIL, true, + true, InvalidXLogRecPtr, XL_ROUTINE(.page_read = read_local_xlog_page, .segment_open = wal_segment_open, diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c index f20a0fe70ad..a08aece5731 100644 --- a/src/backend/replication/logical/logical.c +++ b/src/backend/replication/logical/logical.c @@ -108,9 +108,9 @@ static void LoadOutputPlugin(OutputPluginCallbacks *callbacks, const char *plugi * decoding. */ void -CheckLogicalDecodingRequirements(void) +CheckLogicalDecodingRequirements(bool repack) { - CheckSlotRequirements(); + CheckSlotRequirements(repack); /* * NB: Adding a new requirement likely means that RestoreSlotFromDisk() @@ -305,6 +305,7 @@ StartupDecodingContext(List *output_plugin_options, * output_plugin_options -- contains options passed to the output plugin * need_full_snapshot -- if true, must obtain a snapshot able to read all * tables; if false, one that can read only catalogs is acceptable. + * for_repack -- if true, we're going to be decoding for REPACK. * restart_lsn -- if given as invalid, it's this routine's responsibility to * mark WAL as reserved by setting a convenient restart_lsn for the slot. * Otherwise, we set for decoding to start from the given LSN without @@ -325,6 +326,7 @@ LogicalDecodingContext * CreateInitDecodingContext(const char *plugin, List *output_plugin_options, bool need_full_snapshot, + bool for_repack, XLogRecPtr restart_lsn, XLogReaderRoutine *xl_routine, LogicalOutputPluginWriterPrepareWrite prepare_write, @@ -341,7 +343,7 @@ CreateInitDecodingContext(const char *plugin, * On a standby, this check is also required while creating the slot. * Check the comments in the function. */ - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(for_repack); /* shorter lines... */ slot = MyReplicationSlot; diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c index 9760818941d..512013b0ef0 100644 --- a/src/backend/replication/logical/logicalfuncs.c +++ b/src/backend/replication/logical/logicalfuncs.c @@ -115,7 +115,7 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin CheckSlotPermissions(); - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); if (PG_ARGISNULL(0)) ereport(ERROR, diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c index 2c6c6773ad2..13004ed547a 100644 --- a/src/backend/replication/slot.c +++ b/src/backend/replication/slot.c @@ -1669,19 +1669,25 @@ CheckLogicalSlotExists(void) * slots. */ void -CheckSlotRequirements(void) +CheckSlotRequirements(bool repack) { + int limit; + /* * NB: Adding a new requirement likely means that RestoreSlotFromDisk() * needs the same check. */ - /* XXX we should be able to check exactly which type of slot we need */ - if (max_replication_slots + max_repack_replication_slots == 0) + if (repack) + limit = max_repack_replication_slots; + else + limit = max_replication_slots; + + if (limit == 0) ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("replication slots can only be used if \"%s\" > 0 or \"%s\" > 0", - "max_replication_slots", "max_repack_replication_slots"))); + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("replication slots can only be used if \"%s\" > 0", + repack ? "max_repack_replication_slots" : "max_replication_slots")); if (wal_level < WAL_LEVEL_REPLICA) ereport(ERROR, diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 78dd3c4ea66..16fbd383735 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -90,7 +90,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS) CheckSlotPermissions(); - CheckSlotRequirements(); + CheckSlotRequirements(false); create_physical_replication_slot(NameStr(*name), immediately_reserve, @@ -164,6 +164,7 @@ create_logical_replication_slot(char *name, char *plugin, */ ctx = CreateInitDecodingContext(plugin, NIL, false, /* just catalogs is OK */ + false, /* not repack */ restart_lsn, XL_ROUTINE(.page_read = read_local_xlog_page, .segment_open = wal_segment_open, @@ -203,7 +204,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS) CheckSlotPermissions(); - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); create_logical_replication_slot(NameStr(*name), NameStr(*plugin), @@ -240,7 +241,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS) CheckSlotPermissions(); - CheckSlotRequirements(); + CheckSlotRequirements(false); ReplicationSlotDrop(NameStr(*name), true); @@ -648,9 +649,9 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot) CheckSlotPermissions(); if (logical_slot) - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); else - CheckSlotRequirements(); + CheckSlotRequirements(false); LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index 75ef3419a15..9d7d675fa96 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -1240,7 +1240,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd) Assert(cmd->kind == REPLICATION_KIND_LOGICAL); - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); /* * Initially create persistent slot as ephemeral - that allows us to @@ -1309,6 +1309,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd) Assert(IsLogicalDecodingEnabled()); ctx = CreateInitDecodingContext(cmd->plugin, NIL, need_full_snapshot, + false, InvalidXLogRecPtr, XL_ROUTINE(.page_read = logical_read_xlog_page, .segment_open = WalSndSegmentOpen, @@ -1466,7 +1467,7 @@ StartLogicalReplication(StartReplicationCmd *cmd) QueryCompletion qc; /* make sure that our requirements are still fulfilled */ - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); Assert(!MyReplicationSlot); diff --git a/src/include/replication/logical.h b/src/include/replication/logical.h index bc9d4ece672..bc075b16741 100644 --- a/src/include/replication/logical.h +++ b/src/include/replication/logical.h @@ -115,11 +115,12 @@ typedef struct LogicalDecodingContext } LogicalDecodingContext; -extern void CheckLogicalDecodingRequirements(void); +extern void CheckLogicalDecodingRequirements(bool repack); extern LogicalDecodingContext *CreateInitDecodingContext(const char *plugin, List *output_plugin_options, bool need_full_snapshot, + bool for_repack, XLogRecPtr restart_lsn, XLogReaderRoutine *xl_routine, LogicalOutputPluginWriterPrepareWrite prepare_write, diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h index c316a01a807..489af7d8d6c 100644 --- a/src/include/replication/slot.h +++ b/src/include/replication/slot.h @@ -378,7 +378,7 @@ extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn, char *slotname extern void StartupReplicationSlots(void); extern void CheckPointReplicationSlots(bool is_shutdown); -extern void CheckSlotRequirements(void); +extern void CheckSlotRequirements(bool repack); extern void CheckSlotPermissions(void); extern ReplicationSlotInvalidationCause GetSlotInvalidationCause(const char *cause_name); -- 2.47.3 --r2slln3zpmilwu22-- ^ permalink raw reply [nested|flat] 41+ messages in thread
* [PATCH v52 10/10] CheckLogicalDecodingRequirements: be specific about which GUC is limiting @ 2026-04-03 19:01 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 41+ messages in thread From: Álvaro Herrera @ 2026-04-03 19:01 UTC (permalink / raw) --- src/backend/commands/repack_worker.c | 3 ++- src/backend/replication/logical/logical.c | 8 +++++--- src/backend/replication/logical/logicalfuncs.c | 2 +- src/backend/replication/slot.c | 18 ++++++++++++------ src/backend/replication/slotfuncs.c | 11 ++++++----- src/backend/replication/walsender.c | 5 +++-- src/include/replication/logical.h | 3 ++- src/include/replication/slot.h | 2 +- 8 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c index 610592a05b0..ca827223845 100644 --- a/src/backend/commands/repack_worker.c +++ b/src/backend/commands/repack_worker.c @@ -224,7 +224,7 @@ repack_setup_logical_decoding(Oid relid) * Make sure we can use logical decoding. */ CheckSlotPermissions(); - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(true); /* * A single backend should not execute multiple REPACK commands at a time, @@ -252,6 +252,7 @@ repack_setup_logical_decoding(Oid relid) ctx = CreateInitDecodingContext(REPL_PLUGIN_NAME, NIL, true, + true, InvalidXLogRecPtr, XL_ROUTINE(.page_read = read_local_xlog_page, .segment_open = wal_segment_open, diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c index f20a0fe70ad..a08aece5731 100644 --- a/src/backend/replication/logical/logical.c +++ b/src/backend/replication/logical/logical.c @@ -108,9 +108,9 @@ static void LoadOutputPlugin(OutputPluginCallbacks *callbacks, const char *plugi * decoding. */ void -CheckLogicalDecodingRequirements(void) +CheckLogicalDecodingRequirements(bool repack) { - CheckSlotRequirements(); + CheckSlotRequirements(repack); /* * NB: Adding a new requirement likely means that RestoreSlotFromDisk() @@ -305,6 +305,7 @@ StartupDecodingContext(List *output_plugin_options, * output_plugin_options -- contains options passed to the output plugin * need_full_snapshot -- if true, must obtain a snapshot able to read all * tables; if false, one that can read only catalogs is acceptable. + * for_repack -- if true, we're going to be decoding for REPACK. * restart_lsn -- if given as invalid, it's this routine's responsibility to * mark WAL as reserved by setting a convenient restart_lsn for the slot. * Otherwise, we set for decoding to start from the given LSN without @@ -325,6 +326,7 @@ LogicalDecodingContext * CreateInitDecodingContext(const char *plugin, List *output_plugin_options, bool need_full_snapshot, + bool for_repack, XLogRecPtr restart_lsn, XLogReaderRoutine *xl_routine, LogicalOutputPluginWriterPrepareWrite prepare_write, @@ -341,7 +343,7 @@ CreateInitDecodingContext(const char *plugin, * On a standby, this check is also required while creating the slot. * Check the comments in the function. */ - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(for_repack); /* shorter lines... */ slot = MyReplicationSlot; diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c index 9760818941d..512013b0ef0 100644 --- a/src/backend/replication/logical/logicalfuncs.c +++ b/src/backend/replication/logical/logicalfuncs.c @@ -115,7 +115,7 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin CheckSlotPermissions(); - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); if (PG_ARGISNULL(0)) ereport(ERROR, diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c index 2c6c6773ad2..13004ed547a 100644 --- a/src/backend/replication/slot.c +++ b/src/backend/replication/slot.c @@ -1669,19 +1669,25 @@ CheckLogicalSlotExists(void) * slots. */ void -CheckSlotRequirements(void) +CheckSlotRequirements(bool repack) { + int limit; + /* * NB: Adding a new requirement likely means that RestoreSlotFromDisk() * needs the same check. */ - /* XXX we should be able to check exactly which type of slot we need */ - if (max_replication_slots + max_repack_replication_slots == 0) + if (repack) + limit = max_repack_replication_slots; + else + limit = max_replication_slots; + + if (limit == 0) ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("replication slots can only be used if \"%s\" > 0 or \"%s\" > 0", - "max_replication_slots", "max_repack_replication_slots"))); + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("replication slots can only be used if \"%s\" > 0", + repack ? "max_repack_replication_slots" : "max_replication_slots")); if (wal_level < WAL_LEVEL_REPLICA) ereport(ERROR, diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 78dd3c4ea66..16fbd383735 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -90,7 +90,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS) CheckSlotPermissions(); - CheckSlotRequirements(); + CheckSlotRequirements(false); create_physical_replication_slot(NameStr(*name), immediately_reserve, @@ -164,6 +164,7 @@ create_logical_replication_slot(char *name, char *plugin, */ ctx = CreateInitDecodingContext(plugin, NIL, false, /* just catalogs is OK */ + false, /* not repack */ restart_lsn, XL_ROUTINE(.page_read = read_local_xlog_page, .segment_open = wal_segment_open, @@ -203,7 +204,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS) CheckSlotPermissions(); - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); create_logical_replication_slot(NameStr(*name), NameStr(*plugin), @@ -240,7 +241,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS) CheckSlotPermissions(); - CheckSlotRequirements(); + CheckSlotRequirements(false); ReplicationSlotDrop(NameStr(*name), true); @@ -648,9 +649,9 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot) CheckSlotPermissions(); if (logical_slot) - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); else - CheckSlotRequirements(); + CheckSlotRequirements(false); LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index 75ef3419a15..9d7d675fa96 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -1240,7 +1240,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd) Assert(cmd->kind == REPLICATION_KIND_LOGICAL); - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); /* * Initially create persistent slot as ephemeral - that allows us to @@ -1309,6 +1309,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd) Assert(IsLogicalDecodingEnabled()); ctx = CreateInitDecodingContext(cmd->plugin, NIL, need_full_snapshot, + false, InvalidXLogRecPtr, XL_ROUTINE(.page_read = logical_read_xlog_page, .segment_open = WalSndSegmentOpen, @@ -1466,7 +1467,7 @@ StartLogicalReplication(StartReplicationCmd *cmd) QueryCompletion qc; /* make sure that our requirements are still fulfilled */ - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); Assert(!MyReplicationSlot); diff --git a/src/include/replication/logical.h b/src/include/replication/logical.h index bc9d4ece672..bc075b16741 100644 --- a/src/include/replication/logical.h +++ b/src/include/replication/logical.h @@ -115,11 +115,12 @@ typedef struct LogicalDecodingContext } LogicalDecodingContext; -extern void CheckLogicalDecodingRequirements(void); +extern void CheckLogicalDecodingRequirements(bool repack); extern LogicalDecodingContext *CreateInitDecodingContext(const char *plugin, List *output_plugin_options, bool need_full_snapshot, + bool for_repack, XLogRecPtr restart_lsn, XLogReaderRoutine *xl_routine, LogicalOutputPluginWriterPrepareWrite prepare_write, diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h index c316a01a807..489af7d8d6c 100644 --- a/src/include/replication/slot.h +++ b/src/include/replication/slot.h @@ -378,7 +378,7 @@ extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn, char *slotname extern void StartupReplicationSlots(void); extern void CheckPointReplicationSlots(bool is_shutdown); -extern void CheckSlotRequirements(void); +extern void CheckSlotRequirements(bool repack); extern void CheckSlotPermissions(void); extern ReplicationSlotInvalidationCause GetSlotInvalidationCause(const char *cause_name); -- 2.47.3 --gp2pyozrd5pweboh-- ^ permalink raw reply [nested|flat] 41+ messages in thread
end of thread, other threads:[~2026-04-03 19:01 UTC | newest] Thread overview: 41+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2019-12-13 02:54 [PATCH v24 1/4] vacuum errcontext to show block being processed Justin Pryzby <[email protected]> 2023-11-15 17:45 Re: On non-Windows, hard depend on uselocale(3) Tom Lane <[email protected]> 2023-11-15 18:38 ` Re: On non-Windows, hard depend on uselocale(3) Thomas Munro <[email protected]> 2023-11-15 20:51 ` Re: On non-Windows, hard depend on uselocale(3) Tom Lane <[email protected]> 2023-11-15 21:08 ` Re: On non-Windows, hard depend on uselocale(3) Thomas Munro <[email protected]> 2023-11-15 21:17 ` Re: On non-Windows, hard depend on uselocale(3) Tom Lane <[email protected]> 2023-11-15 22:40 ` Re: On non-Windows, hard depend on uselocale(3) Thomas Munro <[email protected]> 2023-11-15 23:06 ` Re: On non-Windows, hard depend on uselocale(3) Tom Lane <[email protected]> 2023-11-16 19:57 ` Re: On non-Windows, hard depend on uselocale(3) Thomas Munro <[email protected]> 2023-11-17 18:18 ` Re: On non-Windows, hard depend on uselocale(3) Tom Lane <[email protected]> 2023-11-17 22:58 ` Re: On non-Windows, hard depend on uselocale(3) Tom Lane <[email protected]> 2023-11-19 22:00 ` Re: On non-Windows, hard depend on uselocale(3) Thomas Munro <[email protected]> 2023-11-19 22:36 ` Re: On non-Windows, hard depend on uselocale(3) Tom Lane <[email protected]> 2023-11-20 00:00 ` Re: On non-Windows, hard depend on uselocale(3) Thomas Munro <[email protected]> 2023-11-20 16:40 ` Re: On non-Windows, hard depend on uselocale(3) Tom Lane <[email protected]> 2024-08-10 01:29 ` Re: On non-Windows, hard depend on uselocale(3) Thomas Munro <[email protected]> 2024-08-10 03:48 ` Re: On non-Windows, hard depend on uselocale(3) Thomas Munro <[email protected]> 2024-08-10 03:52 ` Re: On non-Windows, hard depend on uselocale(3) Thomas Munro <[email protected]> 2024-08-10 22:11 ` Re: On non-Windows, hard depend on uselocale(3) Thomas Munro <[email protected]> 2024-08-13 23:17 ` Re: On non-Windows, hard depend on uselocale(3) Tristan Partin <[email protected]> 2024-08-15 08:49 ` Re: On non-Windows, hard depend on uselocale(3) Thomas Munro <[email protected]> 2024-08-28 18:50 ` Re: On non-Windows, hard depend on uselocale(3) Peter Eisentraut <[email protected]> 2024-10-01 12:04 ` Re: On non-Windows, hard depend on uselocale(3) Peter Eisentraut <[email protected]> 2024-11-14 01:54 ` Re: On non-Windows, hard depend on uselocale(3) Thomas Munro <[email protected]> 2024-11-14 07:48 ` Re: On non-Windows, hard depend on uselocale(3) Thomas Munro <[email protected]> 2024-11-14 09:54 ` Re: On non-Windows, hard depend on uselocale(3) Heikki Linnakangas <[email protected]> 2024-11-14 12:53 ` Re: On non-Windows, hard depend on uselocale(3) Peter Eisentraut <[email protected]> 2025-03-28 15:30 ` Re: On non-Windows, hard depend on uselocale(3) Peter Eisentraut <[email protected]> 2025-03-28 16:14 ` Re: On non-Windows, hard depend on uselocale(3) Masahiko Sawada <[email protected]> 2025-03-28 16:32 ` Re: On non-Windows, hard depend on uselocale(3) Peter Eisentraut <[email protected]> 2025-03-28 20:34 ` Re: On non-Windows, hard depend on uselocale(3) Peter Eisentraut <[email protected]> 2025-03-29 00:01 ` Re: On non-Windows, hard depend on uselocale(3) Masahiko Sawada <[email protected]> 2025-12-12 18:07 ` Re: On non-Windows, hard depend on uselocale(3) Tom Lane <[email protected]> 2023-11-18 00:03 ` Re: On non-Windows, hard depend on uselocale(3) Andres Freund <[email protected]> 2023-11-15 18:42 ` Re: On non-Windows, hard depend on uselocale(3) Dagfinn Ilmari Mannsåker <[email protected]> 2023-11-15 19:04 ` Re: On non-Windows, hard depend on uselocale(3) Tom Lane <[email protected]> 2023-11-15 19:16 ` Re: On non-Windows, hard depend on uselocale(3) Thomas Munro <[email protected]> 2026-04-03 19:01 [PATCH v51 10/10] CheckLogicalDecodingRequirements: be specific about which GUC is limiting Álvaro Herrera <[email protected]> 2026-04-03 19:01 [PATCH v52 10/10] CheckLogicalDecodingRequirements: be specific about which GUC is limiting Álvaro Herrera <[email protected]> 2026-04-03 19:01 [PATCH v52 10/10] CheckLogicalDecodingRequirements: be specific about which GUC is limiting Álvaro Herrera <[email protected]> 2026-04-03 19:01 [PATCH v51 10/10] CheckLogicalDecodingRequirements: be specific about which GUC is limiting Álvaro Herrera <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox