Open
Description
pico-sdk doesn't support thread-local storage (thread_local keyword), which is required for c++ exceptions to function properly in a multi-core environment. Internally libstdc++ stores some global exception state in a thread_local variable (static __thread abi::__cxa_eh_globals global;
in eh_globals.cc).
If now both RP2040 cores throw or catch exceptions at the same time they are "mixed", because the thread_local state variable is the exact same variable on both cores. There are also no synchronization locks, so both cores can write to the same variable at the same time.
The code below shows how core0's std::current_exception() is overwritten by core1. The expected output is:
[core0] core0 exception
[core1] core1 exception
[core0] core0 exception
[core1] core1 exception
The actual output is:
[core0] core0 exception
[core1] core1 exception
[core0] core1 exception
[core1] core1 exception
#include <cstdio>
#include <stdexcept>
#include <pico/multicore.h>
#include <pico/stdio_usb.h>
void continue_with_other_core(semaphore_t& acquire, semaphore_t& release)
{
sem_release(&release);
sem_acquire_blocking(&acquire);
}
void print_current_exception()
{
try {
std::rethrow_exception(std::current_exception());
}
catch (const std::exception& e) {
std::printf("[core%d] %s\n", get_core_num(), e.what());
}
}
void run_test(semaphore_t& acquire, semaphore_t& release)
{
try {
sem_acquire_blocking(&acquire);
throw std::runtime_error("core" + std::to_string(get_core_num()) + " exception");
}
catch (...) {
print_current_exception();
continue_with_other_core(acquire, release);
print_current_exception();
continue_with_other_core(acquire, release);
}
}
int main()
{
stdio_usb_init();
std::getchar();
std::puts("[start]");
static semaphore_t sem0, sem1;
sem_init(&sem0, 1, 1); // unlocked: start with core0
sem_init(&sem1, 0, 1);
multicore_launch_core1([] { run_test(sem1, sem0); });
run_test(sem0, sem1);
for (;;);
}