Subject: reorganize "Shared Memory and LWLocks" section of docs

From: Nathan Bossart <[email-masked]>
Date: Thu, 11 Jan 2024 22:14:30 -0600

I recently began trying to write documentation for the dynamic shared
memory registry feature [0], and I noticed that the "Shared Memory and
LWLocks" section of the documentation might need some improvement.  At
least, I felt that it would be hard to add any new content to this section
without making it very difficult to follow.

Concretely, I am proposing breaking it into two sections: one for shared
memory and one for LWLocks.  Furthermore, the LWLocks section will be split
into two: one for requesting locks at server startup and one for requesting
locks after server startup.  I intend to also split the shared memory
section into at-startup and after-startup sections if/when the dynamic
shared memory registry feature is committed.

Besides this restructuring, I felt that certain parts of this documentation
could benefit from rephrasing and/or additional detail.

Thoughts?

[0] https://postgr.es/m/20231205034647.GA2705267%40nathanxps13

-- 
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
>From dc5c1b37ced883021f4a92a17aa50b9d80d73fc6 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email-masked]>
Date: Thu, 11 Jan 2024 21:55:25 -0600
Subject: [PATCH v1 1/1] reorganize shared memory and lwlocks documentation

---
 doc/src/sgml/xfunc.sgml | 180 +++++++++++++++++++++++++---------------
 1 file changed, 112 insertions(+), 68 deletions(-)

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 89116ae74c..a758384a9a 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3397,90 +3397,134 @@ CREATE FUNCTION make_array(anyelement) RETURNS anyarray
    </sect2>
 
    <sect2 id="xfunc-shared-addin">
-    <title>Shared Memory and LWLocks</title>
+    <title>Shared Memory</title>
 
-    <para>
-     Add-ins can reserve LWLocks and an allocation of shared memory on server
-     startup.  The add-in's shared library must be preloaded by specifying
-     it in
-     <xref linkend="guc-shared-preload-libraries"/><indexterm><primary>shared_preload_libraries</primary></indexterm>.
-     The shared library should register a <literal>shmem_request_hook</literal>
-     in its <function>_PG_init</function> function.  This
-     <literal>shmem_request_hook</literal> can reserve LWLocks or shared memory.
-     Shared memory is reserved by calling:
+    <sect3 id="xfunc-shared-addin-at-startup">
+     <title>Requesting Shared Memory at Startup</title>
+
+     <para>
+      Add-ins can reserve shared memory on server startup.  To do so, the
+      add-in's shared library must be preloaded by specifying it in
+      <xref linkend="guc-shared-preload-libraries"/><indexterm><primary>shared_preload_libraries</primary></indexterm>.
+      The shared library should also register a
+      <literal>shmem_request_hook</literal> in its
+      <function>_PG_init</function> function.  This
+      <literal>shmem_request_hook</literal> can reserve shared memory by
+      calling:
 <programlisting>
 void RequestAddinShmemSpace(int size)
 </programlisting>
-     from your <literal>shmem_request_hook</literal>.
-    </para>
-    <para>
-     LWLocks are reserved by calling:
+      Each backend sould obtain a pointer to the reserved shared memory by
+      calling:
+<programlisting>
+void *ShmemInitStruct(const char *name, Size size, bool *foundPtr)
+</programlisting>
+      If this function sets <literal>foundPtr</literal> to
+      <literal>false</literal>, the caller should proceed to initialize the
+      contents of the reserved shared memory.  If <literal>foundPtr</literal>
+      is set to <literal>true</literal>, the shared memory was already
+      initialized by another backend, and the caller need not initialize
+      further.
+     </para>
+
+     <para>
+      To avoid race conditions, each backend should use the LWLock
+      <function>AddinShmemInitLock</function> when initializing its allocation
+      of shared memory, as shown here:
+<programlisting>
+static mystruct *ptr = NULL;
+bool        found;
+
+LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
+ptr = ShmemInitStruct("my struct name", size, &amp;found);
+if (!found)
+{
+    ... initialize contents of shared memory ...
+    ptr->locks = GetNamedLWLockTranche("my tranche name");
+}
+LWLockRelease(AddinShmemInitLock);
+</programlisting>
+      <literal>shmem_startup_hook</literal> provides a convenient place for the
+      initialization code, but it is not strictly required that all such code
+      be placed in this hook.  Any registered
+      <literal>shmem_startup_hook</literal> will be executed shortly after each
+      backend attaches to shared memory.  Note that add-ins should still
+      acquire <function>AddinShmemInitLock</function> within this hook, as
+      shown in the example above.
+     </para>
+
+     <para>
+      An example of a <literal>shmem_request_hook</literal> and
+      <literal>shmem_startup_hook</literal> can be found in
+      <filename>contrib/pg_stat_statements/pg_stat_statements.c</filename> in
+      the <productname>PostgreSQL</productname> source tree.
+     </para>
+    </sect3>
+   </sect2>
+
+   <sect2 id="xfunc-addin-lwlocks">
+    <title>LWLocks</title>
+
+    <sect3 id="xfunc-addin-lwlocks-at-startup">
+     <title>Requesting LWLocks at Startup</title>
+
+     <para>
+      Add-ins can reserve LWLocks on server startup.  Like with shared memory,
+      the add-in's shared library must be preloaded by specifying it in
+      <xref linkend="guc-shared-preload-libraries"/><indexterm><primary>shared_preload_libraries</primary></indexterm>,
+      and the shared library should register a
+      <literal>shmem_request_hook</literal> in its
+      <function>_PG_init</function> function.  This
+      <literal>shmem_request_hook</literal> can reserve LWLocks by calling:
 <programlisting>
 void RequestNamedLWLockTranche(const char *tranche_name, int num_lwlocks)
 </programlisting>
-     from your <literal>shmem_request_hook</literal>.  This will ensure that an array of
-     <literal>num_lwlocks</literal> LWLocks is available under the name
-     <literal>tranche_name</literal>.  Use <function>GetNamedLWLockTranche</function>
-     to get a pointer to this array.
-    </para>
-    <para>
-     An example of a <literal>shmem_request_hook</literal> can be found in
-     <filename>contrib/pg_stat_statements/pg_stat_statements.c</filename> in the
-     <productname>PostgreSQL</productname> source tree.
-    </para>
-    <para>
-     There is another, more flexible method of obtaining LWLocks. First,
-     allocate a <literal>tranche_id</literal> from a shared counter by
-     calling:
+      This ensures that an array of <literal>num_lwlocks</literal> LWLocks is
+      available under the name <literal>tranche_name</literal>.  A pointer to
+      this array can be obtained by calling:
 <programlisting>
-int LWLockNewTrancheId(void)
+LWLockPadded *GetNamedLWLockTranche(const char *tranche_name)
 </programlisting>
-     Next, each individual process using the <literal>tranche_id</literal>
-     should associate it with a <literal>tranche_name</literal> by calling:
+     </para>
+    </sect3>
+
+    <sect3 id="xfunc-addin-lwlocks-after-startup">
+     <title>Requesting LWLocks After Startup</title>
+
+     <para>
+      There is another, more flexible method of obtaining LWLocks that can be
+      done after server startup and outside a
+      <literal>shmem_request_hook</literal>.  To do so, first allocate a
+      <literal>tranche_id</literal> by calling:
 <programlisting>
-void LWLockRegisterTranche(int tranche_id, const char *tranche_name)
+int LWLockNewTrancheId(void)
 </programlisting>
-     It is also required to call <function>LWLockInitialize</function> once
-     per LWLock, passing the <literal>tranche_id</literal> as argument:
+      Next, initialize each LWLock, passing the new
+      <literal>tranche_id</literal> as an argument:
 <programlisting>
 void LWLockInitialize(LWLock *lock, int tranche_id)
 </programlisting>
-     A complete usage example of <function>LWLockNewTrancheId</function>,
-     <function>LWLockInitialize</function> and
-     <function>LWLockRegisterTranche</function> can be found in
-     <filename>contrib/pg_prewarm/autoprewarm.c</filename> in the
-     <productname>PostgreSQL</productname> source tree.
-    </para>
-    <para>
-     To avoid possible race-conditions, each backend should use the LWLock
-     <function>AddinShmemInitLock</function> when connecting to and initializing
-     its allocation of shared memory, as shown here:
+      Finally, each backend using the <literal>tranche_id</literal> should
+      associate it with a <literal>tranche_name</literal> by calling:
 <programlisting>
-static mystruct *ptr = NULL;
-
-if (!ptr)
-{
-        bool    found;
-
-        LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
-        ptr = ShmemInitStruct("my struct name", size, &amp;found);
-        if (!found)
-        {
-                initialize contents of shmem area;
-                acquire any requested LWLocks using:
-                ptr->locks = GetNamedLWLockTranche("my tranche name");
-        }
-        LWLockRelease(AddinShmemInitLock);
-}
+void LWLockRegisterTranche(int tranche_id, const char *tranche_name)
 </programlisting>
-    </para>
-    <para>
-     It is convenient to use <literal>shmem_startup_hook</literal> which allows
-     placing all the code responsible for initializing shared memory in one
-     place. When using <literal>shmem_startup_hook</literal> the extension
-     still needs to acquire <function>AddinShmemInitLock</function> in order to
-     work properly on all the supported platforms.
-    </para>
+      Similar to shared memory, each backend should ensure that only one
+      process allocates a new <literal>tranche_id</literal>
+      (<function>LWLockNewTrancheId</function>) and initializes each new LWLock
+      (<function>LWLockInitialize</function>).  One way to do this is to only
+      call these functions in your shared memory initialization code with the
+      <function>AddinShmemInitLock</function> held exclusively.
+     </para>
+
+     <para>
+      A complete usage example of <function>LWLockNewTrancheId</function>,
+      <function>LWLockInitialize</function>, and
+      <function>LWLockRegisterTranche</function> can be found in
+      <filename>contrib/pg_prewarm/autoprewarm.c</filename> in the
+      <productname>PostgreSQL</productname> source tree.
+     </para>
+    </sect3>
    </sect2>
 
    <sect2 id="xfunc-addin-wait-events">
-- 
2.25.1