Previously the .free function of a callback was not called if the
.callback field was NULL, because the callback as a whole would be
considered to be "null".

This change allows you to register callbacks where the .callback field
is NULL, but the .free field is != NULL, meaning that the callback is
freed after the last time it would have been used.

This is mainly convenient for language bindings where we sometimes
want to register a free function to clean up a persistent buffer, but
we don't need the associated completion callback to be actually
called.
---
 docs/libnbd.pod | 15 +++++++++++++++
 lib/internal.h  | 18 +++++++++---------
 2 files changed, 24 insertions(+), 9 deletions(-)

diff --git a/docs/libnbd.pod b/docs/libnbd.pod
index f6fd4cd..d230cb4 100644
--- a/docs/libnbd.pod
+++ b/docs/libnbd.pod
@@ -643,6 +643,21 @@ S<C<chunk.callback = my_fn>> function is called.
 The free function is only accessible in the C API as it is not needed
 in garbage collected programming languages.
 
+=head2 Callbacks with C<.callback=NULL> and C<.free!=NULL>
+
+It is possible to register a callback like this:
+
+  ...
+    (nbd_completion_callback) { .callback = NULL,
+                                .user_data = my_data,
+                                .free = free },
+  ...
+
+The meaning of this is that the callback is never called, but the free
+function is still called after the last time the callback would have
+been called.  This is useful for applying generic freeing actions when
+asynchronous commands are retired.
+
 =head2 Callbacks and locking
 
 The callbacks are invoked at a point where the libnbd lock is held; as
diff --git a/lib/internal.h b/lib/internal.h
index 1344d98..ee59582 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -274,20 +274,20 @@ struct command {
 };
 
 /* Test if a callback is "null" or not, and set it to null. */
-#define CALLBACK_IS_NULL(cb)     ((cb).callback == NULL)
+#define CALLBACK_IS_NULL(cb)     ((cb).callback == NULL && (cb).free == NULL)
 #define CALLBACK_IS_NOT_NULL(cb) (! CALLBACK_IS_NULL ((cb)))
-#define SET_CALLBACK_TO_NULL(cb) ((cb).callback = NULL)
+#define SET_CALLBACK_TO_NULL(cb) ((cb).callback = NULL, (cb).free = NULL)
 
 /* Call a callback. */
-#define CALL_CALLBACK(cb, ...) \
-  (cb).callback ((cb).user_data, ##__VA_ARGS__)
+#define CALL_CALLBACK(cb, ...)                                          \
+  ((cb).callback != NULL ? (cb).callback ((cb).user_data, ##__VA_ARGS__) : 0)
 
 /* Free a callback. */
-#define FREE_CALLBACK(cb)                                               \
-  do {                                                                  \
-    if (CALLBACK_IS_NOT_NULL (cb) && (cb).free != NULL)                 \
-      (cb).free ((cb).user_data);                                       \
-    SET_CALLBACK_TO_NULL (cb);                                          \
+#define FREE_CALLBACK(cb)                               \
+  do {                                                  \
+    if ((cb).free != NULL)                              \
+      (cb).free ((cb).user_data);                       \
+    SET_CALLBACK_TO_NULL (cb);                          \
   } while (0)
 
 /* aio.c */
-- 
2.22.0

_______________________________________________
Libguestfs mailing list
Libguestfs@redhat.com
https://www.redhat.com/mailman/listinfo/libguestfs

Reply via email to