Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

up_squared: slowdown on test execution and timing out on multiple tests #30573

Closed
jenmwms opened this issue Dec 10, 2020 · 11 comments · Fixed by #30715
Closed

up_squared: slowdown on test execution and timing out on multiple tests #30573

jenmwms opened this issue Dec 10, 2020 · 11 comments · Fixed by #30715
Assignees
Labels
area: SMP Symmetric multiprocessing area: X86 x86 Architecture (32-bit) bug The issue is a bug, or the PR is fixing a bug priority: high High impact/importance bug

Comments

@jenmwms
Copy link
Collaborator

jenmwms commented Dec 10, 2020

Describe the bug
There is a regression in some of the tests for up2 in which the tests are taking longer to execute than previously, to the extent that sanitycheck gives a timeout error. Sanitycheck is not an issue here, it is working properly. The issue here is the significant increase in time of test execution on these test suites. This is observed on HW (up2). On emulator (qemu) the tests work well during default time and have no noticeable change.

What have you tried to diagnose or workaround this issue?
A longer timeout can be specified in the testcase.yml. But the workaround is not scalable (#30374). This will pass if sanitycheck is informed to wait e.g. 120-320 seconds for the test to complete, especially sys_put (e.g. test_sys_put_le24 took 1 minute 52 seconds approx.)

This occurs in the following:

  • tests/kernel/common (all of the test_sys_put_)
  • tests/kernel/mem_protect/mem_protect (including test_permission_inheritance, test_mem_domain_remove_add_partition)
  • tests/kernel/device (pm - test_dummy_device)

To Reproduce
Compare the output for:

  1. Use sanitycheck to run tests/kernel/common
  2. Use west to build and flash tests/kernel/common
  • west build -p -b up_squared zephyr/tests/kernel/common/ -DCONFIG_THREAD_LOCAL_STORAGE=y
  • use a stopwatch when a test in the suite appears to hang after START (this is the longer test execution)

Expected behavior
No significant change in test execution time that would yield a sanitycheck timeout error. The default time for sanitycheck on these tests should be sufficient.

Impact
The increased test execution time is exceeding the default timeout used by sanitycheck so a timeout error is generated for those tests. Due to the scope of these tests, it is high impact because the PASS/FAIL of the individual tests in the test suite cannot be checked in CI making it harder to detect further regression without using workaround or alternative.

Logs and console output
Using sanitycheck without the workaround, the issue is observed as timeout errors when the test appears to "freeze"/hang. The results are clipped, it's marked as an error, and moves on to the next test.

*** Booting Zephyr OS build zephyr-v2.4.0-1894-g04a421d1b86f  (delayed boot 500ms) ***
Running test suite common
===================================================================
START - test_bootdelay
 PASS - test_bootdelay
===================================================================
START - test_irq_offload
 PASS - test_irq_offload
===================================================================
START - test_byteorder_memcpy_swap
 PASS - test_byteorder_memcpy_swap
===================================================================
START - test_byteorder_mem_swap
 PASS - test_byteorder_mem_swap
===================================================================
START - test_sys_get_be64
 PASS - test_sys_get_be64
===================================================================
START - test_sys_put_be64

Using the workaround for an extended timeout for sanitycheck to use, or using west method to give the test the time it needs to complete:

*** Booting Zephyr OS build zephyr-v2.4.0-1894-g04a421d1b86f  (delayed boot 500ms) ***
Running test suite common
===================================================================
START - test_bootdelay
 PASS - test_bootdelay
===================================================================
START - test_irq_offload
 PASS - test_irq_offload
===================================================================
START - test_byteorder_memcpy_swap
 PASS - test_byteorder_memcpy_swap
===================================================================
START - test_byteorder_mem_swap
 PASS - test_byteorder_mem_swap
===================================================================
START - test_sys_get_be64
 PASS - test_sys_get_be64
===================================================================
START - test_sys_put_be64

Became frozen for ~2 minutes
then resumed

 PASS - test_sys_put_be64
==========================
START - test_sys_get_be48
 PASS - test_sys_get_be48
===================================================================
START - test_sys_put_be48
 PASS - test_sys_put_be48
===================================================================
START - test_sys_get_be32
 PASS - test_sys_get_be32
===================================================================
START - test_sys_put_be32
 PASS - test_sys_put_be32
===================================================================
START - test_sys_get_be24
 PASS - test_sys_get_be24
===================================================================
START - test_sys_put_be24
 PASS - test_sys_put_be24
===================================================================
START - test_sys_get_be16
 PASS - test_sys_get_be16
===================================================================
START - test_sys_put_be16
 PASS - test_sys_put_be16
===================================================================
START - test_sys_get_le16
 PASS - test_sys_get_le16
===================================================================
START - test_sys_put_le16
 PASS - test_sys_put_le16
===================================================================
START - test_sys_get_le24
 PASS - test_sys_get_le24
===================================================================
START - test_sys_put_le24

Became frozen for ~2 minutes
then resumed

 PASS - test_sys_put_le24
===================================================================
START - test_sys_get_le32
 PASS - test_sys_get_le32
===================================================================
START - test_sys_put_le32

Became frozen for ~2 minutes
then resumed

 PASS - test_sys_put_le32
===================================================================
START - test_sys_get_le48
 PASS - test_sys_get_le48
===================================================================
START - test_sys_put_le48
 PASS - test_sys_put_le48
===================================================================
START - test_sys_get_le64
 PASS - test_sys_get_le64
===================================================================
START - test_sys_put_le64

Became frozen for ~2 minutes
then resumed

PASS - test_sys_put_le64
===================================================================
START - test_atomic
 PASS - test_atomic
===================================================================
START - test_bitfield
twiddling bits....
 PASS - test_bitfield
===================================================================
START - test_printk
22 113 10000 32768 40000 22
p 112 -10000 -32768 -40000 -22
0x1 0x01 0x0001 0x00000001 0x0000000000000001
0x1 0x 1 0x   1 0x       1
42 42 0042 00000042
-42 -42 -042 -0000042
42 42   42       42
42 42 0042 00000042
255     42    abcdef        42
68719476735 -1 18446744073709551615 ffffffffffffffff
0xcafebabe 0xbeef 0x2a
 PASS - test_printk
===================================================================
START - test_timeout_order
thread 0 synced on timer 0
thread 1 synced on timer 1
thread 2 synced on timer 2
 PASS - test_timeout_order
===================================================================
START - test_clock_uptime
 PASS - test_clock_uptime
===================================================================
START - test_clock_cycle
 PASS - test_clock_cycle
===================================================================
START - test_version
 PASS - test_version
===================================================================
START - test_multilib

Became frozen for ~2 minutes
then resumed

 PASS - test_multilib
===================================================================
START - test_thread_context
 PASS - test_thread_context
===================================================================
START - test_ms_time_duration
 PASS - test_ms_time_duration
===================================================================
START - test_bounds_check_mitigation
 PASS - test_bounds_check_mitigation
===================================================================
Test suite common succeeded
===================================================================
PROJECT EXECUTION SUCCESSFUL

Environment (please complete the following information):

  • OS: Linux
  • Toolchain Zephyr SDK 0.11.4
  • c08ef38

Additional context
Slow down on test execution started with this PR: #29411.
Works fine up to this commit: 86cfd90 from the above PR, then the issue starts.
For tests/kernel/common/, the issue starts at b8242bf.
For tests/kernel/mem_protect/mem_protect/, the test suite completes quickly back at b8242bf, then next commit 1367c4a we start seeing it take longer for test_permission_inheritance.

Now consolidated here, this issue was originally captured in separate bugs by test suite:
#30275
#30305
#30306
#30307

@jenmwms jenmwms added bug The issue is a bug, or the PR is fixing a bug priority: high High impact/importance bug area: X86 x86 Architecture (32-bit) labels Dec 10, 2020
@jenmwms jenmwms self-assigned this Dec 10, 2020
@jenmwms
Copy link
Collaborator Author

jenmwms commented Dec 10, 2020

@nashif @andrewboie - fyi
@chen-png - please help me get a better log for sanitycheck results

@andrewboie
Copy link
Contributor

is it consistent where it hangs for ~2 minutes or does the place vary from run to run?

@dcpleung
Copy link
Member

On my UP2, it pauses in different places between reboots.

@andrewboie
Copy link
Contributor

andrewboie commented Dec 10, 2020

some things to try:

  • does this reproduce if other CPU cores are disabled (set CONFIG_MP_NUM_CPUS=1 in defconfig)
  • get the GDB stub that @ceolin implemented going and stop execution in a pause period. what is the system doing in each active core?
  • if you can't get GDB going, put log statements at entry/exit for the arch_* APIs in x86_mmu.c to see if it's getting stuck in any of them or functions they call
  • modify DTS to reduce system RAM size from its current 2GB value to something much smaller like 8MB. does this affect the duration of the pauses?
  • does this reproduce on up_squared_32 (which is unprocessor-only) or just on the 64-bit build?

My suspicion at this time is SMP, but we need to gather more data. The previous issue that @dcpleung isolated was a lengthy period to instantiate page tables for 2GB of RAM on thread creation which no longer happens.

@dcpleung
Copy link
Member

dcpleung commented Dec 10, 2020

  1. Works fine without pauses when doing CONFIG_MP_NUM_CPUS=1.
  2. GDB stub does not support x86_64 so no go there.
  3. Still an issue after changing memory size to 32MB.
  4. up_squared_32 doesn't boot on my board so can't test there. (probably due to serial port not discovered/configured correctly)
  5. Works fine after disabling userspace.
  6. With some printk(), it seems to get stuck at trying to join ztest_thread: k_thread_join()->z_swap()->do_swap()->arch_switch()==z_x86_switch(). Since z_x86_switch() is in assembly, can't add printk() there any more.

@andrewboie
Copy link
Contributor

@dcpleung thanks this is really helpful. OK, I think this may be some race related to SMP and the recent changes I made to have self-exiting threads do their cleanup tasks in the context of the idle thread.

With some printk(), it seems to get stuck at trying to join ztest_thread: k_thread_join()->z_swap()->do_swap()->arch_switch()==z_x86_switch(). Since z_x86_switch() is in assembly, can't add printk() there any more.

This looks like the expected code path where ztest calls k_thread_join() on the target ztest thread, and since it is still running when it gets invoked in run_test() in ztest.c, it blocks until the thread exits and it gets woken up:

static int run_test(struct unit_test *test)
{
	int ret = TC_PASS;

	TC_START(test->name);
	k_thread_create(&ztest_thread, ztest_thread_stack,
			K_THREAD_STACK_SIZEOF(ztest_thread_stack),
			(k_thread_entry_t) test_cb, (struct unit_test *)test,
			NULL, NULL, CONFIG_ZTEST_THREAD_PRIORITY,
			test->thread_options | K_INHERIT_PERMS,
				K_NO_WAIT);

	k_thread_name_set(&ztest_thread, "ztest_thread");
	k_thread_join(&ztest_thread, K_FOREVER);

Problem is, it's not being woken up in a timely manner. The self-abort logic is as follows:

FUNC_NORETURN void z_self_abort(void)
{
	/* Self-aborting threads don't clean themselves up, we
	 * have the idle thread for the current CPU do it.
	 */
	int key;
	struct _cpu *cpu;

	/* Lock local IRQs to prevent us from migrating to another CPU
	 * while we set this up
	 */
	key = arch_irq_lock();
	cpu = _current_cpu;
	__ASSERT(cpu->pending_abort == NULL, "already have a thread to abort");
	cpu->pending_abort = _current;

	LOG_DBG("%p self-aborting, handle on idle thread %p",
		_current, cpu->idle_thread);

	k_thread_suspend(_current);
	z_swap_irqlock(key);
	__ASSERT(false, "should never get here");
	CODE_UNREACHABLE;
}

What's supposed to happen is that immediately after we set cpu->pending_abort, self-suspend, and swap out, is that the idle thread should be scheduled next.

void z_impl_k_thread_suspend(struct k_thread *thread)
{
	(void)z_abort_thread_timeout(thread);

	LOCKED(&sched_spinlock) {
		if (z_is_thread_queued(thread)) {
			_priq_run_remove(&_kernel.ready_q.runq, thread);
			z_mark_thread_as_not_queued(thread);
		}
		z_mark_thread_as_suspended(thread);
		update_cache(thread == _current);
	}

	if (thread == _current) {
		z_reschedule_unlocked();
	}
}

The k_thread_suspend() logic suspends the current thread and then calls update_cache(true):

static void update_cache(int preempt_ok)
{
#ifndef CONFIG_SMP
	struct k_thread *thread = next_up();

	if (should_preempt(thread, preempt_ok)) {
#ifdef CONFIG_TIMESLICING
		if (thread != _current) {
			z_reset_time_slice();
		}
#endif
		update_metairq_preempt(thread);
		_kernel.ready_q.cache = thread;
	} else {
		_kernel.ready_q.cache = _current;
	}

#else
	/* The way this works is that the CPU record keeps its
	 * "cooperative swapping is OK" flag until the next reschedule
	 * call or context switch.  It doesn't need to be tracked per
	 * thread because if the thread gets preempted for whatever
	 * reason the scheduler will make the same decision anyway.
	 */
	_current_cpu->swap_ok = preempt_ok;
#endif
}

Which calls next_up(), which always returns the idle thread object to run if _current_cpu->pending_abort is set:

static ALWAYS_INLINE struct k_thread *next_up(void)
{
	struct k_thread *thread;

	/* If a thread self-aborted we need the idle thread to clean it up
	 * before any other thread can run on this CPU
	 */
	if (_current_cpu->pending_abort != NULL) {
		return _current_cpu->idle_thread;
	}

But what seems to be happening is that the idle thread is not immediately running afterwards. Instead some time passes and THEN it runs, probably due to some other event. So there may be some problem/race condition in how the offload-to-idle scheduling is implemented with SMP.

I think this is a scheduling problem and likely orthogonal to user mode or recent paging changes which might just be making races turn out differently.

@andrewboie
Copy link
Contributor

Oh wow I think I see it.

If SMP is enabled, update_cache() does NOT call next_up(). That's an ifndef not an ifdef. We need to do something else to ensure that idle thread temporarily preempts everything else on this CPU.

@andrewboie
Copy link
Contributor

Hmm but that's not the whole story. In the SMP case, we eventually get into do_swap() in kswap.h. This invokes z_get_ready_thread():

static ALWAYS_INLINE unsigned int do_swap(unsigned int key,
					  struct k_spinlock *lock,
					  int is_spinlock)
{
	ARG_UNUSED(lock);
	struct k_thread *new_thread, *old_thread;

	old_thread = _current;

	z_check_stack_sentinel();

	if (is_spinlock) {
		k_spin_release(lock);
	}

#ifdef CONFIG_SMP
	/* Null out the switch handle, see wait_for_switch() above.
	 * Note that we set it back to a non-null value if we are not
	 * switching!  The value itself doesn't matter, because by
	 * definition _current is running and has no saved state.
	 */
	volatile void **shp = (void *)&old_thread->switch_handle;

	*shp = NULL;
#endif

	new_thread = z_get_next_ready_thread();

And that is implemented to call next_up():

#ifdef CONFIG_SMP
struct k_thread *z_get_next_ready_thread(void)
{
	struct k_thread *ret = 0;

	LOCKED(&sched_spinlock) {
		ret = next_up();
	}

	return ret;
}
#endif

So it should get the idle thread back, and that should be the next thing to run. Except apparently it doesn't sometimes.

@andrewboie
Copy link
Contributor

I would not be surprised if this issue has a relationship to #28105 which has been confounding us for some time.

@andrewboie andrewboie added the area: SMP Symmetric multiprocessing label Dec 11, 2020
@nashif
Copy link
Member

nashif commented Dec 11, 2020

here another issue most likely caused by this bug: #30574, still fails with SMP disabled.

@dcpleung
Copy link
Member

Note that #30574 is caused by CONFIG_INTEL_VTD_ICTL being enabled, so not the same issue.

@nashif nashif assigned dcpleung and unassigned jenmwms Dec 14, 2020
dcpleung added a commit to dcpleung/zephyr that referenced this issue Dec 14, 2020
Inside the idle loop, in some configuration, IRQ is unlocked and
then immediately locked again. There is a side effect:

1. IRQ is unlocked in middle of the loop.
2. Another thread (A) can now run so idle thread is un-scheduled.
3. Thread A runs to its end and going through the thread
   self-abort path.
4. Idle thread is rescheduled again, and continues to run
   the remaining loop when it eventuall calls k_cpu_idle().
   The "pending abort" path is not being executed on thread A
   at this point.
5. Now, thread A is suspended, and the CPU is in idle waiting
   for interrupts (e.g. timeouts).
6. Thread B is waiting to join on thread A. Since thread A has
   not been terminated yet so thread B is waiting until
   the idle thread runs again and starts executing from
   the beginning of while loop.
7. Depending on how many threads are running and how active
   the platform is, idle thread may not run again for a while,
   resulting in thread B appearing to be stuck.

To avoid this situation, the unlock/lock pair in middle of
the loop is removed so no rescheduling can be done mid-loop.
When there is no thread abort pending, it simply locks IRQ
and calls k_cpu_idle(). This is almost identical to the idle
loop before the thread abort code was introduced (except
the check for cpu->pending_abort).

Fixes zephyrproject-rtos#30573

Signed-off-by: Daniel Leung <[email protected]>
nashif pushed a commit that referenced this issue Dec 15, 2020
Inside the idle loop, in some configuration, IRQ is unlocked and
then immediately locked again. There is a side effect:

1. IRQ is unlocked in middle of the loop.
2. Another thread (A) can now run so idle thread is un-scheduled.
3. Thread A runs to its end and going through the thread
   self-abort path.
4. Idle thread is rescheduled again, and continues to run
   the remaining loop when it eventuall calls k_cpu_idle().
   The "pending abort" path is not being executed on thread A
   at this point.
5. Now, thread A is suspended, and the CPU is in idle waiting
   for interrupts (e.g. timeouts).
6. Thread B is waiting to join on thread A. Since thread A has
   not been terminated yet so thread B is waiting until
   the idle thread runs again and starts executing from
   the beginning of while loop.
7. Depending on how many threads are running and how active
   the platform is, idle thread may not run again for a while,
   resulting in thread B appearing to be stuck.

To avoid this situation, the unlock/lock pair in middle of
the loop is removed so no rescheduling can be done mid-loop.
When there is no thread abort pending, it simply locks IRQ
and calls k_cpu_idle(). This is almost identical to the idle
loop before the thread abort code was introduced (except
the check for cpu->pending_abort).

Fixes #30573

Signed-off-by: Daniel Leung <[email protected]>
bwasim pushed a commit to bwasim/zephyr that referenced this issue Jan 5, 2021
Inside the idle loop, in some configuration, IRQ is unlocked and
then immediately locked again. There is a side effect:

1. IRQ is unlocked in middle of the loop.
2. Another thread (A) can now run so idle thread is un-scheduled.
3. Thread A runs to its end and going through the thread
   self-abort path.
4. Idle thread is rescheduled again, and continues to run
   the remaining loop when it eventuall calls k_cpu_idle().
   The "pending abort" path is not being executed on thread A
   at this point.
5. Now, thread A is suspended, and the CPU is in idle waiting
   for interrupts (e.g. timeouts).
6. Thread B is waiting to join on thread A. Since thread A has
   not been terminated yet so thread B is waiting until
   the idle thread runs again and starts executing from
   the beginning of while loop.
7. Depending on how many threads are running and how active
   the platform is, idle thread may not run again for a while,
   resulting in thread B appearing to be stuck.

To avoid this situation, the unlock/lock pair in middle of
the loop is removed so no rescheduling can be done mid-loop.
When there is no thread abort pending, it simply locks IRQ
and calls k_cpu_idle(). This is almost identical to the idle
loop before the thread abort code was introduced (except
the check for cpu->pending_abort).

Fixes zephyrproject-rtos#30573

Signed-off-by: Daniel Leung <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: SMP Symmetric multiprocessing area: X86 x86 Architecture (32-bit) bug The issue is a bug, or the PR is fixing a bug priority: high High impact/importance bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants