FelipeMdeO opened a new pull request, #20047:
URL: https://github.com/apache/nuttx/pull/20047

   ## Summary
   
   `apps/testing/ostest`'s full suite hangs or crashes under
   `CONFIG_SCHED_TICKLESS` due to two separate, generic (arch-independent)
   scheduler bugs. Both are required together to get the suite passing
   end-to-end under tickless.
   
   * `sched/sched_addreadytorun.c`: `nxsched_reassess_timer()` (which
     arms the round-robin timeslice timer) was only ever called from
     `sched_unlock.c`, gated on the task that *was* running having
     already exhausted its timeslice. Nothing armed the timer when a
     *new* `SCHED_RR` task started running via an ordinary context
     switch, so under tickless (no periodic tick to fall back on) two
     same-priority `SCHED_RR` threads never interleaved -- one just ran
     to completion before the other's first iteration was recorded.
   * `sched/wdog/wdog.h`: `wd_timer_start()` computed the delay to the
     next watchdog expiration with no clamp. An already-due watchdog
     (common with back-to-back short-delay `wd_start()`/`wd_cancel()`
     calls) made that delta go negative -- which arch code's
     `NSEC_2_CTICK()`-style macros then cast to unsigned, turning
     "already due" into a multi-year alarm that never fires. Clamping to
     *zero* isn't sufficient either: oneshot hardware comparators (e.g.
     the ESP32-S3 systimer) only interrupt on the counter *crossing* the
     target from below, so arming for "right now" can miss the edge and
     never fire at all.
   
   Neither bug is specific to any one chip -- both are in generic
   `sched/` code reachable by any architecture combining
   `CONFIG_SCHED_TICKLESS` with `CONFIG_RR_INTERVAL > 0` (round-robin
   bug) or any watchdog user issuing short/zero delays (wdog bug). The
   round-robin bug is additionally confirmed on 3 independent tickless
   backends across 2 architectures (Xtensa ESP32-S3, RISC-V ESP32-C3
   legacy and shared backends).
   
   The in-tree `boards/xtensa/esp32s3/esp32s3-devkit/configs/tickless` example
   (`CONFIG_ESP32S3_TICKLESS` + `CONFIG_TESTING_OSTEST`, both already
   enabled by default in that defconfig) reproduces the round-robin crash
   immediately and deterministically on real hardware -- see the
   Reproduction and Testing sections below.
   
   ## Impact
   
   * Impact on hardware (will arch(s) / board(s) / driver(s) change)? 
   **YES -- fixes generic `sched/` code exercised by any board using 
`CONFIG_SCHED_TICKLESS`; no board/driver files touched.**
   
   * Anything else to consider or add? The `CONFIG_SMP` path of the round-robin 
fix (`nxsched_switch_running()`) is intentionally **not** addressed here -- no 
SMP hardware was available to validate against. Flagging for maintainer input 
on whether that gap should block this PR or be tracked separately.
   
   ## Testing
   
   I confirm that changes are verified on local setup and works as intended:
   
   * Build Host(s): Ubuntu 24.04.4 LTS, x86_64, GCC (`xtensa-esp-elf-gcc`, 
crosstool-NG esp-14.2.0_20241119, 14.2.0).
   * Target(s):
     * `xtensa (esp32s3-xiao)`: 
`boards/xtensa/esp32s3/esp32s3-xiao/configs/usbnsh` +
       `CONFIG_ESP32S3_TICKLESS=y` + `CONFIG_TESTING_OSTEST=y` 
(`kconfig-tweak`).
     * `xtensa (esp32s3-devkit)`: in-tree `esp32s3-devkit:tickless` example 
config, unmodified.
     * `riscv (esp32c3-legacy-devkit)`: in-tree `:tickless` config (own 
`esp32c3_tickless.c` backend) -- round-robin fix only.
     * `riscv (esp32c3-devkit)`: in-tree `:tickless` config (shared 
`esp_tickless.c` backend) -- round-robin fix only.
   
   ### How to reproduce (before this fix)
   
   Simplest repro uses only in-tree, unmodified files -- no board-specific
   changes needed:
   
   ```
   ./tools/configure.sh esp32s3-devkit:tickless
   make -j$(nproc)
   # flash nuttx.bin, open the console, then at the nsh> prompt:
   nsh> ostest
   ```
   
   (Any board with `CONFIG_ESP32S3_TICKLESS`/equivalent tickless backend
   + `CONFIG_TESTING_OSTEST` + `CONFIG_RR_INTERVAL > 0` reproduces the
   round-robin crash identically -- this is just the simplest way to get
   there with zero custom config, since it's an existing in-tree example.)
   
   **Console output before the fix** (`esp32s3-devkit:tickless`, stock 
`master`):
   
   ```
   user_main: round-robin scheduler test
   rr_test: Set thread priority to 1
   rr_test: Set thread policy to SCHED_RR
   rr_test: Starting first get_primes_thread
            First get_primes_thread: 88
   rr_test: Starting second get_primes_thread
            Second get_primes_thread: 89
   rr_test: Waiting for threads to complete -- this should take awhile
            If RR scheduling is working, they should start and complete at
            about the same time
   get_primes_thread id=1 started, looking for primes < 30000, doing 10 run(s)
   get_primes_thread id=1 finished, found 3246 primes, last one was 29989
   get_primes_thread id=2 started, looking for primes < 30000, doing 10 run(s)
   get_primes_thread id=2 finished, found 3246 primes, last one was 29989
   rr_test: Roundrobin Failed
   dump_assert_info: Current Version: NuttX  13.0.1-RC0 f595ba31e2 Sep  1 2026 
19:54:51 xtensa
   dump_assert_info: Assertion failed : at file: :0 task: ostest process: 
ostest 0x420239f0
   up_dump_register:    PC: 42022bc1    PS: 00060523
   up_dump_register:    A0: 80376c78    A1: 3fc98bd0    A2: 00000000    A3: 
3fc8d9f0
   [... full register + stack dump omitted ...]
   ostest_main: Exiting with status 256
   ```
   
   Note `id=1` fully finishing (`found 3246 primes`) before `id=2` even
   *starts* -- the two same-priority `SCHED_RR` threads never
   interleaved, so `rr_test`'s own pass check
   (`apps/testing/ostest/roundrobin.c:263-280`, which requires a thread
   switch within the first `CONFIG_TESTING_OSTEST_RR_RUNS` entries of
   `g_rr_values[]`) fails and hits `ASSERT(false)`.
   
   The watchdog bug (bug 2/2 in this PR) is timing-dependent and didn't
   trigger on that particular `esp32s3-devkit` run, but reliably hung
   `apps/testing/ostest/wdog.c`'s `wdog_test()` forever on
   `esp32s3-xiao` (confirmed via a temporary progress printf that every
   one of its 4 threads got stuck on the very first iteration of
   `wdtest_rand()`'s busy-wait for its own watchdog callback -- the
   callback never fired).
   
   ### After this fix
   
   **Full, unmodified `apps/testing/ostest` suite, `esp32s3-xiao`,
   `CONFIG_SCHED_TICKLESS` + `CONFIG_TESTING_OSTEST`, both commits
   applied -- runs to completion with no hangs, no failed asserts:**
   
   ```
   user_main: wdog test
   wdog_test start...
   [... 4 threads x wdtest_once/wdtest_recursive/wdtest_rand, all complete ...]
   wdog_test end...
   
   End of test memory usage:
   VARIABLE  BEFORE   AFTER
   ======== ======== ========
   arena       5de60    5de60
   ...
   
   user_main: round-robin scheduler test
   rr_test: Set thread priority to 1
   rr_test: Set thread policy to SCHED_RR
   rr_test: Starting first get_primes_thread
            First get_primes_thread: 249
   rr_test: Starting second get_primes_thread
            Second get_primes_thread: 250
   rr_test: Waiting for threads to complete -- this should take awhile
            If RR scheduling is working, they should start and complete at
            about the same time
   get_primes_thread id=1 started, looking for primes < 30000, doing 10 run(s)
   get_primes_thread id=2 started, looking for primes < 30000, doing 10 run(s)
   get_primes_thread id=1 finished, found 3246 primes, last one was 29989
   get_primes_thread id=2 finished, found 3246 primes, last one was 29989
   rr_test: Done
   
   [... sporadic, dual sporadic, barrier (8 threads), setjmp, priority
        inheritance, scheduler lock, smp call, nxevent, perf event
        counter -- all pass ...]
   
   Final memory usage:
   VARIABLE  BEFORE   AFTER
   ======== ======== ========
   arena       5de60    5de60
   ordblks         2        5
   mxordblk    58658    54320
   uordblks     5758     57e0
   fordblks    58708    58680
   user_main: Exiting
   ostest_main: Exiting with status 0
   ```
   
   Note both `id=1` and `id=2` now start together (`get_primes_thread
   id=1 started` immediately followed by `id=2 started`, instead of one
   finishing before the other starts), and `rr_test: Done` (not
   `Roundrobin Failed`) confirms `roundrobin.c`'s own interleaving check
   passed.
   
   The round-robin fix alone was additionally validated with just
   `rr_test()` (skipping the rest of the suite) on both
   `esp32c3-legacy-devkit:tickless` and `esp32c3-devkit:tickless` --
   PASS on both, confirming the fix is backend- and architecture-independent.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to