Thread blocks return value of cloud function

argon
Tags: #<Tag:0x00007f1ca3464080>

#1

I have program where I have registered a cloud function, that takes a while (e.g. 5 seconds depending on the argument), that I want to spawn a new thread for so that it is not blocking for the return value of the cloud function or other threads (including the main thread running loop()). The code runs on an Argon with deviceOS 0.9.

The simplified version of my code is as below:

#include "Particle.h"

SYSTEM_THREAD(ENABLED);

void thread_func(void *args) {
  /* Run heavy code */
  Serial.printlnf("thread_func done");
}

int cloud_function(String arg) {
  Thread thread("thread_func_call", thread_func);
  Serial.printlnf("returning");
  return 1;
}

void setup() {
    Particle.function("cloud_func", cloud_function);
    Serial.begin(9600);
}

void loop() {
  delay(1000);
}

I can see that “returning” is printed when the cloud_func is called, but I don’t get a response to my request before after seeing “thread_func done”. The thread running the thread_func seemingly has higher priority than handling the cloud function returns.
I’ve tried adding os_thread_yield() and Particle.process() where possible in the thread_func, but with no luck.

Is the cloud function handling simply a lower priority than application threads?


#2

Not the best approach as FreeRTOS threads are not that easily discarded nor will they function properly once their object variable goes out of scope.
Once they are created they are expected to keep running.


#3

@Antero Just to confirm @Scruffr - a better approach is to run a finite state machine (FSM) in the loop() with a state to run your heavy code that could take a while. The loop does not need to be blocked because you can have another test of progress and nibble away at it. Your cloud_function then just queues the next state and returns very quickly. In summary, you need FSM, a FIFO mode queue, and some checks in the handler to reject request if one is already running?


#4

Thanks for your replies.

I’ve modified the code to run the heavy code in the loop() function, and that works fine.

My main question/concern was actually whether or not the application threads have higher priority than the thread handling cloud requests (which seems to be the case). Whether that’s a good thing or not (I’d argue that it is not a good thing that you can’t make a low-prio application thread) is debatable, but I don’t think there is any place where that is documented.


#5

Nope, they don’t and it’s not the case - it rather seems to be a misconception.
But as pointed out: Poor practice inside (user) threads can disrupt the performance of the entire threading system.

However, you can set the priority of your own threads

  Thread("someThread", some_thread, OS_THREAD_PRIORITY_DEFAULT - 1);

So, not sure why you’d need to argue something that’s not actually the case :confused:

(not knowing how to, is not the same as not being able to :wink:)


#6

Thanks for finding that table for me ScruffR - Forgot where I found it the first time :slight_smile:

With regarding to thread priority, I actually did try creating a thread with priority OS_THREAD_PRIORITY_DEFAULT - 1.
A thread created with that priority, should not, in my understanding, be able to block a thread at a higher priority.

I had a thread running this function (modified for simplicity):

void thread_func(void *arg) {
  for (int i = 0; i < 5000; i++) {
    /* some simple operation */
    os_thread_yield();
    delay(1);
  }
}

However when that thread was running, cloud function calls were not returned from, until that thread had exited (which in this particular case would be at least 5 seconds).


#7

This is how I’d do it

SYSTEM_THREAD(ENABLED);

volatile bool doJob = false;
void thread_func(void *args) {
  while(1) {
    if (doJob) {
      delay(5000);  // mimic long running code
      digitalWrite(D7, !digitalRead(D7));
      doJob = false;
    }      
  }
}

Thread *thread;

int cloud_function(String arg) {
  doJob = true;
  Serial.printlnf("returning");
  return 1;
}

void setup() {
    Serial.begin();
    Particle.function("cloud_func", cloud_function);
    pinMode(D7, OUTPUT);
    digitalWrite(D7, HIGH);
    thread = new Thread("thread_func_call", thread_func);
}

void loop() {
}

Maybe @rickkas7 can chime in but for my understanding FreeRTOS threads should

  • not end once spawned
  • not get orphaned by their object variable going out of scope
  • mustnot access other objects before their constructor has been positively finished (e.g. by “locking” on entry or late spawning - both shown in my code)

#8

I think we are going in a different direction now ScruffR :slight_smile:

I agree that my first example is not the correct way. In the example you provide, does cloud_function send a response instantly?
I’ve tried a similar implementation (using a mutex instead of a boolean), but where I would not get a HTTP response before after the thread_func would finish its busy-work.


#9

As ScruffR mentioned, you cannot stop a FreeRTOS thead or allow it to exit. Once you start it, it must stay running forever. You can use queues and thread pools to work around this limitation.

This will not work:

int cloud_function(String arg) {
  Thread thread("thread_func_call", thread_func);
  Serial.printlnf("returning");
  return 1;
}

The Thread object here is a stack allocated local variable and will be deleted when the function exits. This is not supported. Thread objects must be created as a global variable or allocated with new and never deleted.


#10

Yes, it does (more or less, but definetly before the D7 LED comes on) - why not try yourself :wink:

Thanks, @rickkas7 for confirming what I was suggesting all along :+1:


#11

Interesting, because when I tested with your approach, I would not get my HTTP response (sent from https://console.particle.io) before after the code after my mutex was done running (which was the whole issue behind this thread). My code was also different from yours in the way that instead of doing a delay(5000);, it was

for (int i = 0; i < 1000; i++) {
  delay(1);
  /* write value to HW */
  delay(10);
}

But I wonder if that has something to do with the delay length, as the reference document states:
"Particle.process() is also called during any delay() of at least 1 second."

I did also look into the Thread Pools, but it seemed that the ThreadPool.h file (and the cpp file) had to be manually included in the project (i.e. copied from https://github.com/rickkas7/particle-threads/tree/master/test/07-thread-pool).


#12

That’s only true without SYSTEM_THREAD(ENABLED) - with multi threading delay() will just yiel and give up its timeslice till the set timeout has been reached.
However, even your loop would also accumulate delay time of more than 1 second and while the docs state “any delay() of at least 1 second” it should actually read “any accumulated delay() time of at least 1 second”.

But you can replace my delay(5000) with for (uint32_t ms = millis(); millis() - ms < 5000;); to get a hard blocking loop only preempted by the FreeRTOS thread manager.

If you want to use a mutext, you can like this

SYSTEM_THREAD(ENABLED);

os_mutex_t mutex;

void thread_func(void *args) {
  while(1) {
    os_mutex_lock(mutex);
    for(uint32_t ms = millis(); millis() - ms < 5000;);  // mimic long running code
    digitalWrite(D7, !digitalRead(D7));
  }
}

Thread *thread;

int cloud_function(String arg) {
  Serial.printlnf("returning");
  os_mutex_unlock(mutex);
  return 1;
}

void setup() {
    Serial.begin();
    Particle.function("cloud_func", cloud_function);
    pinMode(D7, OUTPUT);
    digitalWrite(D7, HIGH);
  
    os_mutex_create(&mutex);
    os_mutex_lock(mutex);     // start off locked

    thread = new Thread("thread_func_call", thread_func);
}