[PATCH v5 0/21] ratelimit: Reduce ratelimit's false-positive misses

Paul E. McKenney posted 21 patches 9 months ago
b/drivers/char/random.c              |    9 +
b/drivers/gpu/drm/amd/pm/amdgpu_pm.c |   11 --
b/drivers/gpu/drm/i915/i915_perf.c   |    8 -
b/include/linux/ratelimit.h          |   40 +++++++-
b/include/linux/ratelimit_types.h    |    2
b/lib/Kconfig.debug                  |   11 ++
b/lib/ratelimit.c                    |    8 -
b/lib/tests/Makefile                 |    1
b/lib/tests/test_ratelimit.c         |   79 ++++++++++++++++
include/linux/ratelimit.h            |   13 +-
include/linux/ratelimit_types.h      |    3
lib/ratelimit.c                      |  165 ++++++++++++++++++++---------------
lib/tests/test_ratelimit.c           |   69 ++++++++++++++
13 files changed, 313 insertions(+), 106 deletions(-)
[PATCH v5 0/21] ratelimit: Reduce ratelimit's false-positive misses
Posted by Paul E. McKenney 9 months ago
Hello!

This v5 series replaces open-coded uses of the ratelimit_state structure
with formal APIs, counts all rate-limit misses, replaces jiffies=0 special
case with a flag, provides a ___ratelimit() trylock-failure fastpath to
(almost) eliminate false-positive misses, simplifies the code, and adds
a simple "smoke" test along with a simple stress test.

The key point of this series is the reduction of false-positive misses.
More could be done to avoid open-coded access to the ->interval and
->burst fields, and to tighten up checking of user input for these fields,
but those are jobs for later patches.

The individual patches are as follows:

1.	Create functions to handle ratelimit_state internals.

2.	Avoid open-coded use of ratelimit_state structure's ->missed
	field.

3.	Avoid open-coded use of ratelimit_state structure's ->missed
	field.

4.	Avoid open-coded use of ratelimit_state structure's internals.

5.	Convert the ->missed field to atomic_t.

6.	Count misses due to lock contention.

7.	Avoid jiffies=0 special case.

8.	Reduce ___ratelimit() false-positive rate limiting, courtesy of
	Petr Mladek.

9.	Allow zero ->burst to disable ratelimiting.

10.	Force re-initialization when rate-limiting re-enabled.

11.	Don't flush misses counter if RATELIMIT_MSG_ON_RELEASE.

12.	Avoid atomic decrement if already rate-limited.

13.	Avoid atomic decrement under lock if already rate-limited.

14.	Warn if ->interval or ->burst are negative, courtesy of Petr
	Mladek.

15.	Simplify common-case exit path.

16.	Use nolock_ret label to save a couple of lines of code.

17.	Use nolock_ret label to collapse lock-failure code.

18.	Use nolock_ret restructuring to collapse common case code.

19.	Drop redundant accesses to burst.

20.	Add trivial kunit test for ratelimit.

21.	Add stress test for ratelimit.

Changes since v4:

o	Add a simple stress test.

o	Move the tests to the end of the series for bisectability.

o	Add Reviewed-by tags.

Changes since v3:

o	Correctly handle zero-initialized ratelimit_state structures,
	being careful to avoid acquiring the uninitialized ->lock.

o	Remove redundant checks of the "burst" local variable.

o	Add Reviewed-by tags.

Changes since v2:

o	Apply feedback from Bert Karwatzki, Srikanth Aithal, and Mark
	Brown, fixing a hang that happened on some systems.

o	Applied Reviewed-by tags and added links.

o	Added a prototype patch from Petr Mladek that splats if either
	interval or burst are negative.

o	Added several commits that simplify the code.

Changes since v1 (RFC):

o	Switch from lockless fastpath to carrying out needed updates
	upon trylock failure, per Petr Mladek feedback.  This greatly
	simplifies the code and is a much smaller change from the
	current code.  There is a small performance penalty compared to
	the lockless fastpath, but not enough to matter.

o	Never unconditionally acquire the lock, again per Petr Mladek
	feedback.

o	Better define effects of non-positive burst values (always
	ratelimit) and non-positive interval values (never ratelimit
	when the burst value is positive).

o	The changes from Petr's original are supplied as five incremental
	patches, but could easily be folded into Petr's original if
	desired.  (Left to my lazy self, they stay as-is.)

						Thanx, Paul

------------------------------------------------------------------------

 b/drivers/char/random.c              |    9 +
 b/drivers/gpu/drm/amd/pm/amdgpu_pm.c |   11 --
 b/drivers/gpu/drm/i915/i915_perf.c   |    8 -
 b/include/linux/ratelimit.h          |   40 +++++++-
 b/include/linux/ratelimit_types.h    |    2 
 b/lib/Kconfig.debug                  |   11 ++
 b/lib/ratelimit.c                    |    8 -
 b/lib/tests/Makefile                 |    1 
 b/lib/tests/test_ratelimit.c         |   79 ++++++++++++++++
 include/linux/ratelimit.h            |   13 +-
 include/linux/ratelimit_types.h      |    3 
 lib/ratelimit.c                      |  165 ++++++++++++++++++++---------------
 lib/tests/test_ratelimit.c           |   69 ++++++++++++++
 13 files changed, 313 insertions(+), 106 deletions(-)
[PATCH v6 0/3] ratelimit: Add tests for lib/ratelimit.c
Posted by Paul E. McKenney 7 months ago
Hello!

This v6 series adds a simple "smoke" test along with a simple stress test:

1.	Add trivial kunit test for ratelimit.

2.	Make the ratelimit test more reliable, courtesy of Petr Mladek.

3.	Add stress test for ratelimit.

							Thanx, Paul

Changes since v5:

o	Drop patches that have since been accepted into mainline.

o	Add Petr Mladek's patch improving the reliability of the simple
	test.

Changes since v4:

o	Add a simple stress test.

o	Move the tests to the end of the series for bisectability.

o	Add Reviewed-by tags.

Changes since v3:

o	Correctly handle zero-initialized ratelimit_state structures,
	being careful to avoid acquiring the uninitialized ->lock.

o	Remove redundant checks of the "burst" local variable.

o	Add Reviewed-by tags.

Changes since v2:

o	Apply feedback from Bert Karwatzki, Srikanth Aithal, and Mark
	Brown, fixing a hang that happened on some systems.

o	Applied Reviewed-by tags and added links.

o	Added a prototype patch from Petr Mladek that splats if either
	interval or burst are negative.

o	Added several commits that simplify the code.

Changes since v1 (RFC):

o	Switch from lockless fastpath to carrying out needed updates
	upon trylock failure, per Petr Mladek feedback.  This greatly
	simplifies the code and is a much smaller change from the
	current code.  There is a small performance penalty compared to
	the lockless fastpath, but not enough to matter.

o	Never unconditionally acquire the lock, again per Petr Mladek
	feedback.

o	Better define effects of non-positive burst values (always
	ratelimit) and non-positive interval values (never ratelimit
	when the burst value is positive).

o	The changes from Petr's original are supplied as five incremental
	patches, but could easily be folded into Petr's original if
	desired.  (Left to my lazy self, they stay as-is.)

------------------------------------------------------------------------

 b/lib/Kconfig.debug          |   11 +++++
 b/lib/tests/Makefile         |    1 
 b/lib/tests/test_ratelimit.c |   79 +++++++++++++++++++++++++++++++++++++++++++
 lib/tests/test_ratelimit.c   |   77 ++++++++++++++++++++++++++++++++++++++---
 4 files changed, 162 insertions(+), 6 deletions(-)
[PATCH v6 1/3] lib: Add trivial kunit test for ratelimit
Posted by Paul E. McKenney 7 months ago
Add a simple single-threaded smoke test for lib/ratelimit.c

To run on x86:

	make ARCH=x86_64 mrproper
	./tools/testing/kunit/kunit.py run --arch x86_64 --kconfig_add CONFIG_RATELIMIT_KUNIT_TEST=y --kconfig_add CONFIG_SMP=y lib_ratelimit

This will fail on old ___ratelimit(), and subsequent patches provide
the fixes that are required.

[ paulmck:  Apply timeout and kunit feedback from Petr Mladek. ]

Link: https://lore.kernel.org/all/fbe93a52-365e-47fe-93a4-44a44547d601@paulmck-laptop/
Link: https://lore.kernel.org/all/20250423115409.3425-1-spasswolf@web.de/
Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
Reviewed-by: Petr Mladek <pmladek@suse.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Kuniyuki Iwashima <kuniyu@amazon.com>
Cc: Mateusz Guzik <mjguzik@gmail.com>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: John Ogness <john.ogness@linutronix.de>
Cc: Sergey Senozhatsky <senozhatsky@chromium.org>
Cc: Jon Pan-Doh <pandoh@google.com>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Karolina Stolarek <karolina.stolarek@oracle.com>
---
 lib/Kconfig.debug          | 11 ++++++
 lib/tests/Makefile         |  1 +
 lib/tests/test_ratelimit.c | 79 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 91 insertions(+)
 create mode 100644 lib/tests/test_ratelimit.c

diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index ebe33181b6e6e..d69d27f808340 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -3225,6 +3225,17 @@ config TEST_OBJPOOL
 
 	  If unsure, say N.
 
+config RATELIMIT_KUNIT_TEST
+	tristate "KUnit Test for correctness and stress of ratelimit" if !KUNIT_ALL_TESTS
+	depends on KUNIT
+	default KUNIT_ALL_TESTS
+	help
+	  This builds the "test_ratelimit" module that should be used
+	  for correctness verification and concurrent testings of rate
+	  limiting.
+
+	  If unsure, say N.
+
 config INT_POW_KUNIT_TEST
 	tristate "Integer exponentiation (int_pow) test" if !KUNIT_ALL_TESTS
 	depends on KUNIT
diff --git a/lib/tests/Makefile b/lib/tests/Makefile
index 56d6450144828..3edc30a515840 100644
--- a/lib/tests/Makefile
+++ b/lib/tests/Makefile
@@ -46,5 +46,6 @@ obj-$(CONFIG_STRING_KUNIT_TEST) += string_kunit.o
 obj-$(CONFIG_STRING_HELPERS_KUNIT_TEST) += string_helpers_kunit.o
 obj-$(CONFIG_USERCOPY_KUNIT_TEST) += usercopy_kunit.o
 obj-$(CONFIG_UTIL_MACROS_KUNIT) += util_macros_kunit.o
+obj-$(CONFIG_RATELIMIT_KUNIT_TEST) += test_ratelimit.o
 
 obj-$(CONFIG_TEST_RUNTIME_MODULE)		+= module/
diff --git a/lib/tests/test_ratelimit.c b/lib/tests/test_ratelimit.c
new file mode 100644
index 0000000000000..0374107f5ea89
--- /dev/null
+++ b/lib/tests/test_ratelimit.c
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <kunit/test.h>
+
+#include <linux/ratelimit.h>
+#include <linux/module.h>
+
+/* a simple boot-time regression test */
+
+#define TESTRL_INTERVAL (5 * HZ)
+static DEFINE_RATELIMIT_STATE(testrl, TESTRL_INTERVAL, 3);
+
+#define test_ratelimited(test, expected) \
+	KUNIT_ASSERT_EQ(test, ___ratelimit(&testrl, "test_ratelimit_smoke"), (expected))
+
+static void test_ratelimit_smoke(struct kunit *test)
+{
+	// Check settings.
+	KUNIT_ASSERT_GE(test, TESTRL_INTERVAL, 100);
+
+	// Test normal operation.
+	test_ratelimited(test, true);
+	test_ratelimited(test, true);
+	test_ratelimited(test, true);
+	test_ratelimited(test, false);
+
+	schedule_timeout_idle(TESTRL_INTERVAL - 40);
+	test_ratelimited(test, false);
+
+	schedule_timeout_idle(50);
+	test_ratelimited(test, true);
+
+	schedule_timeout_idle(2 * TESTRL_INTERVAL);
+	test_ratelimited(test, true);
+	test_ratelimited(test, true);
+
+	schedule_timeout_idle(TESTRL_INTERVAL - 40);
+	test_ratelimited(test, true);
+	schedule_timeout_idle(50);
+	test_ratelimited(test, true);
+	test_ratelimited(test, true);
+	test_ratelimited(test, true);
+	test_ratelimited(test, false);
+
+	// Test disabling.
+	testrl.burst = 0;
+	test_ratelimited(test, false);
+	testrl.burst = 2;
+	testrl.interval = 0;
+	test_ratelimited(test, true);
+	test_ratelimited(test, true);
+	test_ratelimited(test, true);
+	test_ratelimited(test, true);
+	test_ratelimited(test, true);
+	test_ratelimited(test, true);
+	test_ratelimited(test, true);
+
+	// Testing re-enabling.
+	testrl.interval = TESTRL_INTERVAL;
+	test_ratelimited(test, true);
+	test_ratelimited(test, true);
+	test_ratelimited(test, false);
+	test_ratelimited(test, false);
+}
+
+static struct kunit_case sort_test_cases[] = {
+	KUNIT_CASE_SLOW(test_ratelimit_smoke),
+	{}
+};
+
+static struct kunit_suite ratelimit_test_suite = {
+	.name = "lib_ratelimit",
+	.test_cases = sort_test_cases,
+};
+
+kunit_test_suites(&ratelimit_test_suite);
+
+MODULE_DESCRIPTION("___ratelimit() KUnit test suite");
+MODULE_LICENSE("GPL");
-- 
2.40.1
Re: [PATCH v6 1/3] lib: Add trivial kunit test for ratelimit
Posted by Andrew Morton 7 months ago
On Wed,  9 Jul 2025 11:03:33 -0700 "Paul E. McKenney" <paulmck@kernel.org> wrote:

> Add a simple single-threaded smoke test for lib/ratelimit.c
> 
> To run on x86:
> 
> 	make ARCH=x86_64 mrproper
> 	./tools/testing/kunit/kunit.py run --arch x86_64 --kconfig_add CONFIG_RATELIMIT_KUNIT_TEST=y --kconfig_add CONFIG_SMP=y lib_ratelimit
> 
> This will fail on old ___ratelimit(), and subsequent patches provide
> the fixes that are required.
> 
> [ paulmck:  Apply timeout and kunit feedback from Petr Mladek. ]

The above line makes me suspect that this was paulmck tweaking someone
else's patch.  If the authorship correct on this one?
Re: [PATCH v6 1/3] lib: Add trivial kunit test for ratelimit
Posted by Steven Rostedt 7 months ago
On Wed, 9 Jul 2025 15:41:52 -0700
Andrew Morton <akpm@linux-foundation.org> wrote:

> On Wed,  9 Jul 2025 11:03:33 -0700 "Paul E. McKenney" <paulmck@kernel.org> wrote:
> 
> > Add a simple single-threaded smoke test for lib/ratelimit.c
> > 
> > To run on x86:
> > 
> > 	make ARCH=x86_64 mrproper
> > 	./tools/testing/kunit/kunit.py run --arch x86_64 --kconfig_add CONFIG_RATELIMIT_KUNIT_TEST=y --kconfig_add CONFIG_SMP=y lib_ratelimit
> > 
> > This will fail on old ___ratelimit(), and subsequent patches provide
> > the fixes that are required.
> > 
> > [ paulmck:  Apply timeout and kunit feedback from Petr Mladek. ]  
> 
> The above line makes me suspect that this was paulmck tweaking someone
> else's patch.  If the authorship correct on this one?

Looks to me that Paul just took some advice from Petr and was just giving
credit. Perhaps he could lose the "paulmck:" part?

Perhaps:

  Suggested-by: Petr Mladek <pmladek@suse.com> # for timeout and kunit feedback

?

-- Steve
Re: [PATCH v6 1/3] lib: Add trivial kunit test for ratelimit
Posted by Paul E. McKenney 7 months ago
On Wed, Jul 09, 2025 at 06:46:29PM -0400, Steven Rostedt wrote:
> On Wed, 9 Jul 2025 15:41:52 -0700
> Andrew Morton <akpm@linux-foundation.org> wrote:
> 
> > On Wed,  9 Jul 2025 11:03:33 -0700 "Paul E. McKenney" <paulmck@kernel.org> wrote:
> > 
> > > Add a simple single-threaded smoke test for lib/ratelimit.c
> > > 
> > > To run on x86:
> > > 
> > > 	make ARCH=x86_64 mrproper
> > > 	./tools/testing/kunit/kunit.py run --arch x86_64 --kconfig_add CONFIG_RATELIMIT_KUNIT_TEST=y --kconfig_add CONFIG_SMP=y lib_ratelimit
> > > 
> > > This will fail on old ___ratelimit(), and subsequent patches provide
> > > the fixes that are required.
> > > 
> > > [ paulmck:  Apply timeout and kunit feedback from Petr Mladek. ]  
> > 
> > The above line makes me suspect that this was paulmck tweaking someone
> > else's patch.  If the authorship correct on this one?
> 
> Looks to me that Paul just took some advice from Petr and was just giving
> credit. Perhaps he could lose the "paulmck:" part?

You got it!

> Perhaps:
> 
>   Suggested-by: Petr Mladek <pmladek@suse.com> # for timeout and kunit feedback

That would work for me.

							Thanx, Paul
[PATCH v6 2/3] lib: Make the ratelimit test more reliable
Posted by Paul E. McKenney 7 months ago
From: Petr Mladek <pmladek@suse.com>

The selftest fails most of the times when running in qemu with
a kernel configured with CONFIG_HZ = 250:

>  test_ratelimit_smoke: 1 callbacks suppressed
>  # test_ratelimit_smoke: ASSERTION FAILED at lib/tests/test_ratelimit.c:28
>                    Expected ___ratelimit(&testrl, "test_ratelimit_smoke") == (false), but
>                        ___ratelimit(&testrl, "test_ratelimit_smoke") == 1 (0x1)
>                        (false) == 0 (0x0)

Try to make the test slightly more reliable by calling the problematic
ratelimit in the middle of the interval.

Signed-off-by: Petr Mladek <pmladek@suse.com>
Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
---
 lib/tests/test_ratelimit.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lib/tests/test_ratelimit.c b/lib/tests/test_ratelimit.c
index 0374107f5ea89..5d6ec88546005 100644
--- a/lib/tests/test_ratelimit.c
+++ b/lib/tests/test_ratelimit.c
@@ -24,19 +24,19 @@ static void test_ratelimit_smoke(struct kunit *test)
 	test_ratelimited(test, true);
 	test_ratelimited(test, false);
 
-	schedule_timeout_idle(TESTRL_INTERVAL - 40);
+	schedule_timeout_idle(TESTRL_INTERVAL / 2);
 	test_ratelimited(test, false);
 
-	schedule_timeout_idle(50);
+	schedule_timeout_idle(TESTRL_INTERVAL * 3 / 4);
 	test_ratelimited(test, true);
 
 	schedule_timeout_idle(2 * TESTRL_INTERVAL);
 	test_ratelimited(test, true);
 	test_ratelimited(test, true);
 
-	schedule_timeout_idle(TESTRL_INTERVAL - 40);
+	schedule_timeout_idle(TESTRL_INTERVAL / 2 );
 	test_ratelimited(test, true);
-	schedule_timeout_idle(50);
+	schedule_timeout_idle(TESTRL_INTERVAL * 3 / 4);
 	test_ratelimited(test, true);
 	test_ratelimited(test, true);
 	test_ratelimited(test, true);
-- 
2.40.1
Re: [PATCH v6 2/3] lib: Make the ratelimit test more reliable
Posted by Andrew Morton 7 months ago
On Wed,  9 Jul 2025 11:03:34 -0700 "Paul E. McKenney" <paulmck@kernel.org> wrote:

> The selftest fails most of the times when running in qemu with
> a kernel configured with CONFIG_HZ = 250:
> 
> >  test_ratelimit_smoke: 1 callbacks suppressed
> >  # test_ratelimit_smoke: ASSERTION FAILED at lib/tests/test_ratelimit.c:28
> >                    Expected ___ratelimit(&testrl, "test_ratelimit_smoke") == (false), but
> >                        ___ratelimit(&testrl, "test_ratelimit_smoke") == 1 (0x1)
> >                        (false) == 0 (0x0)
> 
> Try to make the test slightly more reliable by calling the problematic
> ratelimit in the middle of the interval.
> 
> Signed-off-by: Petr Mladek <pmladek@suse.com>
> Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
> ---
>  lib/tests/test_ratelimit.c | 8 ++++----
>  1 file changed, 4 insertions(+), 4 deletions(-)

Patch 1 adds test_ratelimit.c and patch 2 fixes it.

Unconventional (and undesirable IMO).  Would the world end if I folded
2 into 1?
Re: [PATCH v6 2/3] lib: Make the ratelimit test more reliable
Posted by Paul E. McKenney 7 months ago
On Wed, Jul 09, 2025 at 03:44:54PM -0700, Andrew Morton wrote:
> On Wed,  9 Jul 2025 11:03:34 -0700 "Paul E. McKenney" <paulmck@kernel.org> wrote:
> 
> > The selftest fails most of the times when running in qemu with
> > a kernel configured with CONFIG_HZ = 250:
> > 
> > >  test_ratelimit_smoke: 1 callbacks suppressed
> > >  # test_ratelimit_smoke: ASSERTION FAILED at lib/tests/test_ratelimit.c:28
> > >                    Expected ___ratelimit(&testrl, "test_ratelimit_smoke") == (false), but
> > >                        ___ratelimit(&testrl, "test_ratelimit_smoke") == 1 (0x1)
> > >                        (false) == 0 (0x0)
> > 
> > Try to make the test slightly more reliable by calling the problematic
> > ratelimit in the middle of the interval.
> > 
> > Signed-off-by: Petr Mladek <pmladek@suse.com>
> > Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
> > ---
> >  lib/tests/test_ratelimit.c | 8 ++++----
> >  1 file changed, 4 insertions(+), 4 deletions(-)
> 
> Patch 1 adds test_ratelimit.c and patch 2 fixes it.
> 
> Unconventional (and undesirable IMO).  Would the world end if I folded
> 2 into 1?

Folding them together works for me, as long as Petr is properly credited.

							Thanx, Paul
[PATCH v6 3/3] lib: Add stress test for ratelimit
Posted by Paul E. McKenney 7 months ago
Add a simple stress test for lib/ratelimit.c

To run on x86:

	./tools/testing/kunit/kunit.py run --arch x86_64 --kconfig_add CONFIG_RATELIMIT_KUNIT_TEST=y --kconfig_add CONFIG_SMP=y --qemu_args "-smp 4" lib_ratelimit

On a 16-CPU system, the "4" in "-smp 4" can be varied between 1 and 8.
Larger numbers have higher probabilities of introducing delays that
break the smoke test.  In the extreme case, increasing the number to
larger than the number of CPUs in the underlying system is an excellent
way to get a test failure.

Link: https://lore.kernel.org/all/fbe93a52-365e-47fe-93a4-44a44547d601@paulmck-laptop/
Link: https://lore.kernel.org/all/20250423115409.3425-1-spasswolf@web.de/
Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
Cc: Petr Mladek <pmladek@suse.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Kuniyuki Iwashima <kuniyu@amazon.com>
Cc: Mateusz Guzik <mjguzik@gmail.com>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: John Ogness <john.ogness@linutronix.de>
Cc: Sergey Senozhatsky <senozhatsky@chromium.org>
Cc: Jon Pan-Doh <pandoh@google.com>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Karolina Stolarek <karolina.stolarek@oracle.com>
---
 lib/tests/test_ratelimit.c | 69 ++++++++++++++++++++++++++++++++++++--
 1 file changed, 67 insertions(+), 2 deletions(-)

diff --git a/lib/tests/test_ratelimit.c b/lib/tests/test_ratelimit.c
index 5d6ec88546005..bfaeca49304a5 100644
--- a/lib/tests/test_ratelimit.c
+++ b/lib/tests/test_ratelimit.c
@@ -4,6 +4,8 @@
 
 #include <linux/ratelimit.h>
 #include <linux/module.h>
+#include <linux/kthread.h>
+#include <linux/cpumask.h>
 
 /* a simple boot-time regression test */
 
@@ -63,14 +65,77 @@ static void test_ratelimit_smoke(struct kunit *test)
 	test_ratelimited(test, false);
 }
 
-static struct kunit_case sort_test_cases[] = {
+static struct ratelimit_state stressrl = RATELIMIT_STATE_INIT_FLAGS("stressrl", HZ / 10, 3,
+								    RATELIMIT_MSG_ON_RELEASE);
+
+static int doneflag;
+static const int stress_duration = 2 * HZ;
+
+struct stress_kthread {
+	unsigned long nattempts;
+	unsigned long nunlimited;
+	unsigned long nlimited;
+	unsigned long nmissed;
+	struct task_struct *tp;
+};
+
+static int test_ratelimit_stress_child(void *arg)
+{
+	struct stress_kthread *sktp = arg;
+
+	set_user_nice(current, MAX_NICE);
+	WARN_ON_ONCE(!sktp->tp);
+
+	while (!READ_ONCE(doneflag)) {
+		sktp->nattempts++;
+		if (___ratelimit(&stressrl, __func__))
+			sktp->nunlimited++;
+		else
+			sktp->nlimited++;
+		cond_resched();
+	}
+
+	sktp->nmissed = ratelimit_state_reset_miss(&stressrl);
+	return 0;
+}
+
+static void test_ratelimit_stress(struct kunit *test)
+{
+	int i;
+	const int n_stress_kthread = cpumask_weight(cpu_online_mask);
+	struct stress_kthread skt = { 0 };
+	struct stress_kthread *sktp = kcalloc(n_stress_kthread, sizeof(*sktp), GFP_KERNEL);
+
+	KUNIT_EXPECT_NOT_NULL_MSG(test, sktp, "Memory allocation failure");
+	for (i = 0; i < n_stress_kthread; i++) {
+		sktp[i].tp = kthread_run(test_ratelimit_stress_child, &sktp[i], "%s/%i",
+					 "test_ratelimit_stress_child", i);
+		KUNIT_EXPECT_NOT_NULL_MSG(test, sktp, "kthread creation failure");
+		pr_alert("Spawned test_ratelimit_stress_child %d\n", i);
+	}
+	schedule_timeout_idle(stress_duration);
+	WRITE_ONCE(doneflag, 1);
+	for (i = 0; i < n_stress_kthread; i++) {
+		kthread_stop(sktp[i].tp);
+		skt.nattempts += sktp[i].nattempts;
+		skt.nunlimited += sktp[i].nunlimited;
+		skt.nlimited += sktp[i].nlimited;
+		skt.nmissed += sktp[i].nmissed;
+	}
+	KUNIT_ASSERT_EQ_MSG(test, skt.nunlimited + skt.nlimited, skt.nattempts,
+			    "Outcomes not equal to attempts");
+	KUNIT_ASSERT_EQ_MSG(test, skt.nlimited, skt.nmissed, "Misses not equal to limits");
+}
+
+static struct kunit_case ratelimit_test_cases[] = {
 	KUNIT_CASE_SLOW(test_ratelimit_smoke),
+	KUNIT_CASE_SLOW(test_ratelimit_stress),
 	{}
 };
 
 static struct kunit_suite ratelimit_test_suite = {
 	.name = "lib_ratelimit",
-	.test_cases = sort_test_cases,
+	.test_cases = ratelimit_test_cases,
 };
 
 kunit_test_suites(&ratelimit_test_suite);
-- 
2.40.1