From: David Gow <davidgow@google.com>
To: Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
Matti Vaittinen <mazziesaccount@gmail.com>,
Maxime Ripard <maxime@cerno.tech>,
Brendan Higgins <brendan.higgins@linux.dev>,
Stephen Boyd <sboyd@kernel.org>,
Shuah Khan <skhan@linuxfoundation.org>
Cc: David Gow <davidgow@google.com>,
"Rafael J . Wysocki" <rafael@kernel.org>,
Andy Shevchenko <andriy.shevchenko@linux.intel.com>,
Heikki Krogerus <heikki.krogerus@linux.intel.com>,
Jonathan Cameron <jic23@kernel.org>,
linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org,
kunit-dev@googlegroups.com
Subject: [RFC PATCH 1/2] kunit: resource: Add kunit_defer() functionality
Date: Sat, 25 Mar 2023 12:31:03 +0800 [thread overview]
Message-ID: <20230325043104.3761770-2-davidgow@google.com> (raw)
In-Reply-To: <20230325043104.3761770-1-davidgow@google.com>
Many uses of the KUnit resource system are intended to simply defer
calling a function until the test exits (be it due to success or
failure). The existing kunit_alloc_resource() function is often used for
this, but was awkward to use (requiring passing NULL init functions, etc),
and returned a resource without incrementing its reference count, which
-- while okay for this use-case -- could cause problems in others.
Instead, introduce a simple kunit_defer() API: a simple function
(returning nothing, accepting a single void* argument) can be scheduled
to be called when the test exits. Deferred functions are called in the
opposite order to that which they were registered.
This is implemented as a resource under the hood, so the ordering
between resource cleanup and deferred functions is maintained.
Signed-off-by: David Gow <davidgow@google.com>
---
include/kunit/resource.h | 87 +++++++++++++++++++++++++++++++
lib/kunit/resource.c | 110 +++++++++++++++++++++++++++++++++++++++
2 files changed, 197 insertions(+)
diff --git a/include/kunit/resource.h b/include/kunit/resource.h
index cf6fb8f2ac1b..6c4728ca9237 100644
--- a/include/kunit/resource.h
+++ b/include/kunit/resource.h
@@ -387,4 +387,91 @@ static inline int kunit_destroy_named_resource(struct kunit *test,
*/
void kunit_remove_resource(struct kunit *test, struct kunit_resource *res);
+typedef void (*kunit_defer_function_t)(void *ctx);
+
+/**
+ * kunit_defer() - Defer a function call until the test ends.
+ * @test: Test case to associate the deferred function with.
+ * @func: The function to run on test exit
+ * @ctx: Data passed into @func
+ * @internal_gfp: gfp to use for internal allocations, if unsure, use GFP_KERNEL
+ *
+ * Defer the execution of a function until the test exits, either normally or
+ * due to a failure. @ctx is passed as additional context. All functions
+ * registered with kunit_defer() will execute in the opposite order to that
+ * they were registered in.
+ *
+ * This is useful for cleaning up allocated memory and resources.
+ *
+ * RETURNS:
+ * An opaque "cancellation token", or NULL on error. Pass this token to
+ * kunit_defer_cancel() in order to cancel the deferred execution of func().
+ */
+void *kunit_defer(struct kunit *test, kunit_defer_function_t func,
+ void *ctx, gfp_t internal_gfp);
+
+/**
+ * kunit_defer_cancel_token() - Cancel a deferred function call.
+ * @test: Test case the deferred function is associated with.
+ * @cancel_token: The cancellation token returned by kunit_defer()
+ *
+ * Prevent a function deferred using kunit_defer() from executing when the
+ * test ends.
+ *
+ * Prefer using this to kunit_defer_cancel() where possible.
+ */
+void kunit_defer_cancel_token(struct kunit *test, void *cancel_token);
+
+/**
+ * kunit_defer_trigger_token() - Run a deferred function call immediately.
+ * @test: Test case the deferred function is associated with.
+ * @cancel_token: The cancellation token returned by kunit_defer()
+ *
+ * Execute a deferred function call immediately, instead of waiting for the
+ * test to end.
+ *
+ * Prefer using this to kunit_defer_trigger() where possible.
+ */
+void kunit_defer_trigger_token(struct kunit *test, void *cancel_token);
+
+/**
+ * kunit_defer_cancel() - Cancel a matching deferred function call.
+ * @test: Test case the deferred function is associated with.
+ * @func: The deferred function to cancel.
+ * @ctx: The context passed to the deferred function to trigger.
+ *
+ * Prevent a function deferred via kunit_defer() from executing at shutdown.
+ * Unlike kunit_defer_cancel_token(), this takes the (func, ctx) pair instead of
+ * the cancellation token. If that function/context pair was deferred multiple
+ * times, only the most recent one will be cancelled.
+ *
+ * Prefer using kunit_defer_cancel_token() to this where possible.
+ */
+void kunit_defer_cancel(struct kunit *test,
+ kunit_defer_function_t func,
+ void *ctx);
+
+/**
+ * kunit_defer_trigger() - Run a matching deferred function call immediately.
+ * @test: Test case the deferred function is associated with.
+ * @func: The deferred function to trigger.
+ * @ctx: The context passed to the deferred function to trigger.
+ *
+ * Execute a function deferred via kunit_defer() immediately, rather than when
+ * the test ends.
+ * Unlike kunit_defer_trigger_token(), this takes the (func, ctx) pair instead of
+ * the cancellation token. If that function/context pair was deferred multiple
+ * times, it will only be executed once here. The most recent deferral will
+ * no longer execute when the test ends.
+ *
+ * kunit_defer_trigger(test, func, ctx);
+ * is equivalent to
+ * func(ctx);
+ * kunit_defer_cancel(test, func, ctx);
+ *
+ * Prefer using kunit_defer_trigger_token() to this where possible.
+ */
+void kunit_defer_trigger(struct kunit *test,
+ kunit_defer_function_t func,
+ void *ctx);
#endif /* _KUNIT_RESOURCE_H */
diff --git a/lib/kunit/resource.c b/lib/kunit/resource.c
index c414df922f34..0d0c48054d45 100644
--- a/lib/kunit/resource.c
+++ b/lib/kunit/resource.c
@@ -77,3 +77,113 @@ int kunit_destroy_resource(struct kunit *test, kunit_resource_match_t match,
return 0;
}
EXPORT_SYMBOL_GPL(kunit_destroy_resource);
+
+struct kunit_defer_ctx {
+ kunit_defer_function_t func;
+ void *ctx;
+};
+
+static void __kunit_defer_free(struct kunit_resource *res)
+{
+ struct kunit_defer_ctx *defer_ctx = (struct kunit_defer_ctx *)res->data;
+
+ defer_ctx->func(defer_ctx->ctx);
+
+ kfree(res->data);
+}
+
+void *kunit_defer(struct kunit *test, kunit_defer_function_t func,
+ void *ctx, gfp_t internal_gfp)
+{
+ struct kunit_resource *res;
+ struct kunit_defer_ctx *defer_ctx;
+
+ KUNIT_ASSERT_NOT_NULL_MSG(test, func, "Tried to defer a NULL function!");
+
+ res = kzalloc(sizeof(*res), internal_gfp);
+ if (!res)
+ return NULL;
+
+ defer_ctx = kzalloc(sizeof(*defer_ctx), internal_gfp);
+ if (!defer_ctx)
+ goto free_res;
+
+ defer_ctx->func = func;
+ defer_ctx->ctx = ctx;
+
+ res->should_kfree = true;
+ __kunit_add_resource(test, NULL, __kunit_defer_free, res, defer_ctx);
+
+ return (void *)res;
+
+free_res:
+ kfree(res);
+ return NULL;
+}
+
+void kunit_defer_cancel_token(struct kunit *test, void *cancel_token)
+{
+ struct kunit_resource *res = (struct kunit_resource *)cancel_token;
+
+ /* Remove the free function so we don't run the deferred function. */
+ res->free = NULL;
+
+ kunit_remove_resource(test, res);
+}
+
+void kunit_defer_trigger_token(struct kunit *test, void *cancel_token)
+{
+ struct kunit_resource *res = (struct kunit_resource *)cancel_token;
+
+ /* Removing the resource should trigger the res->free function. */
+ kunit_remove_resource(test, res);
+}
+
+static bool __kunit_defer_match(struct kunit *test,
+ struct kunit_resource *res, void *match_data)
+{
+ struct kunit_defer_ctx *match_ctx = (struct kunit_defer_ctx *)match_data;
+ struct kunit_defer_ctx *res_ctx = (struct kunit_defer_ctx *)res->data;
+
+ /* Make sure this is a free function. */
+ if (res->free != __kunit_defer_free)
+ return false;
+
+ /* Both the function and context data should match. */
+ return (match_ctx->func == res_ctx->func) && (match_ctx->ctx == res_ctx->ctx);
+}
+
+void kunit_defer_cancel(struct kunit *test,
+ kunit_defer_function_t func,
+ void *ctx)
+{
+ struct kunit_defer_ctx defer_ctx;
+ struct kunit_resource *res;
+
+ defer_ctx.func = func;
+ defer_ctx.ctx = ctx;
+
+ res = kunit_find_resource(test, __kunit_defer_match, &defer_ctx);
+ if (res) {
+ kunit_defer_cancel_token(test, res);
+ kunit_put_resource(res);
+ }
+}
+
+void kunit_defer_trigger(struct kunit *test,
+ kunit_defer_function_t func,
+ void *ctx)
+{
+ struct kunit_defer_ctx defer_ctx;
+ struct kunit_resource *res;
+
+ defer_ctx.func = func;
+ defer_ctx.ctx = ctx;
+
+ res = kunit_find_resource(test, __kunit_defer_match, &defer_ctx);
+ if (res) {
+ kunit_defer_trigger_token(test, res);
+ /* We have to put() this here, else free won't be called. */
+ kunit_put_resource(res);
+ }
+}
--
2.40.0.348.gf938b09366-goog
next prev parent reply other threads:[~2023-03-25 4:31 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-03-25 4:31 [RFC PATCH 0/2] KUnit device API proposal David Gow
2023-03-25 4:31 ` David Gow [this message]
2023-03-26 6:33 ` [RFC PATCH 1/2] kunit: resource: Add kunit_defer() functionality Matti Vaittinen
2023-03-27 13:56 ` Maxime Ripard
2023-03-30 7:38 ` Benjamin Berg
2023-03-25 4:31 ` [RFC PATCH 2/2] kunit: Add APIs for managing devices David Gow
2023-03-25 6:04 ` kernel test robot
2023-03-25 6:28 ` Greg Kroah-Hartman
2023-03-25 15:14 ` kernel test robot
2023-03-27 13:57 ` Maxime Ripard
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20230325043104.3761770-2-davidgow@google.com \
--to=davidgow@google.com \
--cc=andriy.shevchenko@linux.intel.com \
--cc=brendan.higgins@linux.dev \
--cc=gregkh@linuxfoundation.org \
--cc=heikki.krogerus@linux.intel.com \
--cc=jic23@kernel.org \
--cc=kunit-dev@googlegroups.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=maxime@cerno.tech \
--cc=mazziesaccount@gmail.com \
--cc=rafael@kernel.org \
--cc=sboyd@kernel.org \
--cc=skhan@linuxfoundation.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.