diff mbox series

[v2,6/8] rcuscale: Add test for using call_rcu_lazy() to emulate kfree_rcu()

Message ID 20220622225102.2112026-8-joel@joelfernandes.org (mailing list archive)
State Superseded
Headers show
Series Implement call_rcu_lazy() and miscellaneous fixes | expand

Commit Message

Joel Fernandes June 22, 2022, 10:51 p.m. UTC
Reuse the kfree_rcu() test in order to be able to compare the memory reclaiming
properties of call_rcu_lazy() with kfree_rcu().

With this test, we find similar memory footprint and time call_rcu_lazy()
free'ing takes compared to kfree_rcu(). Also we confirm that call_rcu_lazy()
can survive OOM during extremely frequent calls.

If we really push it, i.e. boot system with low memory and compare
kfree_rcu() with call_rcu_lazy(), I find that call_rcu_lazy() is more
resilient and is much harder to produce OOM as compared to kfree_rcu().

Signed-off-by: Joel Fernandes (Google) <joel@joelfernandes.org>
---
 kernel/rcu/rcu.h       |  6 ++++
 kernel/rcu/rcuscale.c  | 64 +++++++++++++++++++++++++++++++++++++++++-
 kernel/rcu/tree_nocb.h | 17 ++++++++++-
 3 files changed, 85 insertions(+), 2 deletions(-)

Comments

kernel test robot June 23, 2022, 2:09 a.m. UTC | #1
Hi "Joel,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on linus/master]
[also build test ERROR on v5.19-rc3 next-20220622]
[cannot apply to paulmck-rcu/dev]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/intel-lab-lkp/linux/commits/Joel-Fernandes-Google/Implement-call_rcu_lazy-and-miscellaneous-fixes/20220623-065447
base:   https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git de5c208d533a46a074eb46ea17f672cc005a7269
config: hexagon-randconfig-r045-20220622 (https://download.01.org/0day-ci/archive/20220623/202206230936.goRWmVwu-lkp@intel.com/config)
compiler: clang version 15.0.0 (https://github.com/llvm/llvm-project 46be5faaf03466c3751f8a2882bef5a217e15926)
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/intel-lab-lkp/linux/commit/6c59cb940f39b882c20e6858c41df7c1470b930a
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review Joel-Fernandes-Google/Implement-call_rcu_lazy-and-miscellaneous-fixes/20220623-065447
        git checkout 6c59cb940f39b882c20e6858c41df7c1470b930a
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=hexagon SHELL=/bin/bash kernel/rcu/

If you fix the issue, kindly add following tag where applicable
Reported-by: kernel test robot <lkp@intel.com>

All error/warnings (new ones prefixed by >>):

>> kernel/rcu/rcuscale.c:663:6: warning: no previous prototype for function 'kfree_rcu_lazy' [-Wmissing-prototypes]
   void kfree_rcu_lazy(struct rcu_head *rh)
        ^
   kernel/rcu/rcuscale.c:663:1: note: declare 'static' if the function is not intended to be used outside of this translation unit
   void kfree_rcu_lazy(struct rcu_head *rh)
   ^
   static 
>> kernel/rcu/rcuscale.c:789:6: warning: no previous prototype for function 'call_rcu_lazy_test1' [-Wmissing-prototypes]
   void call_rcu_lazy_test1(struct rcu_head *rh)
        ^
   kernel/rcu/rcuscale.c:789:1: note: declare 'static' if the function is not intended to be used outside of this translation unit
   void call_rcu_lazy_test1(struct rcu_head *rh)
   ^
   static 
>> kernel/rcu/rcuscale.c:810:14: error: call to undeclared function 'rcu_scale_get_jiffies_till_flush'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
                   orig_jif = rcu_scale_get_jiffies_till_flush();
                              ^
>> kernel/rcu/rcuscale.c:813:3: error: call to undeclared function 'rcu_scale_set_jiffies_till_flush'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
                   rcu_scale_set_jiffies_till_flush(2 * HZ);
                   ^
   2 warnings and 2 errors generated.


vim +/rcu_scale_get_jiffies_till_flush +810 kernel/rcu/rcuscale.c

   661	
   662	/* Used if doing RCU-kfree'ing via call_rcu_lazy(). */
 > 663	void kfree_rcu_lazy(struct rcu_head *rh)
   664	{
   665		struct kfree_obj *obj = container_of(rh, struct kfree_obj, rh);
   666		kfree(obj);
   667	}
   668	
   669	static int
   670	kfree_scale_thread(void *arg)
   671	{
   672		int i, loop = 0;
   673		long me = (long)arg;
   674		struct kfree_obj *alloc_ptr;
   675		u64 start_time, end_time;
   676		long long mem_begin, mem_during = 0;
   677		bool kfree_rcu_test_both;
   678		DEFINE_TORTURE_RANDOM(tr);
   679	
   680		VERBOSE_SCALEOUT_STRING("kfree_scale_thread task started");
   681		set_cpus_allowed_ptr(current, cpumask_of(me % nr_cpu_ids));
   682		set_user_nice(current, MAX_NICE);
   683		kfree_rcu_test_both = (kfree_rcu_test_single == kfree_rcu_test_double);
   684	
   685		start_time = ktime_get_mono_fast_ns();
   686	
   687		if (atomic_inc_return(&n_kfree_scale_thread_started) >= kfree_nrealthreads) {
   688			if (gp_exp)
   689				b_rcu_gp_test_started = cur_ops->exp_completed() / 2;
   690			else
   691				b_rcu_gp_test_started = cur_ops->get_gp_seq();
   692		}
   693	
   694		do {
   695			if (!mem_during) {
   696				mem_during = mem_begin = si_mem_available();
   697			} else if (loop % (kfree_loops / 4) == 0) {
   698				mem_during = (mem_during + si_mem_available()) / 2;
   699			}
   700	
   701			for (i = 0; i < kfree_alloc_num; i++) {
   702				alloc_ptr = kmalloc(kfree_mult * sizeof(struct kfree_obj), GFP_KERNEL);
   703				if (!alloc_ptr)
   704					return -ENOMEM;
   705	
   706				if (kfree_rcu_by_lazy) {
   707					call_rcu_lazy(&(alloc_ptr->rh), kfree_rcu_lazy);
   708					continue;
   709				}
   710	
   711				// By default kfree_rcu_test_single and kfree_rcu_test_double are
   712				// initialized to false. If both have the same value (false or true)
   713				// both are randomly tested, otherwise only the one with value true
   714				// is tested.
   715				if ((kfree_rcu_test_single && !kfree_rcu_test_double) ||
   716						(kfree_rcu_test_both && torture_random(&tr) & 0x800))
   717					kfree_rcu(alloc_ptr);
   718				else
   719					kfree_rcu(alloc_ptr, rh);
   720			}
   721	
   722			cond_resched();
   723		} while (!torture_must_stop() && ++loop < kfree_loops);
   724	
   725		if (atomic_inc_return(&n_kfree_scale_thread_ended) >= kfree_nrealthreads) {
   726			end_time = ktime_get_mono_fast_ns();
   727	
   728			if (gp_exp)
   729				b_rcu_gp_test_finished = cur_ops->exp_completed() / 2;
   730			else
   731				b_rcu_gp_test_finished = cur_ops->get_gp_seq();
   732	
   733			pr_alert("Total time taken by all kfree'ers: %llu ns, loops: %d, batches: %ld, memory footprint: %lldMB\n",
   734			       (unsigned long long)(end_time - start_time), kfree_loops,
   735			       rcuscale_seq_diff(b_rcu_gp_test_finished, b_rcu_gp_test_started),
   736			       (mem_begin - mem_during) >> (20 - PAGE_SHIFT));
   737	
   738			if (shutdown) {
   739				smp_mb(); /* Assign before wake. */
   740				wake_up(&shutdown_wq);
   741			}
   742		}
   743	
   744		torture_kthread_stopping("kfree_scale_thread");
   745		return 0;
   746	}
   747	
   748	static void
   749	kfree_scale_cleanup(void)
   750	{
   751		int i;
   752	
   753		if (kfree_rcu_by_lazy)
   754			rcu_force_call_rcu_to_lazy(false);
   755	
   756		if (torture_cleanup_begin())
   757			return;
   758	
   759		if (kfree_reader_tasks) {
   760			for (i = 0; i < kfree_nrealthreads; i++)
   761				torture_stop_kthread(kfree_scale_thread,
   762						     kfree_reader_tasks[i]);
   763			kfree(kfree_reader_tasks);
   764		}
   765	
   766		torture_cleanup_end();
   767	}
   768	
   769	/*
   770	 * shutdown kthread.  Just waits to be awakened, then shuts down system.
   771	 */
   772	static int
   773	kfree_scale_shutdown(void *arg)
   774	{
   775		wait_event(shutdown_wq,
   776			   atomic_read(&n_kfree_scale_thread_ended) >= kfree_nrealthreads);
   777	
   778		smp_mb(); /* Wake before output. */
   779	
   780		kfree_scale_cleanup();
   781		kernel_power_off();
   782		return -EINVAL;
   783	}
   784	
   785	// Used if doing RCU-kfree'ing via call_rcu_lazy().
   786	unsigned long jiffies_at_lazy_cb;
   787	struct rcu_head lazy_test1_rh;
   788	int rcu_lazy_test1_cb_called;
 > 789	void call_rcu_lazy_test1(struct rcu_head *rh)
   790	{
   791		jiffies_at_lazy_cb = jiffies;
   792		WRITE_ONCE(rcu_lazy_test1_cb_called, 1);
   793	}
   794	
   795	static int __init
   796	kfree_scale_init(void)
   797	{
   798		long i;
   799		int firsterr = 0;
   800		unsigned long orig_jif, jif_start;
   801	
   802		// Force all call_rcu() to call_rcu_lazy() so that non-lazy CBs
   803		// do not remove laziness of the lazy ones (since the test tries
   804		// to stress call_rcu_lazy() for OOM).
   805		//
   806		// Also, do a quick self-test to ensure laziness is as much as
   807		// expected.
   808		if (kfree_rcu_by_lazy) {
   809			/* do a test to check the timeout. */
 > 810			orig_jif = rcu_scale_get_jiffies_till_flush();
   811	
   812			rcu_force_call_rcu_to_lazy(true);
 > 813			rcu_scale_set_jiffies_till_flush(2 * HZ);
   814			rcu_barrier();
   815	
   816			jif_start = jiffies;
   817			jiffies_at_lazy_cb = 0;
   818			call_rcu_lazy(&lazy_test1_rh, call_rcu_lazy_test1);
   819	
   820			smp_cond_load_relaxed(&rcu_lazy_test1_cb_called, VAL == 1);
   821	
   822			rcu_scale_set_jiffies_till_flush(orig_jif);
   823	
   824			if (WARN_ON_ONCE(jiffies_at_lazy_cb - jif_start < 2 * HZ)) {
   825				pr_alert("Lazy CBs are not being lazy as expected!\n");
   826				return -1;
   827			}
   828	
   829			if (WARN_ON_ONCE(jiffies_at_lazy_cb - jif_start > 3 * HZ)) {
   830				pr_alert("Lazy CBs are being too lazy!\n");
   831				return -1;
   832			}
   833		}
   834	
   835		kfree_nrealthreads = compute_real(kfree_nthreads);
   836		/* Start up the kthreads. */
   837		if (shutdown) {
   838			init_waitqueue_head(&shutdown_wq);
   839			firsterr = torture_create_kthread(kfree_scale_shutdown, NULL,
   840							  shutdown_task);
   841			if (torture_init_error(firsterr))
   842				goto unwind;
   843			schedule_timeout_uninterruptible(1);
   844		}
   845	
   846		pr_alert("kfree object size=%zu, kfree_rcu_by_lazy=%d\n",
   847				kfree_mult * sizeof(struct kfree_obj),
   848				kfree_rcu_by_lazy);
   849	
   850		kfree_reader_tasks = kcalloc(kfree_nrealthreads, sizeof(kfree_reader_tasks[0]),
   851				       GFP_KERNEL);
   852		if (kfree_reader_tasks == NULL) {
   853			firsterr = -ENOMEM;
   854			goto unwind;
   855		}
   856	
   857		for (i = 0; i < kfree_nrealthreads; i++) {
   858			firsterr = torture_create_kthread(kfree_scale_thread, (void *)i,
   859							  kfree_reader_tasks[i]);
   860			if (torture_init_error(firsterr))
   861				goto unwind;
   862		}
   863	
   864		while (atomic_read(&n_kfree_scale_thread_started) < kfree_nrealthreads)
   865			schedule_timeout_uninterruptible(1);
   866	
   867		torture_init_end();
   868		return 0;
   869	
   870	unwind:
   871		torture_init_end();
   872		kfree_scale_cleanup();
   873		return firsterr;
   874	}
   875
kernel test robot June 23, 2022, 3 a.m. UTC | #2
Hi "Joel,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on linus/master]
[also build test ERROR on v5.19-rc3 next-20220622]
[cannot apply to paulmck-rcu/dev]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/intel-lab-lkp/linux/commits/Joel-Fernandes-Google/Implement-call_rcu_lazy-and-miscellaneous-fixes/20220623-065447
base:   https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git de5c208d533a46a074eb46ea17f672cc005a7269
config: s390-randconfig-r044-20220622 (https://download.01.org/0day-ci/archive/20220623/202206231028.gVHd7wR5-lkp@intel.com/config)
compiler: clang version 15.0.0 (https://github.com/llvm/llvm-project 46be5faaf03466c3751f8a2882bef5a217e15926)
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # install s390 cross compiling tool for clang build
        # apt-get install binutils-s390x-linux-gnu
        # https://github.com/intel-lab-lkp/linux/commit/6c59cb940f39b882c20e6858c41df7c1470b930a
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review Joel-Fernandes-Google/Implement-call_rcu_lazy-and-miscellaneous-fixes/20220623-065447
        git checkout 6c59cb940f39b882c20e6858c41df7c1470b930a
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=s390 SHELL=/bin/bash

If you fix the issue, kindly add following tag where applicable
Reported-by: kernel test robot <lkp@intel.com>

All errors (new ones prefixed by >>):

   /opt/cross/gcc-11.3.0-nolibc/s390x-linux/bin/s390x-linux-ld: kernel/rcu/rcuscale.o: in function `kfree_scale_init':
>> kernel/rcu/rcuscale.c:810: undefined reference to `rcu_scale_get_jiffies_till_flush'
>> /opt/cross/gcc-11.3.0-nolibc/s390x-linux/bin/s390x-linux-ld: kernel/rcu/rcuscale.c:813: undefined reference to `rcu_scale_set_jiffies_till_flush'
   /opt/cross/gcc-11.3.0-nolibc/s390x-linux/bin/s390x-linux-ld: kernel/rcu/rcuscale.c:822: undefined reference to `rcu_scale_set_jiffies_till_flush'
   /opt/cross/gcc-11.3.0-nolibc/s390x-linux/bin/s390x-linux-ld: certs/system_keyring.o: in function `load_system_certificate_list':
   certs/system_keyring.c:207: undefined reference to `x509_load_certificate_list'
   /opt/cross/gcc-11.3.0-nolibc/s390x-linux/bin/s390x-linux-ld: drivers/dma/fsl-edma.o: in function `fsl_edma_probe':
   drivers/dma/fsl-edma.c:302: undefined reference to `devm_ioremap_resource'
   /opt/cross/gcc-11.3.0-nolibc/s390x-linux/bin/s390x-linux-ld: drivers/dma/fsl-edma.c:327: undefined reference to `devm_ioremap_resource'
   /opt/cross/gcc-11.3.0-nolibc/s390x-linux/bin/s390x-linux-ld: drivers/dma/idma64.o: in function `idma64_platform_probe':
   drivers/dma/idma64.c:644: undefined reference to `devm_ioremap_resource'
   /opt/cross/gcc-11.3.0-nolibc/s390x-linux/bin/s390x-linux-ld: drivers/clocksource/timer-of.o: in function `timer_of_init':
   drivers/clocksource/timer-of.c:151: undefined reference to `iounmap'
   /opt/cross/gcc-11.3.0-nolibc/s390x-linux/bin/s390x-linux-ld: drivers/clocksource/timer-of.o: in function `timer_of_base_init':
   drivers/clocksource/timer-of.c:159: undefined reference to `of_iomap'
   /opt/cross/gcc-11.3.0-nolibc/s390x-linux/bin/s390x-linux-ld: drivers/clocksource/timer-of.o: in function `timer_of_cleanup':
   drivers/clocksource/timer-of.c:151: undefined reference to `iounmap'


vim +810 kernel/rcu/rcuscale.c

   794	
   795	static int __init
   796	kfree_scale_init(void)
   797	{
   798		long i;
   799		int firsterr = 0;
   800		unsigned long orig_jif, jif_start;
   801	
   802		// Force all call_rcu() to call_rcu_lazy() so that non-lazy CBs
   803		// do not remove laziness of the lazy ones (since the test tries
   804		// to stress call_rcu_lazy() for OOM).
   805		//
   806		// Also, do a quick self-test to ensure laziness is as much as
   807		// expected.
   808		if (kfree_rcu_by_lazy) {
   809			/* do a test to check the timeout. */
 > 810			orig_jif = rcu_scale_get_jiffies_till_flush();
   811	
   812			rcu_force_call_rcu_to_lazy(true);
 > 813			rcu_scale_set_jiffies_till_flush(2 * HZ);
   814			rcu_barrier();
   815	
   816			jif_start = jiffies;
   817			jiffies_at_lazy_cb = 0;
   818			call_rcu_lazy(&lazy_test1_rh, call_rcu_lazy_test1);
   819	
   820			smp_cond_load_relaxed(&rcu_lazy_test1_cb_called, VAL == 1);
   821	
   822			rcu_scale_set_jiffies_till_flush(orig_jif);
   823	
   824			if (WARN_ON_ONCE(jiffies_at_lazy_cb - jif_start < 2 * HZ)) {
   825				pr_alert("Lazy CBs are not being lazy as expected!\n");
   826				return -1;
   827			}
   828	
   829			if (WARN_ON_ONCE(jiffies_at_lazy_cb - jif_start > 3 * HZ)) {
   830				pr_alert("Lazy CBs are being too lazy!\n");
   831				return -1;
   832			}
   833		}
   834	
   835		kfree_nrealthreads = compute_real(kfree_nthreads);
   836		/* Start up the kthreads. */
   837		if (shutdown) {
   838			init_waitqueue_head(&shutdown_wq);
   839			firsterr = torture_create_kthread(kfree_scale_shutdown, NULL,
   840							  shutdown_task);
   841			if (torture_init_error(firsterr))
   842				goto unwind;
   843			schedule_timeout_uninterruptible(1);
   844		}
   845	
   846		pr_alert("kfree object size=%zu, kfree_rcu_by_lazy=%d\n",
   847				kfree_mult * sizeof(struct kfree_obj),
   848				kfree_rcu_by_lazy);
   849	
   850		kfree_reader_tasks = kcalloc(kfree_nrealthreads, sizeof(kfree_reader_tasks[0]),
   851				       GFP_KERNEL);
   852		if (kfree_reader_tasks == NULL) {
   853			firsterr = -ENOMEM;
   854			goto unwind;
   855		}
   856	
   857		for (i = 0; i < kfree_nrealthreads; i++) {
   858			firsterr = torture_create_kthread(kfree_scale_thread, (void *)i,
   859							  kfree_reader_tasks[i]);
   860			if (torture_init_error(firsterr))
   861				goto unwind;
   862		}
   863	
   864		while (atomic_read(&n_kfree_scale_thread_started) < kfree_nrealthreads)
   865			schedule_timeout_uninterruptible(1);
   866	
   867		torture_init_end();
   868		return 0;
   869	
   870	unwind:
   871		torture_init_end();
   872		kfree_scale_cleanup();
   873		return firsterr;
   874	}
   875
kernel test robot June 23, 2022, 8:10 a.m. UTC | #3
Hi "Joel,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on linus/master]
[also build test WARNING on v5.19-rc3 next-20220622]
[cannot apply to paulmck-rcu/dev]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/intel-lab-lkp/linux/commits/Joel-Fernandes-Google/Implement-call_rcu_lazy-and-miscellaneous-fixes/20220623-065447
base:   https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git de5c208d533a46a074eb46ea17f672cc005a7269
config: x86_64-randconfig-s022 (https://download.01.org/0day-ci/archive/20220623/202206231529.kLjzriV0-lkp@intel.com/config)
compiler: gcc-11 (Debian 11.3.0-3) 11.3.0
reproduce:
        # apt-get install sparse
        # sparse version: v0.6.4-31-g4880bd19-dirty
        # https://github.com/intel-lab-lkp/linux/commit/6c59cb940f39b882c20e6858c41df7c1470b930a
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review Joel-Fernandes-Google/Implement-call_rcu_lazy-and-miscellaneous-fixes/20220623-065447
        git checkout 6c59cb940f39b882c20e6858c41df7c1470b930a
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        make W=1 C=1 CF='-fdiagnostic-prefix -D__CHECK_ENDIAN__' O=build_dir ARCH=x86_64 SHELL=/bin/bash kernel/rcu/

If you fix the issue, kindly add following tag where applicable
Reported-by: kernel test robot <lkp@intel.com>


sparse warnings: (new ones prefixed by >>)
>> kernel/rcu/rcuscale.c:663:6: sparse: sparse: symbol 'kfree_rcu_lazy' was not declared. Should it be static?
>> kernel/rcu/rcuscale.c:786:15: sparse: sparse: symbol 'jiffies_at_lazy_cb' was not declared. Should it be static?
>> kernel/rcu/rcuscale.c:787:17: sparse: sparse: symbol 'lazy_test1_rh' was not declared. Should it be static?
>> kernel/rcu/rcuscale.c:788:5: sparse: sparse: symbol 'rcu_lazy_test1_cb_called' was not declared. Should it be static?
>> kernel/rcu/rcuscale.c:789:6: sparse: sparse: symbol 'call_rcu_lazy_test1' was not declared. Should it be static?
Paul E. McKenney June 26, 2022, 4:13 a.m. UTC | #4
On Wed, Jun 22, 2022 at 10:51:00PM +0000, Joel Fernandes (Google) wrote:
> Reuse the kfree_rcu() test in order to be able to compare the memory reclaiming
> properties of call_rcu_lazy() with kfree_rcu().
> 
> With this test, we find similar memory footprint and time call_rcu_lazy()
> free'ing takes compared to kfree_rcu(). Also we confirm that call_rcu_lazy()
> can survive OOM during extremely frequent calls.
> 
> If we really push it, i.e. boot system with low memory and compare
> kfree_rcu() with call_rcu_lazy(), I find that call_rcu_lazy() is more
> resilient and is much harder to produce OOM as compared to kfree_rcu().

Another approach would be to make rcutorture's forward-progress testing
able to use call_rcu_lazy().  This would test lazy callback flooding.

Yet another approach would be to keep one CPU idle other than a
kthread doing call_rcu_lazy().  Of course "idle" includes redirecting
those pesky interrupts.

It is almost certainly necessary for rcutorture to exercise the
call_rcu_lazy() path regularly.

							Thanx, Paul

> Signed-off-by: Joel Fernandes (Google) <joel@joelfernandes.org>
> ---
>  kernel/rcu/rcu.h       |  6 ++++
>  kernel/rcu/rcuscale.c  | 64 +++++++++++++++++++++++++++++++++++++++++-
>  kernel/rcu/tree_nocb.h | 17 ++++++++++-
>  3 files changed, 85 insertions(+), 2 deletions(-)
> 
> diff --git a/kernel/rcu/rcu.h b/kernel/rcu/rcu.h
> index 71c0f45e70c3..436faf80a66b 100644
> --- a/kernel/rcu/rcu.h
> +++ b/kernel/rcu/rcu.h
> @@ -473,6 +473,12 @@ void do_trace_rcu_torture_read(const char *rcutorturename,
>  			       unsigned long c);
>  void rcu_gp_set_torture_wait(int duration);
>  void rcu_force_call_rcu_to_lazy(bool force);
> +
> +#if IS_ENABLED(CONFIG_RCU_SCALE_TEST)
> +unsigned long rcu_scale_get_jiffies_till_flush(void);
> +void rcu_scale_set_jiffies_till_flush(unsigned long j);
> +#endif
> +
>  #else
>  static inline void rcutorture_get_gp_data(enum rcutorture_type test_type,
>  					  int *flags, unsigned long *gp_seq)
> diff --git a/kernel/rcu/rcuscale.c b/kernel/rcu/rcuscale.c
> index 277a5bfb37d4..58ee5c2cb37b 100644
> --- a/kernel/rcu/rcuscale.c
> +++ b/kernel/rcu/rcuscale.c
> @@ -95,6 +95,7 @@ torture_param(int, verbose, 1, "Enable verbose debugging printk()s");
>  torture_param(int, writer_holdoff, 0, "Holdoff (us) between GPs, zero to disable");
>  torture_param(int, kfree_rcu_test, 0, "Do we run a kfree_rcu() scale test?");
>  torture_param(int, kfree_mult, 1, "Multiple of kfree_obj size to allocate.");
> +torture_param(int, kfree_rcu_by_lazy, 0, "Use call_rcu_lazy() to emulate kfree_rcu()?");
>  
>  static char *scale_type = "rcu";
>  module_param(scale_type, charp, 0444);
> @@ -658,6 +659,13 @@ struct kfree_obj {
>  	struct rcu_head rh;
>  };
>  
> +/* Used if doing RCU-kfree'ing via call_rcu_lazy(). */
> +void kfree_rcu_lazy(struct rcu_head *rh)
> +{
> +	struct kfree_obj *obj = container_of(rh, struct kfree_obj, rh);
> +	kfree(obj);
> +}
> +
>  static int
>  kfree_scale_thread(void *arg)
>  {
> @@ -695,6 +703,11 @@ kfree_scale_thread(void *arg)
>  			if (!alloc_ptr)
>  				return -ENOMEM;
>  
> +			if (kfree_rcu_by_lazy) {
> +				call_rcu_lazy(&(alloc_ptr->rh), kfree_rcu_lazy);
> +				continue;
> +			}
> +
>  			// By default kfree_rcu_test_single and kfree_rcu_test_double are
>  			// initialized to false. If both have the same value (false or true)
>  			// both are randomly tested, otherwise only the one with value true
> @@ -737,6 +750,9 @@ kfree_scale_cleanup(void)
>  {
>  	int i;
>  
> +	if (kfree_rcu_by_lazy)
> +		rcu_force_call_rcu_to_lazy(false);
> +
>  	if (torture_cleanup_begin())
>  		return;
>  
> @@ -766,11 +782,55 @@ kfree_scale_shutdown(void *arg)
>  	return -EINVAL;
>  }
>  
> +// Used if doing RCU-kfree'ing via call_rcu_lazy().
> +unsigned long jiffies_at_lazy_cb;
> +struct rcu_head lazy_test1_rh;
> +int rcu_lazy_test1_cb_called;
> +void call_rcu_lazy_test1(struct rcu_head *rh)
> +{
> +	jiffies_at_lazy_cb = jiffies;
> +	WRITE_ONCE(rcu_lazy_test1_cb_called, 1);
> +}
> +
>  static int __init
>  kfree_scale_init(void)
>  {
>  	long i;
>  	int firsterr = 0;
> +	unsigned long orig_jif, jif_start;
> +
> +	// Force all call_rcu() to call_rcu_lazy() so that non-lazy CBs
> +	// do not remove laziness of the lazy ones (since the test tries
> +	// to stress call_rcu_lazy() for OOM).
> +	//
> +	// Also, do a quick self-test to ensure laziness is as much as
> +	// expected.
> +	if (kfree_rcu_by_lazy) {
> +		/* do a test to check the timeout. */
> +		orig_jif = rcu_scale_get_jiffies_till_flush();
> +
> +		rcu_force_call_rcu_to_lazy(true);
> +		rcu_scale_set_jiffies_till_flush(2 * HZ);
> +		rcu_barrier();
> +
> +		jif_start = jiffies;
> +		jiffies_at_lazy_cb = 0;
> +		call_rcu_lazy(&lazy_test1_rh, call_rcu_lazy_test1);
> +
> +		smp_cond_load_relaxed(&rcu_lazy_test1_cb_called, VAL == 1);
> +
> +		rcu_scale_set_jiffies_till_flush(orig_jif);
> +
> +		if (WARN_ON_ONCE(jiffies_at_lazy_cb - jif_start < 2 * HZ)) {
> +			pr_alert("Lazy CBs are not being lazy as expected!\n");
> +			return -1;
> +		}
> +
> +		if (WARN_ON_ONCE(jiffies_at_lazy_cb - jif_start > 3 * HZ)) {
> +			pr_alert("Lazy CBs are being too lazy!\n");
> +			return -1;
> +		}
> +	}
>  
>  	kfree_nrealthreads = compute_real(kfree_nthreads);
>  	/* Start up the kthreads. */
> @@ -783,7 +843,9 @@ kfree_scale_init(void)
>  		schedule_timeout_uninterruptible(1);
>  	}
>  
> -	pr_alert("kfree object size=%zu\n", kfree_mult * sizeof(struct kfree_obj));
> +	pr_alert("kfree object size=%zu, kfree_rcu_by_lazy=%d\n",
> +			kfree_mult * sizeof(struct kfree_obj),
> +			kfree_rcu_by_lazy);
>  
>  	kfree_reader_tasks = kcalloc(kfree_nrealthreads, sizeof(kfree_reader_tasks[0]),
>  			       GFP_KERNEL);
> diff --git a/kernel/rcu/tree_nocb.h b/kernel/rcu/tree_nocb.h
> index b481f1ea57c0..255f2945b0fc 100644
> --- a/kernel/rcu/tree_nocb.h
> +++ b/kernel/rcu/tree_nocb.h
> @@ -257,6 +257,21 @@ static bool wake_nocb_gp(struct rcu_data *rdp, bool force)
>  }
>  
>  #define LAZY_FLUSH_JIFFIES (10 * HZ)
> +unsigned long jiffies_till_flush = LAZY_FLUSH_JIFFIES;
> +
> +#ifdef CONFIG_RCU_SCALE_TEST
> +void rcu_scale_set_jiffies_till_flush(unsigned long jif)
> +{
> +	jiffies_till_flush = jif;
> +}
> +EXPORT_SYMBOL(rcu_scale_set_jiffies_till_flush);
> +
> +unsigned long rcu_scale_get_jiffies_till_flush(void)
> +{
> +	return jiffies_till_flush;
> +}
> +EXPORT_SYMBOL(rcu_scale_get_jiffies_till_flush);
> +#endif
>  
>  /*
>   * Arrange to wake the GP kthread for this NOCB group at some future
> @@ -275,7 +290,7 @@ static void wake_nocb_gp_defer(struct rcu_data *rdp, int waketype,
>  	 * of callback storm, no need to wake up too early.
>  	 */
>  	if (waketype == RCU_NOCB_WAKE_LAZY) {
> -		mod_timer(&rdp_gp->nocb_timer, jiffies + LAZY_FLUSH_JIFFIES);
> +		mod_timer(&rdp_gp->nocb_timer, jiffies + jiffies_till_flush);
>  		WRITE_ONCE(rdp_gp->nocb_defer_wakeup, waketype);
>  	} else if (waketype == RCU_NOCB_WAKE_BYPASS) {
>  		mod_timer(&rdp_gp->nocb_timer, jiffies + 2);
> -- 
> 2.37.0.rc0.104.g0611611a94-goog
>
Joel Fernandes July 8, 2022, 4:25 a.m. UTC | #5
On Sat, Jun 25, 2022 at 09:13:27PM -0700, Paul E. McKenney wrote:
> On Wed, Jun 22, 2022 at 10:51:00PM +0000, Joel Fernandes (Google) wrote:
> > Reuse the kfree_rcu() test in order to be able to compare the memory reclaiming
> > properties of call_rcu_lazy() with kfree_rcu().
> > 
> > With this test, we find similar memory footprint and time call_rcu_lazy()
> > free'ing takes compared to kfree_rcu(). Also we confirm that call_rcu_lazy()
> > can survive OOM during extremely frequent calls.
> > 
> > If we really push it, i.e. boot system with low memory and compare
> > kfree_rcu() with call_rcu_lazy(), I find that call_rcu_lazy() is more
> > resilient and is much harder to produce OOM as compared to kfree_rcu().
> 
> Another approach would be to make rcutorture's forward-progress testing
> able to use call_rcu_lazy().  This would test lazy callback flooding.
> 
> Yet another approach would be to keep one CPU idle other than a
> kthread doing call_rcu_lazy().  Of course "idle" includes redirecting
> those pesky interrupts.
> 
> It is almost certainly necessary for rcutorture to exercise the
> call_rcu_lazy() path regularly.

Currently I added a test like the following which adds a new torture type, my
thought was to stress the new code to make sure nothing crashed or hung the
kernel. That is working well except I don't exactly understand the total-gps
print showing 0, which the other print shows 1188 GPs. I'll go dig into that
tomorrow.. thanks!

The print shows
TREE11 ------- 1474 GPs (12.2833/s) [rcu_lazy: g0 f0x0 total-gps=0]
TREE11 no success message, 7 successful version messages

diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c
index 7120165a9342..cc6b7392d801 100644
--- a/kernel/rcu/rcutorture.c
+++ b/kernel/rcu/rcutorture.c
@@ -872,6 +872,64 @@ static struct rcu_torture_ops tasks_rude_ops = {
 
 #endif // #else #ifdef CONFIG_TASKS_RUDE_RCU
 
+#ifdef CONFIG_RCU_LAZY
+
+/*
+ * Definitions for lazy RCU torture testing.
+ */
+unsigned long orig_jiffies_till_flush;
+
+static void rcu_sync_torture_init_lazy(void)
+{
+	rcu_sync_torture_init();
+
+	orig_jiffies_till_flush = rcu_lazy_get_jiffies_till_flush();
+	rcu_lazy_set_jiffies_till_flush(50);
+}
+
+static void rcu_lazy_cleanup(void)
+{
+	rcu_lazy_set_jiffies_till_flush(orig_jiffies_till_flush);
+}
+
+static struct rcu_torture_ops rcu_lazy_ops = {
+	.ttype			= RCU_LAZY_FLAVOR,
+	.init			= rcu_sync_torture_init_lazy,
+	.cleanup		= rcu_lazy_cleanup,
+	.readlock		= rcu_torture_read_lock,
+	.read_delay		= rcu_read_delay,
+	.readunlock		= rcu_torture_read_unlock,
+	.readlock_held		= torture_readlock_not_held,
+	.get_gp_seq		= rcu_get_gp_seq,
+	.gp_diff		= rcu_seq_diff,
+	.deferred_free		= rcu_torture_deferred_free,
+	.sync			= synchronize_rcu,
+	.exp_sync		= synchronize_rcu_expedited,
+	.get_gp_state		= get_state_synchronize_rcu,
+	.start_gp_poll		= start_poll_synchronize_rcu,
+	.poll_gp_state		= poll_state_synchronize_rcu,
+	.cond_sync		= cond_synchronize_rcu,
+	.call			= call_rcu_lazy,
+	.cb_barrier		= rcu_barrier,
+	.fqs			= rcu_force_quiescent_state,
+	.stats			= NULL,
+	.gp_kthread_dbg		= show_rcu_gp_kthreads,
+	.check_boost_failed	= rcu_check_boost_fail,
+	.stall_dur		= rcu_jiffies_till_stall_check,
+	.irq_capable		= 1,
+	.can_boost		= IS_ENABLED(CONFIG_RCU_BOOST),
+	.extendables		= RCUTORTURE_MAX_EXTEND,
+	.name			= "rcu_lazy"
+};
+
+#define LAZY_OPS &rcu_lazy_ops,
+
+#else // #ifdef CONFIG_RCU_LAZY
+
+#define LAZY_OPS
+
+#endif // #else #ifdef CONFIG_RCU_LAZY
+
 
 #ifdef CONFIG_TASKS_TRACE_RCU
 
@@ -3145,7 +3203,7 @@ rcu_torture_init(void)
 	unsigned long gp_seq = 0;
 	static struct rcu_torture_ops *torture_ops[] = {
 		&rcu_ops, &rcu_busted_ops, &srcu_ops, &srcud_ops, &busted_srcud_ops,
-		TASKS_OPS TASKS_RUDE_OPS TASKS_TRACING_OPS
+		TASKS_OPS TASKS_RUDE_OPS TASKS_TRACING_OPS LAZY_OPS
 		&trivial_ops,
 	};
 
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE11 b/tools/testing/selftests/rcutorture/configs/rcu/TREE11
new file mode 100644
index 000000000000..436013f3e015
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE11
@@ -0,0 +1,18 @@
+CONFIG_SMP=y
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_PREEMPT_RCU=y
+CONFIG_HZ_PERIODIC=n
+CONFIG_NO_HZ_IDLE=y
+CONFIG_NO_HZ_FULL=n
+CONFIG_RCU_TRACE=y
+CONFIG_HOTPLUG_CPU=y
+CONFIG_MAXSMP=y
+CONFIG_CPUMASK_OFFSTACK=y
+CONFIG_RCU_NOCB_CPU=y
+CONFIG_DEBUG_LOCK_ALLOC=n
+CONFIG_RCU_BOOST=n
+CONFIG_DEBUG_OBJECTS_RCU_HEAD=n
+CONFIG_RCU_EXPERT=y
+CONFIG_RCU_LAZY=y
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE11.boot b/tools/testing/selftests/rcutorture/configs/rcu/TREE11.boot
new file mode 100644
index 000000000000..9b6f720d4ccd
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE11.boot
@@ -0,0 +1,8 @@
+maxcpus=8 nr_cpus=43
+rcutree.gp_preinit_delay=3
+rcutree.gp_init_delay=3
+rcutree.gp_cleanup_delay=3
+rcu_nocbs=0-7
+rcutorture.torture_type=rcu_lazy
+rcutorture.nocbs_nthreads=8
+rcutorture.fwd_progress=0
Paul E. McKenney July 8, 2022, 11:06 p.m. UTC | #6
On Fri, Jul 08, 2022 at 04:25:09AM +0000, Joel Fernandes wrote:
> On Sat, Jun 25, 2022 at 09:13:27PM -0700, Paul E. McKenney wrote:
> > On Wed, Jun 22, 2022 at 10:51:00PM +0000, Joel Fernandes (Google) wrote:
> > > Reuse the kfree_rcu() test in order to be able to compare the memory reclaiming
> > > properties of call_rcu_lazy() with kfree_rcu().
> > > 
> > > With this test, we find similar memory footprint and time call_rcu_lazy()
> > > free'ing takes compared to kfree_rcu(). Also we confirm that call_rcu_lazy()
> > > can survive OOM during extremely frequent calls.
> > > 
> > > If we really push it, i.e. boot system with low memory and compare
> > > kfree_rcu() with call_rcu_lazy(), I find that call_rcu_lazy() is more
> > > resilient and is much harder to produce OOM as compared to kfree_rcu().
> > 
> > Another approach would be to make rcutorture's forward-progress testing
> > able to use call_rcu_lazy().  This would test lazy callback flooding.
> > 
> > Yet another approach would be to keep one CPU idle other than a
> > kthread doing call_rcu_lazy().  Of course "idle" includes redirecting
> > those pesky interrupts.
> > 
> > It is almost certainly necessary for rcutorture to exercise the
> > call_rcu_lazy() path regularly.
> 
> Currently I added a test like the following which adds a new torture type, my
> thought was to stress the new code to make sure nothing crashed or hung the
> kernel. That is working well except I don't exactly understand the total-gps
> print showing 0, which the other print shows 1188 GPs. I'll go dig into that
> tomorrow.. thanks!
> 
> The print shows
> TREE11 ------- 1474 GPs (12.2833/s) [rcu_lazy: g0 f0x0 total-gps=0]
> TREE11 no success message, 7 successful version messages

Nice!!!  It is very good to see you correctly using the rcu_torture_ops
facility correctly!

And this could be good for your own testing, and I am happy to pull it
in for that purpose (given it being fixed, having a good commit log,
and so on).  After all, TREE10 is quite similar -- not part of CFLIST,
but useful for certain types of focused testing.

However, it would be very good to get call_rcu_lazy() testing going
more generally, and in particular in TREE01 where offloading changes
dynamically.  A good way to do this is to add a .call_lazy() component
to the rcu_torture_ops structure, and check for it in a manner similar
to that done for the .deferred_free() component.  Including adding a
gp_normal_lazy module parameter.  This would allow habitual testing
on a few scenarios and focused lazy testing on all of them via the
--bootargs parameter.

On the total-gps=0, the usual suspicion would be that the lazy callbacks
never got invoked.  It looks like you were doing about a two-minute run,
so maybe a longer run?  Though weren't they supposed to kick in at 15
seconds or so?  Or did this value of zero come about because this run
used exactly 300 grace periods?

							Thanx, Paul

> diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c
> index 7120165a9342..cc6b7392d801 100644
> --- a/kernel/rcu/rcutorture.c
> +++ b/kernel/rcu/rcutorture.c
> @@ -872,6 +872,64 @@ static struct rcu_torture_ops tasks_rude_ops = {
>  
>  #endif // #else #ifdef CONFIG_TASKS_RUDE_RCU
>  
> +#ifdef CONFIG_RCU_LAZY
> +
> +/*
> + * Definitions for lazy RCU torture testing.
> + */
> +unsigned long orig_jiffies_till_flush;
> +
> +static void rcu_sync_torture_init_lazy(void)
> +{
> +	rcu_sync_torture_init();
> +
> +	orig_jiffies_till_flush = rcu_lazy_get_jiffies_till_flush();
> +	rcu_lazy_set_jiffies_till_flush(50);
> +}
> +
> +static void rcu_lazy_cleanup(void)
> +{
> +	rcu_lazy_set_jiffies_till_flush(orig_jiffies_till_flush);
> +}
> +
> +static struct rcu_torture_ops rcu_lazy_ops = {
> +	.ttype			= RCU_LAZY_FLAVOR,
> +	.init			= rcu_sync_torture_init_lazy,
> +	.cleanup		= rcu_lazy_cleanup,
> +	.readlock		= rcu_torture_read_lock,
> +	.read_delay		= rcu_read_delay,
> +	.readunlock		= rcu_torture_read_unlock,
> +	.readlock_held		= torture_readlock_not_held,
> +	.get_gp_seq		= rcu_get_gp_seq,
> +	.gp_diff		= rcu_seq_diff,
> +	.deferred_free		= rcu_torture_deferred_free,
> +	.sync			= synchronize_rcu,
> +	.exp_sync		= synchronize_rcu_expedited,
> +	.get_gp_state		= get_state_synchronize_rcu,
> +	.start_gp_poll		= start_poll_synchronize_rcu,
> +	.poll_gp_state		= poll_state_synchronize_rcu,
> +	.cond_sync		= cond_synchronize_rcu,
> +	.call			= call_rcu_lazy,
> +	.cb_barrier		= rcu_barrier,
> +	.fqs			= rcu_force_quiescent_state,
> +	.stats			= NULL,
> +	.gp_kthread_dbg		= show_rcu_gp_kthreads,
> +	.check_boost_failed	= rcu_check_boost_fail,
> +	.stall_dur		= rcu_jiffies_till_stall_check,
> +	.irq_capable		= 1,
> +	.can_boost		= IS_ENABLED(CONFIG_RCU_BOOST),
> +	.extendables		= RCUTORTURE_MAX_EXTEND,
> +	.name			= "rcu_lazy"
> +};
> +
> +#define LAZY_OPS &rcu_lazy_ops,
> +
> +#else // #ifdef CONFIG_RCU_LAZY
> +
> +#define LAZY_OPS
> +
> +#endif // #else #ifdef CONFIG_RCU_LAZY
> +
>  
>  #ifdef CONFIG_TASKS_TRACE_RCU
>  
> @@ -3145,7 +3203,7 @@ rcu_torture_init(void)
>  	unsigned long gp_seq = 0;
>  	static struct rcu_torture_ops *torture_ops[] = {
>  		&rcu_ops, &rcu_busted_ops, &srcu_ops, &srcud_ops, &busted_srcud_ops,
> -		TASKS_OPS TASKS_RUDE_OPS TASKS_TRACING_OPS
> +		TASKS_OPS TASKS_RUDE_OPS TASKS_TRACING_OPS LAZY_OPS
>  		&trivial_ops,
>  	};
>  
> diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE11 b/tools/testing/selftests/rcutorture/configs/rcu/TREE11
> new file mode 100644
> index 000000000000..436013f3e015
> --- /dev/null
> +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE11
> @@ -0,0 +1,18 @@
> +CONFIG_SMP=y
> +CONFIG_PREEMPT_NONE=n
> +CONFIG_PREEMPT_VOLUNTARY=n
> +CONFIG_PREEMPT=y
> +#CHECK#CONFIG_PREEMPT_RCU=y
> +CONFIG_HZ_PERIODIC=n
> +CONFIG_NO_HZ_IDLE=y
> +CONFIG_NO_HZ_FULL=n
> +CONFIG_RCU_TRACE=y
> +CONFIG_HOTPLUG_CPU=y
> +CONFIG_MAXSMP=y
> +CONFIG_CPUMASK_OFFSTACK=y
> +CONFIG_RCU_NOCB_CPU=y
> +CONFIG_DEBUG_LOCK_ALLOC=n
> +CONFIG_RCU_BOOST=n
> +CONFIG_DEBUG_OBJECTS_RCU_HEAD=n
> +CONFIG_RCU_EXPERT=y
> +CONFIG_RCU_LAZY=y
> diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE11.boot b/tools/testing/selftests/rcutorture/configs/rcu/TREE11.boot
> new file mode 100644
> index 000000000000..9b6f720d4ccd
> --- /dev/null
> +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE11.boot
> @@ -0,0 +1,8 @@
> +maxcpus=8 nr_cpus=43
> +rcutree.gp_preinit_delay=3
> +rcutree.gp_init_delay=3
> +rcutree.gp_cleanup_delay=3
> +rcu_nocbs=0-7
> +rcutorture.torture_type=rcu_lazy
> +rcutorture.nocbs_nthreads=8
> +rcutorture.fwd_progress=0
> -- 
> 2.37.0.rc0.161.g10f37bed90-goog
>
Joel Fernandes July 12, 2022, 8:27 p.m. UTC | #7
Ah, with all the threads, I missed this one :(. Sorry about that.

On Fri, Jul 8, 2022 at 7:06 PM Paul E. McKenney <paulmck@kernel.org> wrote:

> > Currently I added a test like the following which adds a new torture type, my
> > thought was to stress the new code to make sure nothing crashed or hung the
> > kernel. That is working well except I don't exactly understand the total-gps
> > print showing 0, which the other print shows 1188 GPs. I'll go dig into that
> > tomorrow.. thanks!
> >
> > The print shows
> > TREE11 ------- 1474 GPs (12.2833/s) [rcu_lazy: g0 f0x0 total-gps=0]
> > TREE11 no success message, 7 successful version messages
>
> Nice!!!  It is very good to see you correctly using the rcu_torture_ops
> facility correctly!
>
> And this could be good for your own testing, and I am happy to pull it
> in for that purpose (given it being fixed, having a good commit log,
> and so on).  After all, TREE10 is quite similar -- not part of CFLIST,
> but useful for certain types of focused testing.
>
> However, it would be very good to get call_rcu_lazy() testing going
> more generally, and in particular in TREE01 where offloading changes
> dynamically.  A good way to do this is to add a .call_lazy() component
> to the rcu_torture_ops structure, and check for it in a manner similar
> to that done for the .deferred_free() component.  Including adding a
> gp_normal_lazy module parameter.  This would allow habitual testing
> on a few scenarios and focused lazy testing on all of them via the
> --bootargs parameter.

Ok, if you don't mind I will make this particular enhancement to the
torture test in a future patchset, since I kind of decided on doing v3
with just fixes to what I have and more testing. Certainly happy to
enhance these tests in a future version.

> On the total-gps=0, the usual suspicion would be that the lazy callbacks
> never got invoked.  It looks like you were doing about a two-minute run,
> so maybe a longer run?  Though weren't they supposed to kick in at 15
> seconds or so?  Or did this value of zero come about because this run
> used exactly 300 grace periods?

It was zero because it required the RCU_FLAVOR torture type, where as
my torture type was lazy. Adding RCU_LAZY_FLAVOR to the list fixed it
:)

Thanks!

 - Joel


> > diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c
> > index 7120165a9342..cc6b7392d801 100644
> > --- a/kernel/rcu/rcutorture.c
> > +++ b/kernel/rcu/rcutorture.c
> > @@ -872,6 +872,64 @@ static struct rcu_torture_ops tasks_rude_ops = {
> >
> >  #endif // #else #ifdef CONFIG_TASKS_RUDE_RCU
> >
> > +#ifdef CONFIG_RCU_LAZY
> > +
> > +/*
> > + * Definitions for lazy RCU torture testing.
> > + */
> > +unsigned long orig_jiffies_till_flush;
> > +
> > +static void rcu_sync_torture_init_lazy(void)
> > +{
> > +     rcu_sync_torture_init();
> > +
> > +     orig_jiffies_till_flush = rcu_lazy_get_jiffies_till_flush();
> > +     rcu_lazy_set_jiffies_till_flush(50);
> > +}
> > +
> > +static void rcu_lazy_cleanup(void)
> > +{
> > +     rcu_lazy_set_jiffies_till_flush(orig_jiffies_till_flush);
> > +}
> > +
> > +static struct rcu_torture_ops rcu_lazy_ops = {
> > +     .ttype                  = RCU_LAZY_FLAVOR,
> > +     .init                   = rcu_sync_torture_init_lazy,
> > +     .cleanup                = rcu_lazy_cleanup,
> > +     .readlock               = rcu_torture_read_lock,
> > +     .read_delay             = rcu_read_delay,
> > +     .readunlock             = rcu_torture_read_unlock,
> > +     .readlock_held          = torture_readlock_not_held,
> > +     .get_gp_seq             = rcu_get_gp_seq,
> > +     .gp_diff                = rcu_seq_diff,
> > +     .deferred_free          = rcu_torture_deferred_free,
> > +     .sync                   = synchronize_rcu,
> > +     .exp_sync               = synchronize_rcu_expedited,
> > +     .get_gp_state           = get_state_synchronize_rcu,
> > +     .start_gp_poll          = start_poll_synchronize_rcu,
> > +     .poll_gp_state          = poll_state_synchronize_rcu,
> > +     .cond_sync              = cond_synchronize_rcu,
> > +     .call                   = call_rcu_lazy,
> > +     .cb_barrier             = rcu_barrier,
> > +     .fqs                    = rcu_force_quiescent_state,
> > +     .stats                  = NULL,
> > +     .gp_kthread_dbg         = show_rcu_gp_kthreads,
> > +     .check_boost_failed     = rcu_check_boost_fail,
> > +     .stall_dur              = rcu_jiffies_till_stall_check,
> > +     .irq_capable            = 1,
> > +     .can_boost              = IS_ENABLED(CONFIG_RCU_BOOST),
> > +     .extendables            = RCUTORTURE_MAX_EXTEND,
> > +     .name                   = "rcu_lazy"
> > +};
> > +
> > +#define LAZY_OPS &rcu_lazy_ops,
> > +
> > +#else // #ifdef CONFIG_RCU_LAZY
> > +
> > +#define LAZY_OPS
> > +
> > +#endif // #else #ifdef CONFIG_RCU_LAZY
> > +
> >
> >  #ifdef CONFIG_TASKS_TRACE_RCU
> >
> > @@ -3145,7 +3203,7 @@ rcu_torture_init(void)
> >       unsigned long gp_seq = 0;
> >       static struct rcu_torture_ops *torture_ops[] = {
> >               &rcu_ops, &rcu_busted_ops, &srcu_ops, &srcud_ops, &busted_srcud_ops,
> > -             TASKS_OPS TASKS_RUDE_OPS TASKS_TRACING_OPS
> > +             TASKS_OPS TASKS_RUDE_OPS TASKS_TRACING_OPS LAZY_OPS
> >               &trivial_ops,
> >       };
> >
> > diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE11 b/tools/testing/selftests/rcutorture/configs/rcu/TREE11
> > new file mode 100644
> > index 000000000000..436013f3e015
> > --- /dev/null
> > +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE11
> > @@ -0,0 +1,18 @@
> > +CONFIG_SMP=y
> > +CONFIG_PREEMPT_NONE=n
> > +CONFIG_PREEMPT_VOLUNTARY=n
> > +CONFIG_PREEMPT=y
> > +#CHECK#CONFIG_PREEMPT_RCU=y
> > +CONFIG_HZ_PERIODIC=n
> > +CONFIG_NO_HZ_IDLE=y
> > +CONFIG_NO_HZ_FULL=n
> > +CONFIG_RCU_TRACE=y
> > +CONFIG_HOTPLUG_CPU=y
> > +CONFIG_MAXSMP=y
> > +CONFIG_CPUMASK_OFFSTACK=y
> > +CONFIG_RCU_NOCB_CPU=y
> > +CONFIG_DEBUG_LOCK_ALLOC=n
> > +CONFIG_RCU_BOOST=n
> > +CONFIG_DEBUG_OBJECTS_RCU_HEAD=n
> > +CONFIG_RCU_EXPERT=y
> > +CONFIG_RCU_LAZY=y
> > diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE11.boot b/tools/testing/selftests/rcutorture/configs/rcu/TREE11.boot
> > new file mode 100644
> > index 000000000000..9b6f720d4ccd
> > --- /dev/null
> > +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE11.boot
> > @@ -0,0 +1,8 @@
> > +maxcpus=8 nr_cpus=43
> > +rcutree.gp_preinit_delay=3
> > +rcutree.gp_init_delay=3
> > +rcutree.gp_cleanup_delay=3
> > +rcu_nocbs=0-7
> > +rcutorture.torture_type=rcu_lazy
> > +rcutorture.nocbs_nthreads=8
> > +rcutorture.fwd_progress=0
> > --
> > 2.37.0.rc0.161.g10f37bed90-goog
> >
Paul E. McKenney July 12, 2022, 8:58 p.m. UTC | #8
On Tue, Jul 12, 2022 at 04:27:05PM -0400, Joel Fernandes wrote:
> Ah, with all the threads, I missed this one :(. Sorry about that.

I know that feeling...

> On Fri, Jul 8, 2022 at 7:06 PM Paul E. McKenney <paulmck@kernel.org> wrote:
> 
> > > Currently I added a test like the following which adds a new torture type, my
> > > thought was to stress the new code to make sure nothing crashed or hung the
> > > kernel. That is working well except I don't exactly understand the total-gps
> > > print showing 0, which the other print shows 1188 GPs. I'll go dig into that
> > > tomorrow.. thanks!
> > >
> > > The print shows
> > > TREE11 ------- 1474 GPs (12.2833/s) [rcu_lazy: g0 f0x0 total-gps=0]
> > > TREE11 no success message, 7 successful version messages
> >
> > Nice!!!  It is very good to see you correctly using the rcu_torture_ops
> > facility correctly!
> >
> > And this could be good for your own testing, and I am happy to pull it
> > in for that purpose (given it being fixed, having a good commit log,
> > and so on).  After all, TREE10 is quite similar -- not part of CFLIST,
> > but useful for certain types of focused testing.
> >
> > However, it would be very good to get call_rcu_lazy() testing going
> > more generally, and in particular in TREE01 where offloading changes
> > dynamically.  A good way to do this is to add a .call_lazy() component
> > to the rcu_torture_ops structure, and check for it in a manner similar
> > to that done for the .deferred_free() component.  Including adding a
> > gp_normal_lazy module parameter.  This would allow habitual testing
> > on a few scenarios and focused lazy testing on all of them via the
> > --bootargs parameter.
> 
> Ok, if you don't mind I will make this particular enhancement to the
> torture test in a future patchset, since I kind of decided on doing v3
> with just fixes to what I have and more testing. Certainly happy to
> enhance these tests in a future version.

No need to gate v3 on those tests.

> > On the total-gps=0, the usual suspicion would be that the lazy callbacks
> > never got invoked.  It looks like you were doing about a two-minute run,
> > so maybe a longer run?  Though weren't they supposed to kick in at 15
> > seconds or so?  Or did this value of zero come about because this run
> > used exactly 300 grace periods?
> 
> It was zero because it required the RCU_FLAVOR torture type, where as
> my torture type was lazy. Adding RCU_LAZY_FLAVOR to the list fixed it
> :)

Heh!  Then it didn't actually do any testing.  Done that as well!

							Thanx, Paul

> Thanks!
> 
>  - Joel
> 
> 
> > > diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c
> > > index 7120165a9342..cc6b7392d801 100644
> > > --- a/kernel/rcu/rcutorture.c
> > > +++ b/kernel/rcu/rcutorture.c
> > > @@ -872,6 +872,64 @@ static struct rcu_torture_ops tasks_rude_ops = {
> > >
> > >  #endif // #else #ifdef CONFIG_TASKS_RUDE_RCU
> > >
> > > +#ifdef CONFIG_RCU_LAZY
> > > +
> > > +/*
> > > + * Definitions for lazy RCU torture testing.
> > > + */
> > > +unsigned long orig_jiffies_till_flush;
> > > +
> > > +static void rcu_sync_torture_init_lazy(void)
> > > +{
> > > +     rcu_sync_torture_init();
> > > +
> > > +     orig_jiffies_till_flush = rcu_lazy_get_jiffies_till_flush();
> > > +     rcu_lazy_set_jiffies_till_flush(50);
> > > +}
> > > +
> > > +static void rcu_lazy_cleanup(void)
> > > +{
> > > +     rcu_lazy_set_jiffies_till_flush(orig_jiffies_till_flush);
> > > +}
> > > +
> > > +static struct rcu_torture_ops rcu_lazy_ops = {
> > > +     .ttype                  = RCU_LAZY_FLAVOR,
> > > +     .init                   = rcu_sync_torture_init_lazy,
> > > +     .cleanup                = rcu_lazy_cleanup,
> > > +     .readlock               = rcu_torture_read_lock,
> > > +     .read_delay             = rcu_read_delay,
> > > +     .readunlock             = rcu_torture_read_unlock,
> > > +     .readlock_held          = torture_readlock_not_held,
> > > +     .get_gp_seq             = rcu_get_gp_seq,
> > > +     .gp_diff                = rcu_seq_diff,
> > > +     .deferred_free          = rcu_torture_deferred_free,
> > > +     .sync                   = synchronize_rcu,
> > > +     .exp_sync               = synchronize_rcu_expedited,
> > > +     .get_gp_state           = get_state_synchronize_rcu,
> > > +     .start_gp_poll          = start_poll_synchronize_rcu,
> > > +     .poll_gp_state          = poll_state_synchronize_rcu,
> > > +     .cond_sync              = cond_synchronize_rcu,
> > > +     .call                   = call_rcu_lazy,
> > > +     .cb_barrier             = rcu_barrier,
> > > +     .fqs                    = rcu_force_quiescent_state,
> > > +     .stats                  = NULL,
> > > +     .gp_kthread_dbg         = show_rcu_gp_kthreads,
> > > +     .check_boost_failed     = rcu_check_boost_fail,
> > > +     .stall_dur              = rcu_jiffies_till_stall_check,
> > > +     .irq_capable            = 1,
> > > +     .can_boost              = IS_ENABLED(CONFIG_RCU_BOOST),
> > > +     .extendables            = RCUTORTURE_MAX_EXTEND,
> > > +     .name                   = "rcu_lazy"
> > > +};
> > > +
> > > +#define LAZY_OPS &rcu_lazy_ops,
> > > +
> > > +#else // #ifdef CONFIG_RCU_LAZY
> > > +
> > > +#define LAZY_OPS
> > > +
> > > +#endif // #else #ifdef CONFIG_RCU_LAZY
> > > +
> > >
> > >  #ifdef CONFIG_TASKS_TRACE_RCU
> > >
> > > @@ -3145,7 +3203,7 @@ rcu_torture_init(void)
> > >       unsigned long gp_seq = 0;
> > >       static struct rcu_torture_ops *torture_ops[] = {
> > >               &rcu_ops, &rcu_busted_ops, &srcu_ops, &srcud_ops, &busted_srcud_ops,
> > > -             TASKS_OPS TASKS_RUDE_OPS TASKS_TRACING_OPS
> > > +             TASKS_OPS TASKS_RUDE_OPS TASKS_TRACING_OPS LAZY_OPS
> > >               &trivial_ops,
> > >       };
> > >
> > > diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE11 b/tools/testing/selftests/rcutorture/configs/rcu/TREE11
> > > new file mode 100644
> > > index 000000000000..436013f3e015
> > > --- /dev/null
> > > +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE11
> > > @@ -0,0 +1,18 @@
> > > +CONFIG_SMP=y
> > > +CONFIG_PREEMPT_NONE=n
> > > +CONFIG_PREEMPT_VOLUNTARY=n
> > > +CONFIG_PREEMPT=y
> > > +#CHECK#CONFIG_PREEMPT_RCU=y
> > > +CONFIG_HZ_PERIODIC=n
> > > +CONFIG_NO_HZ_IDLE=y
> > > +CONFIG_NO_HZ_FULL=n
> > > +CONFIG_RCU_TRACE=y
> > > +CONFIG_HOTPLUG_CPU=y
> > > +CONFIG_MAXSMP=y
> > > +CONFIG_CPUMASK_OFFSTACK=y
> > > +CONFIG_RCU_NOCB_CPU=y
> > > +CONFIG_DEBUG_LOCK_ALLOC=n
> > > +CONFIG_RCU_BOOST=n
> > > +CONFIG_DEBUG_OBJECTS_RCU_HEAD=n
> > > +CONFIG_RCU_EXPERT=y
> > > +CONFIG_RCU_LAZY=y
> > > diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE11.boot b/tools/testing/selftests/rcutorture/configs/rcu/TREE11.boot
> > > new file mode 100644
> > > index 000000000000..9b6f720d4ccd
> > > --- /dev/null
> > > +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE11.boot
> > > @@ -0,0 +1,8 @@
> > > +maxcpus=8 nr_cpus=43
> > > +rcutree.gp_preinit_delay=3
> > > +rcutree.gp_init_delay=3
> > > +rcutree.gp_cleanup_delay=3
> > > +rcu_nocbs=0-7
> > > +rcutorture.torture_type=rcu_lazy
> > > +rcutorture.nocbs_nthreads=8
> > > +rcutorture.fwd_progress=0
> > > --
> > > 2.37.0.rc0.161.g10f37bed90-goog
> > >
Joel Fernandes July 12, 2022, 9:15 p.m. UTC | #9
On 7/12/2022 4:58 PM, Paul E. McKenney wrote:
> On Tue, Jul 12, 2022 at 04:27:05PM -0400, Joel Fernandes wrote:
>> Ah, with all the threads, I missed this one :(. Sorry about that.
> 
> I know that feeling...
> 
>> On Fri, Jul 8, 2022 at 7:06 PM Paul E. McKenney <paulmck@kernel.org> wrote:
>>
>>>> Currently I added a test like the following which adds a new torture type, my
>>>> thought was to stress the new code to make sure nothing crashed or hung the
>>>> kernel. That is working well except I don't exactly understand the total-gps
>>>> print showing 0, which the other print shows 1188 GPs. I'll go dig into that
>>>> tomorrow.. thanks!
>>>>
>>>> The print shows
>>>> TREE11 ------- 1474 GPs (12.2833/s) [rcu_lazy: g0 f0x0 total-gps=0]
>>>> TREE11 no success message, 7 successful version messages
>>>
>>> Nice!!!  It is very good to see you correctly using the rcu_torture_ops
>>> facility correctly!
>>>
>>> And this could be good for your own testing, and I am happy to pull it
>>> in for that purpose (given it being fixed, having a good commit log,
>>> and so on).  After all, TREE10 is quite similar -- not part of CFLIST,
>>> but useful for certain types of focused testing.
>>>
>>> However, it would be very good to get call_rcu_lazy() testing going
>>> more generally, and in particular in TREE01 where offloading changes
>>> dynamically.  A good way to do this is to add a .call_lazy() component
>>> to the rcu_torture_ops structure, and check for it in a manner similar
>>> to that done for the .deferred_free() component.  Including adding a
>>> gp_normal_lazy module parameter.  This would allow habitual testing
>>> on a few scenarios and focused lazy testing on all of them via the
>>> --bootargs parameter.
>>
>> Ok, if you don't mind I will make this particular enhancement to the
>> torture test in a future patchset, since I kind of decided on doing v3
>> with just fixes to what I have and more testing. Certainly happy to
>> enhance these tests in a future version.
> 
> No need to gate v3 on those tests.
> 
>>> On the total-gps=0, the usual suspicion would be that the lazy callbacks
>>> never got invoked.  It looks like you were doing about a two-minute run,
>>> so maybe a longer run?  Though weren't they supposed to kick in at 15
>>> seconds or so?  Or did this value of zero come about because this run
>>> used exactly 300 grace periods?
>>
>> It was zero because it required the RCU_FLAVOR torture type, where as
>> my torture type was lazy. Adding RCU_LAZY_FLAVOR to the list fixed it
>> :)
> 
> Heh!  Then it didn't actually do any testing.  Done that as well!

Sorry to not be clear, I meant the switch-case list below, not the
torture list in rcutorture.c! It was in the rcutorture.c so was being
tested, just reporting zero gp_seq as I pointed.

/*
 * Send along grace-period-related data for rcutorture diagnostics.
 */
void rcutorture_get_gp_data(enum rcutorture_type test_type, int *flags,
                            unsigned long *gp_seq)
{
        switch (test_type) {
        case RCU_FLAVOR:
        case RCU_LAZY_FLAVOR:
                *flags = READ_ONCE(rcu_state.gp_flags);
                *gp_seq = rcu_seq_current(&rcu_state.gp_seq);
                break;
        default:
                break;
        }
}
EXPORT_SYMBOL_GPL(rcutorture_get_gp_data);
Paul E. McKenney July 12, 2022, 10:41 p.m. UTC | #10
On Tue, Jul 12, 2022 at 05:15:23PM -0400, Joel Fernandes wrote:
> 
> 
> On 7/12/2022 4:58 PM, Paul E. McKenney wrote:
> > On Tue, Jul 12, 2022 at 04:27:05PM -0400, Joel Fernandes wrote:
> >> Ah, with all the threads, I missed this one :(. Sorry about that.
> > 
> > I know that feeling...
> > 
> >> On Fri, Jul 8, 2022 at 7:06 PM Paul E. McKenney <paulmck@kernel.org> wrote:
> >>
> >>>> Currently I added a test like the following which adds a new torture type, my
> >>>> thought was to stress the new code to make sure nothing crashed or hung the
> >>>> kernel. That is working well except I don't exactly understand the total-gps
> >>>> print showing 0, which the other print shows 1188 GPs. I'll go dig into that
> >>>> tomorrow.. thanks!
> >>>>
> >>>> The print shows
> >>>> TREE11 ------- 1474 GPs (12.2833/s) [rcu_lazy: g0 f0x0 total-gps=0]
> >>>> TREE11 no success message, 7 successful version messages
> >>>
> >>> Nice!!!  It is very good to see you correctly using the rcu_torture_ops
> >>> facility correctly!
> >>>
> >>> And this could be good for your own testing, and I am happy to pull it
> >>> in for that purpose (given it being fixed, having a good commit log,
> >>> and so on).  After all, TREE10 is quite similar -- not part of CFLIST,
> >>> but useful for certain types of focused testing.
> >>>
> >>> However, it would be very good to get call_rcu_lazy() testing going
> >>> more generally, and in particular in TREE01 where offloading changes
> >>> dynamically.  A good way to do this is to add a .call_lazy() component
> >>> to the rcu_torture_ops structure, and check for it in a manner similar
> >>> to that done for the .deferred_free() component.  Including adding a
> >>> gp_normal_lazy module parameter.  This would allow habitual testing
> >>> on a few scenarios and focused lazy testing on all of them via the
> >>> --bootargs parameter.
> >>
> >> Ok, if you don't mind I will make this particular enhancement to the
> >> torture test in a future patchset, since I kind of decided on doing v3
> >> with just fixes to what I have and more testing. Certainly happy to
> >> enhance these tests in a future version.
> > 
> > No need to gate v3 on those tests.
> > 
> >>> On the total-gps=0, the usual suspicion would be that the lazy callbacks
> >>> never got invoked.  It looks like you were doing about a two-minute run,
> >>> so maybe a longer run?  Though weren't they supposed to kick in at 15
> >>> seconds or so?  Or did this value of zero come about because this run
> >>> used exactly 300 grace periods?
> >>
> >> It was zero because it required the RCU_FLAVOR torture type, where as
> >> my torture type was lazy. Adding RCU_LAZY_FLAVOR to the list fixed it
> >> :)
> > 
> > Heh!  Then it didn't actually do any testing.  Done that as well!
> 
> Sorry to not be clear, I meant the switch-case list below, not the
> torture list in rcutorture.c! It was in the rcutorture.c so was being
> tested, just reporting zero gp_seq as I pointed.
> 
> /*
>  * Send along grace-period-related data for rcutorture diagnostics.
>  */
> void rcutorture_get_gp_data(enum rcutorture_type test_type, int *flags,
>                             unsigned long *gp_seq)
> {
>         switch (test_type) {
>         case RCU_FLAVOR:
>         case RCU_LAZY_FLAVOR:
>                 *flags = READ_ONCE(rcu_state.gp_flags);
>                 *gp_seq = rcu_seq_current(&rcu_state.gp_seq);
>                 break;
>         default:
>                 break;
>         }
> }
> EXPORT_SYMBOL_GPL(rcutorture_get_gp_data);

Ah, that would do it!  Thank you for the clarification.

							Thanx, Paul
diff mbox series

Patch

diff --git a/kernel/rcu/rcu.h b/kernel/rcu/rcu.h
index 71c0f45e70c3..436faf80a66b 100644
--- a/kernel/rcu/rcu.h
+++ b/kernel/rcu/rcu.h
@@ -473,6 +473,12 @@  void do_trace_rcu_torture_read(const char *rcutorturename,
 			       unsigned long c);
 void rcu_gp_set_torture_wait(int duration);
 void rcu_force_call_rcu_to_lazy(bool force);
+
+#if IS_ENABLED(CONFIG_RCU_SCALE_TEST)
+unsigned long rcu_scale_get_jiffies_till_flush(void);
+void rcu_scale_set_jiffies_till_flush(unsigned long j);
+#endif
+
 #else
 static inline void rcutorture_get_gp_data(enum rcutorture_type test_type,
 					  int *flags, unsigned long *gp_seq)
diff --git a/kernel/rcu/rcuscale.c b/kernel/rcu/rcuscale.c
index 277a5bfb37d4..58ee5c2cb37b 100644
--- a/kernel/rcu/rcuscale.c
+++ b/kernel/rcu/rcuscale.c
@@ -95,6 +95,7 @@  torture_param(int, verbose, 1, "Enable verbose debugging printk()s");
 torture_param(int, writer_holdoff, 0, "Holdoff (us) between GPs, zero to disable");
 torture_param(int, kfree_rcu_test, 0, "Do we run a kfree_rcu() scale test?");
 torture_param(int, kfree_mult, 1, "Multiple of kfree_obj size to allocate.");
+torture_param(int, kfree_rcu_by_lazy, 0, "Use call_rcu_lazy() to emulate kfree_rcu()?");
 
 static char *scale_type = "rcu";
 module_param(scale_type, charp, 0444);
@@ -658,6 +659,13 @@  struct kfree_obj {
 	struct rcu_head rh;
 };
 
+/* Used if doing RCU-kfree'ing via call_rcu_lazy(). */
+void kfree_rcu_lazy(struct rcu_head *rh)
+{
+	struct kfree_obj *obj = container_of(rh, struct kfree_obj, rh);
+	kfree(obj);
+}
+
 static int
 kfree_scale_thread(void *arg)
 {
@@ -695,6 +703,11 @@  kfree_scale_thread(void *arg)
 			if (!alloc_ptr)
 				return -ENOMEM;
 
+			if (kfree_rcu_by_lazy) {
+				call_rcu_lazy(&(alloc_ptr->rh), kfree_rcu_lazy);
+				continue;
+			}
+
 			// By default kfree_rcu_test_single and kfree_rcu_test_double are
 			// initialized to false. If both have the same value (false or true)
 			// both are randomly tested, otherwise only the one with value true
@@ -737,6 +750,9 @@  kfree_scale_cleanup(void)
 {
 	int i;
 
+	if (kfree_rcu_by_lazy)
+		rcu_force_call_rcu_to_lazy(false);
+
 	if (torture_cleanup_begin())
 		return;
 
@@ -766,11 +782,55 @@  kfree_scale_shutdown(void *arg)
 	return -EINVAL;
 }
 
+// Used if doing RCU-kfree'ing via call_rcu_lazy().
+unsigned long jiffies_at_lazy_cb;
+struct rcu_head lazy_test1_rh;
+int rcu_lazy_test1_cb_called;
+void call_rcu_lazy_test1(struct rcu_head *rh)
+{
+	jiffies_at_lazy_cb = jiffies;
+	WRITE_ONCE(rcu_lazy_test1_cb_called, 1);
+}
+
 static int __init
 kfree_scale_init(void)
 {
 	long i;
 	int firsterr = 0;
+	unsigned long orig_jif, jif_start;
+
+	// Force all call_rcu() to call_rcu_lazy() so that non-lazy CBs
+	// do not remove laziness of the lazy ones (since the test tries
+	// to stress call_rcu_lazy() for OOM).
+	//
+	// Also, do a quick self-test to ensure laziness is as much as
+	// expected.
+	if (kfree_rcu_by_lazy) {
+		/* do a test to check the timeout. */
+		orig_jif = rcu_scale_get_jiffies_till_flush();
+
+		rcu_force_call_rcu_to_lazy(true);
+		rcu_scale_set_jiffies_till_flush(2 * HZ);
+		rcu_barrier();
+
+		jif_start = jiffies;
+		jiffies_at_lazy_cb = 0;
+		call_rcu_lazy(&lazy_test1_rh, call_rcu_lazy_test1);
+
+		smp_cond_load_relaxed(&rcu_lazy_test1_cb_called, VAL == 1);
+
+		rcu_scale_set_jiffies_till_flush(orig_jif);
+
+		if (WARN_ON_ONCE(jiffies_at_lazy_cb - jif_start < 2 * HZ)) {
+			pr_alert("Lazy CBs are not being lazy as expected!\n");
+			return -1;
+		}
+
+		if (WARN_ON_ONCE(jiffies_at_lazy_cb - jif_start > 3 * HZ)) {
+			pr_alert("Lazy CBs are being too lazy!\n");
+			return -1;
+		}
+	}
 
 	kfree_nrealthreads = compute_real(kfree_nthreads);
 	/* Start up the kthreads. */
@@ -783,7 +843,9 @@  kfree_scale_init(void)
 		schedule_timeout_uninterruptible(1);
 	}
 
-	pr_alert("kfree object size=%zu\n", kfree_mult * sizeof(struct kfree_obj));
+	pr_alert("kfree object size=%zu, kfree_rcu_by_lazy=%d\n",
+			kfree_mult * sizeof(struct kfree_obj),
+			kfree_rcu_by_lazy);
 
 	kfree_reader_tasks = kcalloc(kfree_nrealthreads, sizeof(kfree_reader_tasks[0]),
 			       GFP_KERNEL);
diff --git a/kernel/rcu/tree_nocb.h b/kernel/rcu/tree_nocb.h
index b481f1ea57c0..255f2945b0fc 100644
--- a/kernel/rcu/tree_nocb.h
+++ b/kernel/rcu/tree_nocb.h
@@ -257,6 +257,21 @@  static bool wake_nocb_gp(struct rcu_data *rdp, bool force)
 }
 
 #define LAZY_FLUSH_JIFFIES (10 * HZ)
+unsigned long jiffies_till_flush = LAZY_FLUSH_JIFFIES;
+
+#ifdef CONFIG_RCU_SCALE_TEST
+void rcu_scale_set_jiffies_till_flush(unsigned long jif)
+{
+	jiffies_till_flush = jif;
+}
+EXPORT_SYMBOL(rcu_scale_set_jiffies_till_flush);
+
+unsigned long rcu_scale_get_jiffies_till_flush(void)
+{
+	return jiffies_till_flush;
+}
+EXPORT_SYMBOL(rcu_scale_get_jiffies_till_flush);
+#endif
 
 /*
  * Arrange to wake the GP kthread for this NOCB group at some future
@@ -275,7 +290,7 @@  static void wake_nocb_gp_defer(struct rcu_data *rdp, int waketype,
 	 * of callback storm, no need to wake up too early.
 	 */
 	if (waketype == RCU_NOCB_WAKE_LAZY) {
-		mod_timer(&rdp_gp->nocb_timer, jiffies + LAZY_FLUSH_JIFFIES);
+		mod_timer(&rdp_gp->nocb_timer, jiffies + jiffies_till_flush);
 		WRITE_ONCE(rdp_gp->nocb_defer_wakeup, waketype);
 	} else if (waketype == RCU_NOCB_WAKE_BYPASS) {
 		mod_timer(&rdp_gp->nocb_timer, jiffies + 2);