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

Added multithreading support for ESP32s #3310

Closed
wants to merge 2 commits into from

Conversation

Phando
Copy link

@Phando Phando commented Aug 9, 2023

Added optional multithreading support to core WLED classes. WLED, UsermodManager and Usermod. The actual multithreading is done in custom usermods. Any usermod with a backgroundLoop, will be executed on the background thread.

@softhack007 softhack007 self-requested a review August 9, 2023 07:51
Copy link
Collaborator

@softhack007 softhack007 left a comment

Choose a reason for hiding this comment

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

Hi, thanks for this interesting proposal.
Below please find some first comments - I'll need some more time to go through everything...

Please also study how multi-tasking is managed in four-line-display(ALT) and in the audioreactive usermod.

void doBackgroundWork(){
backgroundIndex++;
Serial.println("B: " + String(backgroundIndex));
}
Copy link
Collaborator

@softhack007 softhack007 Aug 9, 2023

Choose a reason for hiding this comment

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

Please generally(!) avoid fastled EVERY_N_MILLIS as it does a very bad job .... for example, if the N_MILLIS time is missed by a single microsecond, it does nothing.

You might be able to utilize vTaskDelayUntil(). There are examples in the fourLineDisplay_ALT or audioreactive usermods.

Copy link
Author

Choose a reason for hiding this comment

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

I thought EVERY_N... was clean, I will move to vTaskDelayUntil, thank you for that.

Copy link
Collaborator

@softhack007 softhack007 Aug 9, 2023

Choose a reason for hiding this comment

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

The benefit of every_n_... is that it does not block as it avoids calling delay().
But you can easily substitute it with a few lines like this:

const unsigned long task1_period = 40;   // to run every 40 millisecond
static unsigned long task1_last_run = 0;   // "static" needed so the value is preserved
if ((millis() - task1_last_run) >= task1_period) { // activate after at least 40 millis have passed
   task1_last_run = millis();  // remember last runtime
   task1_do_work();              // do work
}
// do something else, or just wait

In contrast to 8266 or other small arduino platforms, on esp32 millis() and delay() do work as expected even when you are outside of the main looptask (i.e. in a seperate task). They are still "forbidden" for interrupt service routines (ISR).
vTaskDelayUntil() will suspend your main task until a specific time, its for "nothing to do" situations.

}

void loop() {
EVERY_N_MILLISECONDS(1000) { doMainWork(); }
Copy link
Collaborator

Choose a reason for hiding this comment

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

See my previous comment

@@ -11,7 +11,7 @@

# CI binaries
; default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth # ESP32 variant builds are temporarily excluded from CI due to toolchain issues on the GitHub Actions Linux environment
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB, esp32s3dev_8MB_PSRAM_opi
; default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB, esp32s3dev_8MB_PSRAM_opi
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please revert this

Copy link
Author

Choose a reason for hiding this comment

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

I am happy to revert this part. I usually use platform_overrides. Is that the preferred method for adding new functionality?

@blazoncek
Copy link
Collaborator

I am not a RTOS expert or have deep knowledge about task scheduling on ESP but these crossed my mind nevertheless:

  • why are you inventing new API for task loops? why not just schedule each usermod's loop as a separate task instead?
  • did it cross your mind that not all libraries used in usermods are thread safe?
  • setting 10k for task stack is quite liberal and IMO a lot
  • you should not modify platformio.ini

I wonder what @softhack007 will say as he is better at low level stuff.

@softhack007
Copy link
Collaborator

  • did it cross your mind that not all libraries used in usermods are thread safe?

I fully support this point. Not all libraries are thread-safe, and the typical usermod is not thread-safe either.

@@ -285,6 +285,31 @@ DEBUG_PRINTLN(F("Watchdog: disabled"));
#endif
}

#if defined(WLED_ENABLE_BACKGROUND)
Copy link
Collaborator

@softhack007 softhack007 Aug 9, 2023

Choose a reason for hiding this comment

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

Can you move these two functions (background main loop, and creation of the new task) into your main usermod?
I think its not strictly needed that this is added into the WLED core. You can achieve the same thing in the "setup" function of your main UsermodBackground::Setup(). Similar to what usermods/audioreactive does.

if (enabled) onUpdateBegin(false); // create FFT task

void onUpdateBegin(bool init)

if (init && FFT_Task) {
vTaskSuspend(FFT_Task); // update is about to begin, disable task to prevent crash
if (udpSyncConnected) { // close UDP sync connection (if open)
udpSyncConnected = false;
fftUdp.stop();
}
} else {
// update has failed or create task requested
if (FFT_Task) {
vTaskResume(FFT_Task);
connected(); // resume UDP
} else
// xTaskCreatePinnedToCore(
xTaskCreate( // no need to "pin" this task to core #0
FFTcode, // Function to implement the task
"FFT", // Name of the task
5000, // Stack size in words
NULL, // Task input parameter
1, // Priority of the task
&FFT_Task // Task handle
// , 0 // Core where the task should run
);
}
micDataReal = 0.0f; // just to be sure
if (enabled) disableSoundProcessing = false;
}

Copy link
Author

Choose a reason for hiding this comment

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

I will look at audio reactive for sure. I am not trying to reinvent the wheel, but make writing usermods easier. By adding backgroundLoop to wled.cpp and the other core files, all devs need to do is implement the backgroundLoop function. Also this strategy can support multiple usermods all with main and background loops. That said, it is never my goal to reinvent the wheel, just make the usermod experience easier for new devs.

The stack size is large, but settable via the BACKGROUND_STACK_SIZE setting.

for (;;)
{
// instance->usermods.backgroundLoop();
usermods.backgroundLoop();
Copy link
Collaborator

@softhack007 softhack007 Aug 9, 2023

Choose a reason for hiding this comment

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

Sorry, but this way of "running" stuff in the background is very problematic.

Together with the activation conditions (every_n_milliseconds) in each of your background usermods, this basically implements a busy-wait-loop that will compete for 100% CPU cycles with all other tasks on core#0 (e.g. audioreactive, system tasks (like wifi or tcp), and ISRs).

Your code would even compile on -C3 and -S2 (that only have core #0), but it will cause instabilities and may even heat up cheaper boards (=fire hazard).

Normally in FreeRTOS a task only uses the CPU when it has something to do, and the task goes into "waiting" state (vTaskDelay, waiting on semaphores or queues, etc) when nothing is scheduled. This allows other tasks at the same priority to activate in time to meat their task deadlines.

Copy link
Collaborator

@softhack007 softhack007 Aug 9, 2023

Choose a reason for hiding this comment

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

To improve, I would suggest that each backgroundLoop() should return the number of millis until it wants to be activated again, similar to return FRAMETIME; used in effects (FX.cpp). This would allow your scheduler to identify "nothing to do" situations, and free the CPU by vTaskDelay() if no background loop is due for running.

Copy link
Author

Choose a reason for hiding this comment

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

Think this is going to change how I write multithreaded apps for the foreseeable future. Thank you

@Phando
Copy link
Author

Phando commented Aug 9, 2023

Thank you for all the comments. I was not aware that the four line display and audioreactive implemented multithreading. Your comments are all insightful and grounded in reality. There are some bits in there I wish I knew years ago. I'll go back to the drawing board and see if there is need for this at all.

@Phando Phando closed this Aug 9, 2023
@softhack007
Copy link
Collaborator

softhack007 commented Aug 9, 2023

don't give up .... its still an interesting feature :-) Maybe just the final details will be different from your first design.
Could still be something for 0.15.0, as there are plans already to evolve/improve the usermods framework into similar direction.

cheers 🥇

@Phando
Copy link
Author

Phando commented Aug 9, 2023

@softhack007 I am not giving up, I was stoked for the feedback. Given all the comments, I need to go back and rework my solution and try again.

@blazoncek
Copy link
Collaborator

I would suggest a complete re-think of how usermods are called.
Your approach is in the right direction IMO except perhaps a bit too simple ATM.

I think modifying add() function and adding a few semaphores would do the trick.

@Phando Phando deleted the multithread branch August 24, 2023 16:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants