Allow lwlocks to be disowned
authorAndres Freund <[email protected]>
Sat, 22 Feb 2025 01:55:23 +0000 (20:55 -0500)
committerAndres Freund <[email protected]>
Sat, 22 Feb 2025 01:55:23 +0000 (20:55 -0500)
To implement AIO writes, the backend initiating writes needs to transfer the
lock ownership to the AIO subsystem, so the lock held during the write can be
released in another backend.

Other backends need to be able to "complete" an asynchronously started IO to
avoid deadlocks (consider e.g. one backend starting IO for a buffer and then
waiting for a heavyweight lock held by another relation followed by the
current holder of the heavyweight lock waiting for the IO to complete).

To that end, this commit adds LWLockDisown() and LWLockReleaseDisowned(). If
code uses LWLockDisown() it's the code's responsibility to ensure that the
lock is released in case of errors.

Reviewed-by: Heikki Linnakangas <[email protected]>
Discussion: https://postgr.es/m/1f6b50a7-38ef-4d87-8246-786d39f46ab9@iki.fi

src/backend/storage/lmgr/lwlock.c
src/include/storage/lwlock.h

index f1e74f184f102748ec47bfe6d1893247bb49fc93..8a7fb6a22c6a44206ebd3fcfcc7153b9c790f642 100644 (file)
@@ -1775,14 +1775,25 @@ LWLockUpdateVar(LWLock *lock, pg_atomic_uint64 *valptr, uint64 val)
 
 
 /*
- * LWLockRelease - release a previously acquired lock
+ * Stop treating lock as held by current backend.
+ *
+ * This is the code that can be shared between actually releasing a lock
+ * (LWLockRelease()) and just not tracking ownership of the lock anymore
+ * without releasing the lock (LWLockDisown()).
+ *
+ * Returns the mode in which the lock was held by the current backend.
+ *
+ * NB: This does not call RESUME_INTERRUPTS(), but leaves that responsibility
+ * of the caller.
+ *
+ * NB: This will leave lock->owner pointing to the current backend (if
+ * LOCK_DEBUG is set). This is somewhat intentional, as it makes it easier to
+ * debug cases of missing wakeups during lock release.
  */
-void
-LWLockRelease(LWLock *lock)
+static inline LWLockMode
+LWLockDisownInternal(LWLock *lock)
 {
    LWLockMode  mode;
-   uint32      oldstate;
-   bool        check_waiters;
    int         i;
 
    /*
@@ -1802,7 +1813,18 @@ LWLockRelease(LWLock *lock)
    for (; i < num_held_lwlocks; i++)
        held_lwlocks[i] = held_lwlocks[i + 1];
 
-   PRINT_LWDEBUG("LWLockRelease", lock, mode);
+   return mode;
+}
+
+/*
+ * Helper function to release lock, shared between LWLockRelease() and
+ * LWLockeleaseDisowned().
+ */
+static void
+LWLockReleaseInternal(LWLock *lock, LWLockMode mode)
+{
+   uint32      oldstate;
+   bool        check_waiters;
 
    /*
     * Release my hold on lock, after that it can immediately be acquired by
@@ -1840,6 +1862,38 @@ LWLockRelease(LWLock *lock)
        LOG_LWDEBUG("LWLockRelease", lock, "releasing waiters");
        LWLockWakeup(lock);
    }
+}
+
+
+/*
+ * Stop treating lock as held by current backend.
+ *
+ * After calling this function it's the callers responsibility to ensure that
+ * the lock gets released (via LWLockReleaseDisowned()), even in case of an
+ * error. This only is desirable if the lock is going to be released in a
+ * different process than the process that acquired it.
+ */
+void
+LWLockDisown(LWLock *lock)
+{
+   LWLockDisownInternal(lock);
+
+   RESUME_INTERRUPTS();
+}
+
+/*
+ * LWLockRelease - release a previously acquired lock
+ */
+void
+LWLockRelease(LWLock *lock)
+{
+   LWLockMode  mode;
+
+   mode = LWLockDisownInternal(lock);
+
+   PRINT_LWDEBUG("LWLockRelease", lock, mode);
+
+   LWLockReleaseInternal(lock, mode);
 
    /*
     * Now okay to allow cancel/die interrupts.
@@ -1847,6 +1901,15 @@ LWLockRelease(LWLock *lock)
    RESUME_INTERRUPTS();
 }
 
+/*
+ * Release lock previously disowned with LWLockDisown().
+ */
+void
+LWLockReleaseDisowned(LWLock *lock, LWLockMode mode)
+{
+   LWLockReleaseInternal(lock, mode);
+}
+
 /*
  * LWLockReleaseClearVar - release a previously acquired lock, reset variable
  */
index 2aa46fd50da1fcb3a38975120268e7bee23f7372..13a7dc89980490c49f2179cd77dc52d1bbfbf734 100644 (file)
@@ -129,6 +129,8 @@ extern bool LWLockAcquireOrWait(LWLock *lock, LWLockMode mode);
 extern void LWLockRelease(LWLock *lock);
 extern void LWLockReleaseClearVar(LWLock *lock, pg_atomic_uint64 *valptr, uint64 val);
 extern void LWLockReleaseAll(void);
+extern void LWLockDisown(LWLock *l);
+extern void LWLockReleaseDisowned(LWLock *l, LWLockMode mode);
 extern bool LWLockHeldByMe(LWLock *lock);
 extern bool LWLockAnyHeldByMe(LWLock *lock, int nlocks, size_t stride);
 extern bool LWLockHeldByMeInMode(LWLock *lock, LWLockMode mode);