Skip to content

tests/sys/cpp11_thread: use less stack for boards with low RAM memory#22417

Open
crasbe wants to merge 2 commits into
RIOT-OS:masterfrom
crasbe:pr/cpp11_thread_low_memory_boards
Open

tests/sys/cpp11_thread: use less stack for boards with low RAM memory#22417
crasbe wants to merge 2 commits into
RIOT-OS:masterfrom
crasbe:pr/cpp11_thread_low_memory_boards

Conversation

@crasbe

@crasbe crasbe commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Contribution description

Following #22404, I ran dist/tools/compile_and_test_for_board/compile_and_test_for_board.py and noticed that tests/sys/cpp11_thread would crash. The reason is that the amount of stack used just exceeds the RAM available in the microcontroller.

Instead of reducing it globally, which would disable printing the stack usage for all microcontrollers, I adapted the LOW_MEMORY_BOARDS variable known from other tests and examples.

To get the additional boards that work now which didn't before, I set the stack to 512 bytes and ran make generate-Makefile.ci and copied the boards over afterwards.
It is possible that there are boards that would fail the test because they have enough memory for the static compilation check but not for the actual execution though, but without testing them it's kinda hard to know.

One way to find out would be to check the RAM_LEN somewhere and compile for all boards and add the ones that have <16KB of RAM, but I didn't want to go that far...

Testing procedure

Unfortunately I don't have all the hardware for this, but now it works with the nucleo-g031k8 where it previously crashed.

With #22404 applied, this is the result from master:

cbuec@W11nMate:~/RIOTstuff/riot-vanillaice/RIOT$ BOARD=nucleo-g031k8 make -C tests/sys/cpp11_thread/ flash test -j
make: Entering directory '/home/cbuec/RIOTstuff/riot-vanillaice/RIOT/tests/sys/cpp11_thread'
Building application "tests_cpp11_thread" for "nucleo-g031k8" with CPU "stm32".

"make" -C /home/cbuec/RIOTstuff/riot-vanillaice/RIOT/pkg/cmsis/
...
"make" -C /home/cbuec/RIOTstuff/riot-vanillaice/RIOT/sys/ztimer64
   text    data     bss     dec     hex filename
  34040     192    2468   36700    8f5c /home/cbuec/RIOTstuff/riot-vanillaice/RIOT/tests/sys/cpp11_thread/bin/nucleo-g031k8/tests_cpp11_thread.elf
/home/cbuec/RIOTstuff/riot-vanillaice/RIOT/dist/tools/openocd/openocd.sh flash /home/cbuec/RIOTstuff/riot-vanillaice/RIOT/tests/sys/cpp11_thread/bin/nucleo-g031k8/tests_cpp11_thread.elf
### Flashing Target ###
Open On-Chip Debugger 0.12.0+dev-00645-g49ef1d010 (2026-05-28-14:54)
...
Done flashing
r
/home/cbuec/RIOTstuff/riot-vanillaice/RIOT/dist/tools/pyterm/pyterm -p "/dev/ttyACM0" -b "115200" -ln "/tmp/pyterm-cbuec" -rn "2026-06-24_16.49.07-tests_cpp11_thread-nucleo-g031k8" --no-reconnect --noprefix --no-repeat-command-on-empty-line
Connect to serial port /dev/ttyACM0
Welcome to pyterm!
Type '/exit' to exit.
READY
s
START
main(): This is RIOT! (Version: 2026.07-devel-388-g9ae8f-timer-nucleo-g031k8)

************ C++ thread test ***********
Creating one thread and passing an argument ...
Done

tests/sys/cpp11_thread/main.cpp:56 => failed condition
*** RIOT kernel panic:
CONDITION FAILED.

*** halted.

Timeout in expect script at "child.expect_exact("Creating detached thread ...")" (tests/sys/cpp11_thread/tests/01-run.py:18)

make: *** [/home/cbuec/RIOTstuff/riot-vanillaice/RIOT/makefiles/tests/tests.inc.mk:32: test] Error 1
make: Leaving directory '/home/cbuec/RIOTstuff/riot-vanillaice/RIOT/tests/sys/cpp11_thread'

With #22404 applied and this PR:

cbuec@W11nMate:~/RIOTstuff/riot-vanillaice/RIOT$ BOARD=nucleo-g031k8 make -C tests/sys/cpp11_thread/ flash test -j
make: Entering directory '/home/cbuec/RIOTstuff/riot-vanillaice/RIOT/tests/sys/cpp11_thread'
Using a reduced stack size for low memory board "nucleo-g031k8"!
Building application "tests_cpp11_thread" for "nucleo-g031k8" with CPU "stm32".

"make" -C /home/cbuec/RIOTstuff/riot-vanillaice/RIOT/pkg/cmsis/
...
"make" -C /home/cbuec/RIOTstuff/riot-vanillaice/RIOT/sys/ztimer64
   text    data     bss     dec     hex filename
  33896     192    1444   35532    8acc /home/cbuec/RIOTstuff/riot-vanillaice/RIOT/tests/sys/cpp11_thread/bin/nucleo-g031k8/tests_cpp11_thread.elf
/home/cbuec/RIOTstuff/riot-vanillaice/RIOT/dist/tools/openocd/openocd.sh flash /home/cbuec/RIOTstuff/riot-vanillaice/RIOT/tests/sys/cpp11_thread/bin/nucleo-g031k8/tests_cpp11_thread.elf
### Flashing Target ###
Open On-Chip Debugger 0.12.0+dev-00645-g49ef1d010 (2026-05-28-14:54)
...
Done flashing
r
Using a reduced stack size for low memory board "nucleo-g031k8"!
/home/cbuec/RIOTstuff/riot-vanillaice/RIOT/dist/tools/pyterm/pyterm -p "/dev/ttyACM0" -b "115200" -ln "/tmp/pyterm-cbuec" -rn "2026-06-24_16.51.50-tests_cpp11_thread-nucleo-g031k8" --no-reconnect --noprefix --no-repeat-command-on-empty-line
Connect to serial port /dev/ttyACM0
Welcome to pyterm!
Type '/exit' to exit.
READY
s
START
main(): This is RIOT! (Version: 2026.07-devel-391-g04ff9-pr/cpp11_thread_low_memory_boards)

************ C++ thread test ***********
Creating one thread and passing an argument ...
Done

Creating detached thread ...
Done

Join on 'finished' thread ...
Done

Join on 'running' thread ...
Done

Testing sleep_for ...
Done

Testing sleep_until ...
Done

Swapping two threads ...
Done

Move constructor ...
Done

Bye, bye.
******************************************

make: Leaving directory '/home/cbuec/RIOTstuff/riot-vanillaice/RIOT/tests/sys/cpp11_thread'

Issues/PRs references

Declaration of AI-Tools / LLMs usage:

AI-Tools / LLMs that were used are:

  • none

@crasbe crasbe requested review from maribu and mguetschow June 24, 2026 14:54
@crasbe crasbe added Type: bug The issue reports a bug / The PR fixes a bug (including spelling errors) CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR labels Jun 24, 2026
@github-actions github-actions Bot added the Area: tests Area: tests and testing framework label Jun 24, 2026
@AnnsAnns

Copy link
Copy Markdown
Member

You are sending PRs by the minute today gawd damn 🤠

@AnnsAnns AnnsAnns left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

waow

Comment on lines +11 to +30
# Boards with little RAM (~8KB) can't handle threads with the large default
# stack size.
# Beware: The reduced stack size will also suppress the thread stack usage
# printout!
LOW_MEMORY_BOARDS := \
nucleo-c031c6 \
nucleo-f042k6 \
nucleo-g031k8 \
nucleo-l031k6 \
stm32g0316-disco \
weact-g030f6 \
#

ifneq (,$(filter $(BOARD),$(LOW_MEMORY_BOARDS)))
$(shell $(COLOR_ECHO) "$(COLOR_YELLOW)Using a reduced stack size for low" \
"memory board \"$(BOARD)\"!$(COLOR_RESET)" 1>&2)

CFLAGS += -DTHREAD_STACKSIZE_MAIN=512
DISABLE_MODULE += test_utils_print_stack_usage
endif

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this somehow be checked in code by checking for the define? I'm generally fine with this but idk how other people stand on doing it like this

@crasbe crasbe Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like this?

#if RAM_LEN <= 8096
#  if THREAD_STACKSIZE_MAIN > 512
#    error "A stack size of more than 512 bytes might lead to a memory overflow!" \
           "Consider adding the board to the LOW_MEMORY_BOARDS list in the Makefile."
#  endif
#endif

Disclaimer: Haven't tested it yet.

@AnnsAnns AnnsAnns Jul 1, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah that as well but I meant that in the sense of simply changing the THREAD_STACKSIZE_MAIN in the header, e.g.

#ifndef THREAD_STACKSIZE_MAIN
#     define THREAD_STACKSIZE_MAIN 512
#endif

so the stack size reduction is closer to the boards itself, I feel like doing this externally might make it harder to spot where the number suddenly changes

@riot-ci

riot-ci commented Jun 24, 2026

Copy link
Copy Markdown

Murdock results

✔️ PASSED

d34685a tests/sys/cpp11_thread: reduce stacksize for low RAM boards

Success Failures Total Runtime
11123 0 11124 10m:56s

Artifacts

@maribu maribu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, let's not do this.

The test is not really testing the board, but the CPU architecture and toolchain. If one Cortex M33 board works, all other will also. It IMO just not worth spending code end effort in making this test runnable on more boards.

There also is the issue that hard coded stack sizes don't really work. On AVR 512 B is plenty, as it is rather stack efficient. On native64, anything below 4096 will run into a stack overflow. We could try using THREAD_STACKSIZE_SMALL instead. But again, why bother if this test doesn't work on some boards, as long as we have at least one board per family supported?

I also fear that some people would read the code here that stack sizes do relate to available RAM. But stack size should only chosen based on what the code needs, regardless of what board we have.

@crasbe

crasbe commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

But again, why bother if this test doesn't work on some boards, as long as we have at least one board per family supported?

I don't think this is a good way of looking at it. If someone ports a board and runs the compile and test script and a test fails, they'd rightfully assume that something is wrong with the port.

Relying on tribal knowledge of some maintainers that certain tests just don't work on some boards for some reason is bad, as that knowledge is quickly lost.

But stack size should only chosen based on what the code needs, regardless of what board we have.

Well the used stack according to the print_whatever module is only 136 Bytes or so. Reducing the stack size to anything less than 512 Bytes will lead to a crash though.

If the proposed solution is not acceptable, I would at least want to have the boards with little RAM on the board blacklist (or what is it called now? I don't remember) and an explanation why that is.

Although I do think that maybe the #if-#error thing wouldn't be so bad. That would at least point someone who's porting porting a board to the board blacklist when the RAM length is insufficient.

Mind you, this took me 1-2 hours to debug and find out.
There is zero documentation on what this test is supposed to do and what possible limitations might be.

@maribu

maribu commented Jun 24, 2026

Copy link
Copy Markdown
Member

What exactly is the failure mode here?

Does it compile and then fail at the linking stage due to e.g. .bss overflown?

A failure at runtime is not expected here.

@crasbe

crasbe commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

What exactly is the failure mode here?

Does it compile and then fail at the linking stage due to e.g. .bss overflown?

A failure at runtime is not expected here.

It compiles successfully and fails at runtime, see the logs in the initial message.

@maribu

maribu commented Jun 24, 2026

Copy link
Copy Markdown
Member

OK, but then just making more RAM available won't fix the underlying issue, it will just cause some boards to no longer be affected by it.

@crasbe

crasbe commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Who knows whether or not that's the expected behavior. There's no documentation. Some tests say that they're expected to fail, so who knows 🤷

@maribu

maribu commented Jun 24, 2026

Copy link
Copy Markdown
Member

The issue here is that the constructor of thread is using dynamic memory allocation via new under the hood to allocate (among other stuff):

/**
 * @brief Holds context data for the thread.
 */
struct thread_data {
  thread_data() : ref_count{2}, joining_thread{KERNEL_PID_UNDEF} {
    // nop
  }
  /** @cond INTERNAL */
  std::atomic<unsigned> ref_count;
  kernel_pid_t joining_thread;
  std::array<char, THREAD_STACKSIZE_MAIN> stack;
  /** @endcond */
};

There are a number of red flags in the code here:

  1. Use of new / dynamic memory allocation in embedded environment is bound to cause random runtime failures, even when plenty of RAM is available, fragmentation can still bite you
  2. The allocation failure clearly is not handled. I assume it would throw and exception, only that exceptions usually are disabled in the nano toolchains. It clearly does not work nor throws an exception.
  3. The allocation is complete hidden. A single line like thread t([=](const int j) { expect(j == i); }, i); causes KiBs of RAM to be allocated in the heap with no indication of how expensive the operation is
  4. Use of THREAD_STACKSIZE_MAIN for a non main thread stack. Anyone touching that macro will be more than surprised that it has the side-effect of changing the stack size of C++ threads.
  5. No configuration of the stack size in C++: Once you have threads that are tiny and large, you are wasting a lot of memory

IMO we can just remove the whole C++ thread API and call it a day. I think nobody uses this anyway. I'm sure nobody should use it, looking at the code.

@maribu

maribu commented Jun 24, 2026

Copy link
Copy Markdown
Member

I don't have any hardware at hand. Could you test #22418 and see if the failure mode is a bit more boring with that?

I still believe dropping the C++ thread API is the right call here. But having new do error handling is something users probably can expect.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: tests Area: tests and testing framework CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Type: bug The issue reports a bug / The PR fixes a bug (including spelling errors)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants