pg_threads.h take II

Started by Thomas Munro20 days ago7 messageshackers
Jump to latest
#1Thomas Munro
thomas.munro@gmail.com

Hi,

Here's another go at port/pg_threads.h, a C11 <threads.h>-like
interface, with some patches to use it (some just sketch-quality).
Previous work:

/messages/by-id/CA+hUKGLtmexrpMtxBRLCVePqV_dtWG-ZsEbyPrYc+NBB2TkNsw@mail.gmail.com

We still can't require C11 <threads.h> yet, though the interface has a
clear future upgrade/remap path to do that eventually. Since I last
wrote about this topic, we gained the ability to count on C11
thread_local being available (it's a language/compiler feature and
doesn't need a <threads.h> header or library), and partly because of
that, but also because I don't think anyone really likes it, I ripped
out that tss_t stuff. I came up with a narrower API to get
thread-exit cleanup callbacks if you need them for your thread_local
resources, same pthread_key_t/FlsAlloc stuff underneath it but without
having to get lost in the weeds of <threads.h> conformance.

v1-0001-ecpg-Fix-auto_mem-cleanup-on-thread-exit.patch

Already discussed in another thread, a couple of existing ECPG
thread-exit cleanup bugs need to be fixed (bugs that cancel each other
out in master, but leak, and with my patches the leak was fixed in
passing but then a double-free would crash):

/messages/by-id/CA+hUKGLv202ndx=kP1fZJapNcbfnpq-rezHNyt+cQ4pdxr1akg@mail.gmail.com

v1-0002-port-Provide-minimal-pg_threads.h-API.patch

The main thing I'm wondering about is how to make the error reporting
a bit nicer. Neither pthreads nor C11 threads set errno, but it's
nice if you can use %m. Hmm.

v1-0003-port-Use-pg_threads.h-API-for-pg_localconv_r.patch

Trivial.

v1-0004-ecpg-Improve-variable-name.patch
v1-0005-ecpg-Use-pg_threads.h.patch

These demonstrate the replacement of pthread_key_t with thread_local,
and where needed, pg_thrd_atexit_t. That's the narrower thing I came
up with to replace pthread_t/tss_t with something that more directly
models what we want, I think.

I could do pg_tss_t, but it takes a lot of extra book-keeping code to
make Windows FlsAlloc match the spec.

v1-0006-pgbench-Use-pg_threads.h.patch

Kills another local thread-porting abstraction.

v1-0007-libpq-Use-pg_threads.h.patch

Kills another local thread-porting abstraction.

v1-0008-pg_basebackup-Use-pg_threads.h.patch

Kills the fork() mode used for Unix, harmonises Unix and Windows.

v1-0009-pg_dump-Remove-TerminateThread-call.patch

This one already has a discussion thread (I figured it might be
another back-patchable issue, IDK), and it looks like the solution I
used here isn't what we want to go with for the future, but to do it
the way Heikki and Jelte are suggesting will probably require deciding
how to access atomics or at least compiler barrires in front-end code,
which I'll look into this week.

Consider this approach a stand-in for now, as I needed to get rid of a
TerminateThread() call somehow. We can't have that in pg_threads.h
for various reasons (it's all kinds of undefined).

/messages/by-id/CA+hUKGJgO=o-vLFahGdR2WesuX3h1-0j=a8z72fChc-MG1Hveg@mail.gmail.com

v1-0010-pg_dump-Block-signals-during-signal-handler.patch

Not very interesting, I was just triggered by steampunk Unix
anachronisms in passing... an experimental patch further down
replaces all of this anyway.

v1-0011-pg_dump-Use-pg_threads.h-and-remove-fork-path.patch

Kills the fork() mode used for Unix, harmonises Unix and Windows.
After this patch, the cancellation stuff admittedly looks pretty
shaky, but I wanted to separate the fork()-ectomy from any larger
refactoring, so bear with me...

Next I was planning to rip out the pipes that fork()-mode needed and
use an in-memory work queue, and probably make a reusable pg_thrd_pool
instead of open coding it here, but it turned out that Bryan was
already way ahead of me there:

/messages/by-id/8c712d76-ecf7-4749-a6d8-dddc01f298ec@gmail.com

Luckily it looks like he started working at that end of the problem
and I started with this fork()-ectomy/harmonisation piece, so I
think/hope that we can fit those efforts together quite easily... I
will look at his patches this week.

v1-0012-pg_signal_processor-Infrastructure-for-cleanup.patch
v1-0013-fe_utils-Provide-cancel_set-for-fast-quit-paths.patch
v1-0014-pg_dump-Refactor-connection-cancel-management.patch

I considered lots of different ways to make pg_dump cleanup better,
and this is the approach that *seemed* to be roughly along the lines
that Jelte suggested he wanted for various reasons, over in the
TerminateThread() thread. Namely, make it so that Unix and Windows
both run your cleanup code on a thread, but also force Windows to
serialise the handlers, so that the semantics match and we can escape
from the constraints of async-signal-safe signal handlers. On Unix it
has a thread sitting there waiting for you to hit ^C, just looping
over sigwait() and calling handlers serially, that are then free to
use arbitrarily complex code before _exit() turns out the lights.

Warning/disclaimer: 0012-0014 are only experimental sketches trying
stuff out, and might not work that well in various ways, I just wanted
to get your take on this general direction. Could skin this in lots
of other ways too and since you guys are actively hacking on this area
you might already have another idea in mind or even patches?

After these patches, there are a few places left that use raw Windows
threading APIs, but they're places that are creating threads to solve
signaling and I/O problems that have better solutions, topics for
another email (but briefly I mean: the interrupts/latches refactoring
patch can kill "our" signal-emulation threads, and support for
overlapped I/O can allow multiplexing pipe I/O with latches without
intermediate data-pumping threads (cf Run::IPC which also has that
kind of Windows-hates-select() problem), and support for process-exit
WaitEvents can kill another helper thread, ... yada yada).

Attachments:

v1-0001-ecpg-Fix-auto_mem-cleanup-on-thread-exit.patchtext/x-patch; charset=US-ASCII; name=v1-0001-ecpg-Fix-auto_mem-cleanup-on-thread-exit.patchDownload+8-4
v1-0002-port-Provide-minimal-pg_threads.h-API.patchtext/x-patch; charset=US-ASCII; name=v1-0002-port-Provide-minimal-pg_threads.h-API.patchDownload+843-1
v1-0003-port-Use-pg_threads.h-API-for-pg_localconv_r.patchtext/x-patch; charset=US-ASCII; name=v1-0003-port-Use-pg_threads.h-API-for-pg_localconv_r.patchDownload+6-6
v1-0004-ecpg-Improve-variable-name.patchtext/x-patch; charset=US-ASCII; name=v1-0004-ecpg-Improve-variable-name.patchDownload+6-7
v1-0005-ecpg-Use-pg_threads.h.patchtext/x-patch; charset=US-ASCII; name=v1-0005-ecpg-Use-pg_threads.h.patchDownload+285-697
v1-0006-pgbench-Use-pg_threads.h.patchtext/x-patch; charset=US-ASCII; name=v1-0006-pgbench-Use-pg_threads.h.patchDownload+15-195
v1-0007-libpq-Use-pg_threads.h.patchtext/x-patch; charset=US-ASCII; name=v1-0007-libpq-Use-pg_threads.h.patchDownload+21-140
v1-0008-pg_basebackup-Use-pg_threads.h.patchtext/x-patch; charset=US-ASCII; name=v1-0008-pg_basebackup-Use-pg_threads.h.patchDownload+49-208
v1-0009-pg_dump-Remove-TerminateThread-call.patchtext/x-patch; charset=US-ASCII; name=v1-0009-pg_dump-Remove-TerminateThread-call.patchDownload+31-33
v1-0010-pg_dump-Block-signals-during-signal-handler.patchtext/x-patch; charset=US-ASCII; name=v1-0010-pg_dump-Block-signals-during-signal-handler.patchDownload+17-14
v1-0011-pg_dump-Use-pg_threads.h-and-remove-fork-path.patchtext/x-patch; charset=US-ASCII; name=v1-0011-pg_dump-Use-pg_threads.h-and-remove-fork-path.patchDownload+167-460
v1-0012-pg_signal_processor-Infrastructure-for-cleanup.patchtext/x-patch; charset=US-ASCII; name=v1-0012-pg_signal_processor-Infrastructure-for-cleanup.patchDownload+206-1
v1-0013-fe_utils-Provide-cancel_set-for-fast-quit-paths.patchtext/x-patch; charset=US-ASCII; name=v1-0013-fe_utils-Provide-cancel_set-for-fast-quit-paths.patchDownload+182-1
v1-0014-pg_dump-Refactor-connection-cancel-management.patchtext/x-patch; charset=US-ASCII; name=v1-0014-pg_dump-Refactor-connection-cancel-management.patchDownload+165-391
#2Peter Eisentraut
peter_e@gmx.net
In reply to: Thomas Munro (#1)
Re: pg_threads.h take II

On 06.07.26 14:09, Thomas Munro wrote:

We still can't require C11 <threads.h> yet, though the interface has a
clear future upgrade/remap path to do that eventually. Since I last
wrote about this topic, we gained the ability to count on C11
thread_local being available

We still support platforms/compilers where it's not available, but

/messages/by-id/2a965ac6-fa42-4054-bee0-b1618e7729d6@eisentraut.org

which is ready to be committed, will de-support those, so by the time
this patch set is ready, we'll be fine.

#3Thomas Munro
thomas.munro@gmail.com
In reply to: Thomas Munro (#1)
Re: pg_threads.h take II

On Tue, Jul 7, 2026 at 12:09 AM Thomas Munro <thomas.munro@gmail.com> wrote:

used here isn't what we want to go with for the future, but to do it
the way Heikki and Jelte are suggesting will probably require deciding
how to access atomics or at least compiler barrires in front-end code,
which I'll look into this week.

Correction: at least memory barriers

#4Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Thomas Munro (#1)
Re: pg_threads.h take II

On 06/07/2026 15:09, Thomas Munro wrote:

Hi,

Here's another go at port/pg_threads.h, a C11 <threads.h>-like
interface, with some patches to use it (some just sketch-quality).
Previous work:

/messages/by-id/CA+hUKGLtmexrpMtxBRLCVePqV_dtWG-ZsEbyPrYc+NBB2TkNsw@mail.gmail.com

We still can't require C11 <threads.h> yet, though the interface has a
clear future upgrade/remap path to do that eventually. Since I last
wrote about this topic, we gained the ability to count on C11
thread_local being available (it's a language/compiler feature and
doesn't need a <threads.h> header or library), and partly because of
that, but also because I don't think anyone really likes it, I ripped
out that tss_t stuff. I came up with a narrower API to get
thread-exit cleanup callbacks if you need them for your thread_local
resources, same pthread_key_t/FlsAlloc stuff underneath it but without
having to get lost in the weeds of <threads.h> conformance.

Nice!

I've now reviewed patches 0001-0007. I think they're ready to be
committed, with some small comments below:

v1-0002-port-Provide-minimal-pg_threads.h-API.patch

The main thing I'm wondering about is how to make the error reporting
a bit nicer. Neither pthreads nor C11 threads set errno, but it's
nice if you can use %m. Hmm.

Perhaps provide a function like:

void
set_errno_from_pg_thrd_error(int error)
{
switch((pg_thrd_error_t) error)
{
case pg_thrd_nomem:
errno = ENOMEM;
break;
case pg_thrd_busy:
errno = EBUSY;
break;
...
}
}

+/* Convert native error to pg_thrd_error_t. */
+static inline int
+pg_thrd_maperror(int error)
+{
+#ifdef WIN32
+	return error ? pg_thrd_success : pg_thrd_error;
+#else
+	return error == 0 ? pg_thrd_success : pg_thrd_error;
+#endif
+}

The WIN32 version of this is confusing. There is only one caller of this
with WIN32:

+#elif defined(WIN32)
+	return pg_thrd_maperror(InitializeSynchronizationBarrier(barrier, count, 0));
+#else

So that's not wrong. But I'd suggest only defining pg_thrd_maperror()
with pthreads, and handling that one WIN32 caller directly without the
helper function. Maybe rename pg_thrd_maperror() to something like
map_pthread_retval_to_pg_thrd_error() or something.

Do the pthread functions return an errno on error? Currently, all errors
get squashed to pg_thrd_error, which is a little unfortunate.

+/* Like C11 mtx_type_t. */
+typedef enum pg_mtx_type_t
+{
+	pg_mtx_plain = 0
+} pg_mtx_type_t;
+
+/* Like C11 mtx_init(). */
+static inline int
+pg_mtx_init(pg_mtx_t *mutex, int type)
+{
+#ifdef WIN32
+	return pg_rwlock_init(mutex);
+#else
+	return pg_thrd_maperror(pthread_mutex_init(mutex, NULL));
+#endif
+}

Since we only support plain mutexes, how about "Assert(type ==
pg_mtx_plan)" here?

+/*-------------------------------------------------------------------------
+ *
+ * Barriers.  Not in C11.
+ *
+ *-------------------------------------------------------------------------
+ */

I wonder if we really need barriers. They're not that useful IMHO. I'd
tend to just open-code this directly with a mutex and the condition
variable in most cases. (Not a strong objection, there's little harm in
having it either)

+/*
+ * Wait for all expected threads to arrive at the barrier, and elect one
+ * arbitrary thread to perform a phase of computation serially.  Sets
+ * *elected_thread to true in the elected thread, and false in all others.
+ */
+static inline int
+pg_barrier_wait_and_elect(pg_barrier_t *barrier, bool *elected_thread)
+{

No callers use the *elected_thread return value. What was the idea here?

+#ifdef WIN32
+
+	/*
+	 * Retrieve handle passed here by pg_thrd_create() before allowing this
+	 * thread to run.  (pg_thrd_current() can't use CurrentThread(), because
+	 * that returns a pseudo-handle with the same value in all threads.)
+	 */
+	Assert(start_info->self);
+	my_thrd_handle = start_info->self;
+#endif

Does that refer to the GetCurrentThreadId() function? We use that in a
few places currently.

How can it return the same value in all threads, isn't that completely
useless? And does that mean all our current uses of it are broken?

- Heikki

#5Bryan Green
dbryan.green@gmail.com
In reply to: Heikki Linnakangas (#4)
Re: pg_threads.h take II

On 7/6/26 3:48 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 06/07/2026 15:09, Thomas Munro wrote:

Hi,

Here's another go at port/pg_threads.h, a C11 <threads.h>-like
interface, with some patches to use it (some just sketch-quality).
Previous work:

/messages/by-id/flat/
CA%2BhUKGLtmexrpMtxBRLCVePqV_dtWG-ZsEbyPrYc%2BNBB2TkNsw%40mail.gmail.com

We still can't require C11 <threads.h> yet, though the interface has a
clear future upgrade/remap path to do that eventually.  Since I last
wrote about this topic, we gained the ability to count on C11
thread_local being available (it's a language/compiler feature and
doesn't need a <threads.h> header or library), and partly because of
that, but also because I don't think anyone really likes it, I ripped
out that tss_t stuff.  I came up with a narrower API to get
thread-exit cleanup callbacks if you need them for your thread_local
resources, same pthread_key_t/FlsAlloc stuff underneath it but without
having to get lost in the weeds of <threads.h> conformance.

Nice!

I've now reviewed patches 0001-0007. I think they're ready to be
committed, with some small comments below:

v1-0002-port-Provide-minimal-pg_threads.h-API.patch

The main thing I'm wondering about is how to make the error reporting
a bit nicer.  Neither pthreads nor C11 threads set errno, but it's
nice if you can use %m.  Hmm.

Perhaps provide a function like:

void
set_errno_from_pg_thrd_error(int error)
{
    switch((pg_thrd_error_t) error)
    {
        case pg_thrd_nomem:
            errno = ENOMEM;
            break;
        case pg_thrd_busy:
            errno = EBUSY;
            break;
       ...
    }
}

+/* Convert native error to pg_thrd_error_t. */
+static inline int
+pg_thrd_maperror(int error)
+{
+#ifdef WIN32
+    return error ? pg_thrd_success : pg_thrd_error;
+#else
+    return error == 0 ? pg_thrd_success : pg_thrd_error;
+#endif
+}

The WIN32 version of this is confusing. There is only one caller of this
with WIN32:

+#elif defined(WIN32)
+    return pg_thrd_maperror(InitializeSynchronizationBarrier(barrier,
count, 0));
+#else

So that's not wrong. But I'd suggest only defining pg_thrd_maperror()
with pthreads, and handling that one WIN32 caller directly without the
helper function. Maybe rename pg_thrd_maperror() to something like
map_pthread_retval_to_pg_thrd_error() or something.

Do the pthread functions return an errno on error? Currently, all errors
get squashed to pg_thrd_error, which is a little unfortunate.

+/* Like C11 mtx_type_t. */
+typedef enum pg_mtx_type_t
+{
+    pg_mtx_plain = 0
+} pg_mtx_type_t;
+
+/* Like C11 mtx_init(). */
+static inline int
+pg_mtx_init(pg_mtx_t *mutex, int type)
+{
+#ifdef WIN32
+    return pg_rwlock_init(mutex);
+#else
+    return pg_thrd_maperror(pthread_mutex_init(mutex, NULL));
+#endif
+}

Since we only support plain mutexes, how about "Assert(type ==
pg_mtx_plan)" here?

+/
*-------------------------------------------------------------------------
+ *
+ * Barriers.  Not in C11.
+ *
+
*-------------------------------------------------------------------------
+ */

I wonder if we really need barriers. They're not that useful IMHO. I'd
tend to just open-code this directly with a mutex and the condition
variable in most cases. (Not a strong objection, there's little harm in
having it either)

+/*
+ * Wait for all expected threads to arrive at the barrier, and elect one
+ * arbitrary thread to perform a phase of computation serially.  Sets
+ * *elected_thread to true in the elected thread, and false in all
others.
+ */
+static inline int
+pg_barrier_wait_and_elect(pg_barrier_t *barrier, bool *elected_thread)
+{

No callers use the *elected_thread return value. What was the idea here?

+#ifdef WIN32
+
+    /*
+     * Retrieve handle passed here by pg_thrd_create() before
allowing this
+     * thread to run.  (pg_thrd_current() can't use CurrentThread(),
because
+     * that returns a pseudo-handle with the same value in all threads.)
+     */
+    Assert(start_info->self);
+    my_thrd_handle = start_info->self;
+#endif

Does that refer to the GetCurrentThreadId() function? We use that in a
few places currently.

How can it return the same value in all threads, isn't that completely
useless? And does that mean all our current uses of it are broken?

- Heikki

GetCurrentThread() not GetCurrentThreadId()...  GetCurrentThread()
returns a special sentinel handle (-2) that when passed to another
Win32 function causes the kernel to resolve to whatever thread made
the call....

So, no-- not broken.

#6Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Bryan Green (#5)
Re: pg_threads.h take II

On 07/07/2026 00:01, dbryan.green@gmail.com wrote:

On 7/6/26 3:48 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 06/07/2026 15:09, Thomas Munro wrote:

+#ifdef WIN32
+
+    /*
+     * Retrieve handle passed here by pg_thrd_create() before
allowing this
+     * thread to run.  (pg_thrd_current() can't use CurrentThread(),
because
+     * that returns a pseudo-handle with the same value in all threads.)
+     */
+    Assert(start_info->self);
+    my_thrd_handle = start_info->self;
+#endif

Does that refer to the GetCurrentThreadId() function? We use that in a
few places currently.

How can it return the same value in all threads, isn't that completely
useless? And does that mean all our current uses of it are broken?

GetCurrentThread() not GetCurrentThreadId()...  GetCurrentThread()
returns a special sentinel handle (-2) that when passed to another
Win32 function causes the kernel to resolve to whatever thread made
the call....

So, no-- not broken.

Ah gotcha. Could we use GetCurrentThreadId() here then? (I have no
problem with the way it's currently done in the patch either though,
just curious)

- Heikki

#7Bryan Green
dbryan.green@gmail.com
In reply to: Heikki Linnakangas (#6)
Re: pg_threads.h take II

On 7/6/2026 4:13 PM, Heikki Linnakangas wrote:

On 07/07/2026 00:01, dbryan.green@gmail.com wrote:

On 7/6/26 3:48 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 06/07/2026 15:09, Thomas Munro wrote:

+#ifdef WIN32
+
+    /*
+     * Retrieve handle passed here by pg_thrd_create() before
allowing this
+     * thread to run.  (pg_thrd_current() can't use CurrentThread(),
because
+     * that returns a pseudo-handle with the same value in all
threads.)
+     */
+    Assert(start_info->self);
+    my_thrd_handle = start_info->self;
+#endif

Does that refer to the GetCurrentThreadId() function? We use that in a
few places currently.

How can it return the same value in all threads, isn't that completely
useless? And does that mean all our current uses of it are broken?

GetCurrentThread() not GetCurrentThreadId()...  GetCurrentThread()
returns a special sentinel handle (-2) that when passed to another
Win32 function causes the kernel to resolve to whatever thread made
the call....

So, no-- not broken.

Ah gotcha. Could we use GetCurrentThreadId() here then? (I have no
problem with the way it's currently done in the patch either though,
just curious)

- Heikki

I haven't looked at all of the code, but I would assume there is a
WaitFor...Object() somewhere and those only work with actual
handles...not id's. GetCurrentThreadId() returns a dword for id. I'll
look over the patches as well.

--
Bryan Green
EDB: https://www.enterprisedb.com