On Wed, 5 Apr 2023 at 01:55, Benjamin Berg benjamin@sipsolutions.net wrote:
Hi,
On Tue, 2023-04-04 at 15:32 +0200, Maxime Ripard wrote:
[SNIP]
+/**
- kunit_add_action() - Defer an 'action' (function call) until the test ends.
- @test: Test case to associate the action 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_add_action() 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_remove_action_token() in order to cancel the deferred execution of
- func().
- */
+struct kunit_action_ctx *kunit_add_action(struct kunit *test, kunit_defer_function_t func,
void *ctx, gfp_t internal_gfp);
Do we expect any other context than GFP_KERNEL?
If so, then maybe we can have kunit_add_action() assume GFP_KERNEL and add a variant for the odd case where we would actually need a different GFP flag.
Does anything other than GFP_KERNEL make sense? I would assume these functions should only ever be called from a kunit context, i.e. the passed test is guaranteed to be identical to the value returned by kunit_get_current_test().
That's not strictly-speaking guaranteed. (Indeed, we have some, albeit contrived, counterexamples in the test.)
The theoretical use-case here is if the kunit context pointer is passed to another thread or workqueue or something.
There aren't any such users, yet (apart from, possibly, kunit_kmalloc_array()), though. So we could use GFP_KERNEL by default for now, and add a variant if such a use-case appears.
That said, I am happy with merging this in this form. I feel the right thing here is a patch (with corresponding spatch) that changes all of the related APIs to remove the gfp argument.
+/**
- kunit_remove_action_token() - Cancel a deferred action.
- @test: Test case the action is associated with.
- @cancel_token: The cancellation token returned by kunit_add_action()
- Prevent an action deferred using kunit_add_action() from executing when the
- test ends.
- You can also use the (test, function, context) triplet to remove an action
- with kunit_remove_action().
- */
+void kunit_remove_action_token(struct kunit *test, struct kunit_action_ctx *cancel_token);
It's not clear to me why we still need the token. If kunit_remove_action() works fine, why would we need to store the token?
Can't we just make kunit_add_action() return an int to indicate whether it failed or not, and that's it?
[SNIP]
One thing worth pointing is that if kunit_add_action() fails, the cleanup function passed as an argument won't run.
So, if the kzalloc call ever fails, patch 2 will leak its res->data() resource for example.
devm (and drmm) handles this using a variant called devm_add_action_or_reset, we should either provide the same variant or just go for that behavior by default.
Both version of the function would need a return value. An alternative might be to make assertions part of the API. But as with dropping the gfp argument, that seems like a more intrusive change that needs to happen independently.
Anyway, I am fine with action_or_reset as the default and possibly the only behaviour. I expect that every API user will want an assertion that checks for failure here anyway.
I'm tempted to just have both kunit_add_action() and kunit_add_action_or_reset(), just to keep things matching the devm_ API to minimise any confusion.
And if we're not too worried about proliferating variants of these (and, personally, I quite like them), we could have a kunit_add_action_or_asserrt() version as well.
Benjamin
If kunit_* functions can assert in error conditions, then the example
void test_func(struct kunit *test) { char u8 *buf = kunit_kzalloc(test, 1024, GFP_KERNEL); struct sk_buff *skb_a; struct sk_buff *skb_b; /* Further variables */
KUNIT_ASSERT_NOT_NULL(test, buf);
skb_a = skb_alloc(1024, GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, skb_a); if (kunit_add_cleanup(test, (kunit_defer_function_t) kfree_skb, skb_a)) KUNIT_ASSERT_FAILURE("Failed to add cleanup");
/* Or, maybe: */ skb_b = skb_alloc(1024, GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, skb_b); KUNIT_ASSERT_EQ(test, 0, kunit_add_cleanup(test, (kunit_defer_function_t) kfree_skb, skb_b));
/* run code that may assert */ }
could be shortened to (with a trivial kunit_skb_alloc helper)
void test_func(struct kunit *test) { char u8 *buf = kunit_kzalloc(test, 1024, GFP_KERNEL); struct sk_buff *skb_a = kunit_skb_alloc(1024, GFP_KERNEL); struct sk_buff *skb_b = kunit_skb_alloc(1024, GFP_KERNEL); /* Further variables */
/* run code that may assert */ }
I should just post a patch for the existing API and see what people say then ...
We definitely already have some functions (e.g. __kunit_activate_static_stub()) which just assert on failure. In general, we've avoided doing so where we think there might be a good reason to handle failures separately (or it makes the API diverge a lot from a function we're imitating), but I'm open to using them more. Specialised handling of allocation failures in a test is likely to be rare enough that making those who need it write their own helpers wouldn't be a disaster. Or we could have an _or_assert() variant.
In any case, I think your example pretty comfortably speaks for itself.
Cheers, -- David