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