Enables to call Unregister*XactCallback() in Call*XactCallback()

Started by Hao Wualmost 4 years ago6 messages
#1Hao Wu
gfphoenix78@gmail.com
1 attachment(s)

Hi hackers,

It's a natural requirement to unregister the callback for transaction or
subtransaction when the callback is invoked, so we don't have to
unregister the callback somewhere. If it's not allowed to do it
in CallXactCallback() or CallSubXactCallback(), we must find the
right place and handle it carefully.

Luckily, we just need a few lines of code to support this feature,
by saving the next pointer before calling the callback.

The usage looks like:
```
static void
AtEOXact_cleanup_state(SubXactEvent event, SubTransactionId mySubid,
SubTransactionId parentSubid, void *arg)
{
SubXactCleanupItem *item = (SubXactCleanupItem *)arg;
Assert(FullTransactionIdEquals(item->fullXid,
GetTopFullTransactionIdIfAny()));
if (item->mySubid == mySubid &&
(event == SUBXACT_EVENT_COMMIT_SUB || event ==
SUBXACT_EVENT_ABORT_SUB))
{
/* to do some cleanup for subtransaction */
...
UnregisterSubXactCallback(AtEOXact_cleanup_state, arg);
}
}
```

Regards,
Hao Wu

Attachments:

safely-delete-callback.diffapplication/octet-stream; name=safely-delete-callback.diffDownload
commit c617789af9006a1e4716d039b3f1f7fb45ae5227
Author: Hao Wu <gfphoenix78@gmail.com>
Date:   Mon Mar 28 23:06:20 2022 -0700

    Enables to call Unregister*XactCallback() in Call*XactCallback()
    
    It's a natural requirement to unregister the callback for transaction or
    subtransaction when the callback is invoked, so we don't have to
    unregister the callback somewhere. If it's not allowed to do it
    in CallXactCallback() or CallSubXactCallback(), we must handle it
    carefully.
    Luckily, we just need a few lines of code to support this feature,
    by saving the next pointer before calling the callback.

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 3596a7d734..3eda32d3d3 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -3630,9 +3630,13 @@ static void
 CallXactCallbacks(XactEvent event)
 {
 	XactCallbackItem *item;
+	XactCallbackItem *next;
 
-	for (item = Xact_callbacks; item; item = item->next)
+	for (item = Xact_callbacks; item; item = next)
+	{
+		next = item->next;
 		item->callback(event, item->arg);
+	}
 }
 
 
@@ -3687,9 +3691,13 @@ CallSubXactCallbacks(SubXactEvent event,
 					 SubTransactionId parentSubid)
 {
 	SubXactCallbackItem *item;
+	SubXactCallbackItem *next;
 
-	for (item = SubXact_callbacks; item; item = item->next)
+	for (item = SubXact_callbacks; item; item = next)
+	{
+		next = item->next;
 		item->callback(event, mySubid, parentSubid, item->arg);
+	}
 }
 
 
#2Andres Freund
andres@anarazel.de
In reply to: Hao Wu (#1)
Re: Enables to call Unregister*XactCallback() in Call*XactCallback()

Hi,

On 2022-03-29 14:48:54 +0800, Hao Wu wrote:

It's a natural requirement to unregister the callback for transaction or
subtransaction when the callback is invoked, so we don't have to
unregister the callback somewhere.

You normally shouldn'd need to do this frequently - what's your use case?
UnregisterXactCallback() is O(N), so workloads registering / unregistering a
lot of callbacks would be problematic.

Luckily, we just need a few lines of code to support this feature,
by saving the next pointer before calling the callback.

That seems reasonable...

Greetings,

Andres Freund

#3Hao Wu
gfphoenix78@gmail.com
In reply to: Andres Freund (#2)
Re: Enables to call Unregister*XactCallback() in Call*XactCallback()

You normally shouldn'd need to do this frequently - what's your use case?
UnregisterXactCallback() is O(N), so workloads registering / unregistering
a
lot of callbacks would be problematic.

It's not about workloads or efficiency. Here is the use case:

I want to register a callback for some subtransaction, and only run this
callback once
when the subtransaction ends, no matter if it was committed or cancelled.

It's reasonable to unregister the callback at the end of the callback, or I
have to
call UnregisterSubXactCallback() somewhere. Because
SubXact_callbacks/Xact_callbacks
is only set by Unregister*XactCallback(). The question now becomes
1. where to call UnregisterSubXactCallback()
2. ensure that calling UnregisterSubXactCallback() is not triggered in the
current callback

This patch enables us to safely delete the current callback when the
callback finishes to
implement run callback once and unregister in one place.

Regards,
Hao Wu

#4Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#2)
1 attachment(s)
Re: Enables to call Unregister*XactCallback() in Call*XactCallback()

Andres Freund <andres@anarazel.de> writes:

On 2022-03-29 14:48:54 +0800, Hao Wu wrote:

It's a natural requirement to unregister the callback for transaction or
subtransaction when the callback is invoked, so we don't have to
unregister the callback somewhere.

You normally shouldn'd need to do this frequently - what's your use case?
UnregisterXactCallback() is O(N), so workloads registering / unregistering a
lot of callbacks would be problematic.

It'd only be slow if you had a lot of distinct callbacks registered
at the same time, which doesn't sound like a common situation.

Luckily, we just need a few lines of code to support this feature,
by saving the next pointer before calling the callback.

That seems reasonable...

Yeah. Whether it's efficient or not, seems like it should *work*.
I'm a bit inclined to call this a bug-fix and backpatch it.

I went looking for other occurrences of this code in places that have
an unregister function, and found one in ResourceOwnerReleaseInternal,
so I think we should fix that too. Also, a comment seems advisable;
that leads me to the attached v2.

regards, tom lane

Attachments:

safely-delete-callback-2.patchtext/x-diff; charset=us-ascii; name=safely-delete-callback-2.patchDownload
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 2bb975943c..c1ffbd89b8 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -3656,9 +3656,14 @@ static void
 CallXactCallbacks(XactEvent event)
 {
 	XactCallbackItem *item;
+	XactCallbackItem *next;
 
-	for (item = Xact_callbacks; item; item = item->next)
+	for (item = Xact_callbacks; item; item = next)
+	{
+		/* allow callbacks to unregister themselves when called */
+		next = item->next;
 		item->callback(event, item->arg);
+	}
 }
 
 
@@ -3713,9 +3718,14 @@ CallSubXactCallbacks(SubXactEvent event,
 					 SubTransactionId parentSubid)
 {
 	SubXactCallbackItem *item;
+	SubXactCallbackItem *next;
 
-	for (item = SubXact_callbacks; item; item = item->next)
+	for (item = SubXact_callbacks; item; item = next)
+	{
+		/* allow callbacks to unregister themselves when called */
+		next = item->next;
 		item->callback(event, mySubid, parentSubid, item->arg);
+	}
 }
 
 
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index ece5d98261..37b43ee1f8 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -501,6 +501,7 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 	ResourceOwner child;
 	ResourceOwner save;
 	ResourceReleaseCallbackItem *item;
+	ResourceReleaseCallbackItem *next;
 	Datum		foundres;
 
 	/* Recurse to handle descendants */
@@ -701,8 +702,12 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 	}
 
 	/* Let add-on modules get a chance too */
-	for (item = ResourceRelease_callbacks; item; item = item->next)
+	for (item = ResourceRelease_callbacks; item; item = next)
+	{
+		/* allow callbacks to unregister themselves when called */
+		next = item->next;
 		item->callback(phase, isCommit, isTopLevel, item->arg);
+	}
 
 	CurrentResourceOwner = save;
 }
#5Nathan Bossart
nathandbossart@gmail.com
In reply to: Tom Lane (#4)
Re: Enables to call Unregister*XactCallback() in Call*XactCallback()

On Mon, Sep 26, 2022 at 06:05:34PM -0400, Tom Lane wrote:

Yeah. Whether it's efficient or not, seems like it should *work*.
I'm a bit inclined to call this a bug-fix and backpatch it.

I went looking for other occurrences of this code in places that have
an unregister function, and found one in ResourceOwnerReleaseInternal,
so I think we should fix that too. Also, a comment seems advisable;
that leads me to the attached v2.

LGTM. I have no opinion on back-patching.

--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com

#6Tom Lane
tgl@sss.pgh.pa.us
In reply to: Nathan Bossart (#5)
Re: Enables to call Unregister*XactCallback() in Call*XactCallback()

Nathan Bossart <nathandbossart@gmail.com> writes:

On Mon, Sep 26, 2022 at 06:05:34PM -0400, Tom Lane wrote:

Yeah. Whether it's efficient or not, seems like it should *work*.
I'm a bit inclined to call this a bug-fix and backpatch it.

LGTM. I have no opinion on back-patching.

I had second thoughts about back-patching: doing so would encourage
extensions to rely on this working in pre-v16 branches, which they'd
better not since they might be in a not-up-to-date installation.

We could still squeeze this into v15 without creating such a hazard,
but post-rc1 doesn't seem like a good time for inessential tweaks.

Hence, pushed to HEAD only.

regards, tom lane