5

I use ESP-32 and need to pass std::shared_ptr using FreeRTOS queue. However, it loose one link. I think that this is source of a problem:

#include <iostream>
#include <memory>
#define PRINT_USE_COUNT(p) std::cout << "Use count: " << p.use_count() << std::endl;

extern "C" {
    #include <freertos/FreeRTOS.h>
    #include <freertos/task.h>
    #include <freertos/queue.h>
}

class testClass {
    public:
        testClass() {
            std::cout << "Class is constructed" << std::endl;
        };
        virtual ~testClass() {
            std::cout << "Class is destructed" << std::endl;
        };
};

struct container {
    std::shared_ptr<testClass> field;
};

extern "C" void app_main(void) {
    auto queue = xQueueCreate(1, sizeof(container));
    auto p = std::make_shared<testClass>();
    PRINT_USE_COUNT(p); // 1
    {
        container c;
        c.field = p;
        PRINT_USE_COUNT(p); // 2
        xQueueSendToBack(queue, &c, 0);
        PRINT_USE_COUNT(p); // 2
    }
    PRINT_USE_COUNT(p); // 1 (Ooops!)
    {
        container c;
        assert(xQueueReceive(queue, &c, 0) == pdTRUE);
        PRINT_USE_COUNT(c.field); // 1
    }
    // Class is destructed
    std::cout << "Test finished" << std::endl;
    vQueueDelete(queue);
}

So there is a pointer in queue, but it isn't counted!

How can I solve this issue (and keep using FreeRTOS queue if possible)? Using std::move doesn't help.

12
  • 1
    How do you allocate sound ? It's hard to tell what you're doing without a minimal example of SoundControl implementation and without knowing how you declare and allocate sound Commented Aug 1, 2018 at 11:55
  • @Clonk Is there enought details for now? Commented Aug 1, 2018 at 11:58
  • 5
    FreeRTOS queue is a C (con)struct. I highly doubt it was designed with C++ classes in mind and as such it probably can't handle anything but PODs/trivial classes. I.e. no destructors and such. Looking at the documentation, it just accepts plain void * and copies it to its storage byte-by-byte. You won't be able to fix it unless they provide C++ API or at least a way to pass deleter. Commented Aug 1, 2018 at 12:56
  • 4
    FREERTOS queue functions use plain memcpy to add items to queue, just look at source code. Queue is created with sizeof(item) parameter, and then it just copies n bytes to/from the queue. Plain C. Commented Aug 1, 2018 at 12:58
  • 4
    Pass plain pointers and manage object lifetime yourself, don't expect this from FREERTOS. Commented Aug 1, 2018 at 13:00

3 Answers 3

3

A C-style queue of raw pointers will only works for C++ shared_ptr iff std::is_trivial<T>::value is true (mainly POD or trivially copyable object).

Since there are memcpy and other plain C operation manipulating memory the reference count will not be handled properly (because it is C-code behind the scene and it does not call destructor among other thing) and you could end up with a memory leak.

There is no easy way to circumvent this, but the best way is to manage the memory yourself.

See this question also : Shared pointers and queues in FreeRTOS

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you. I probably have to write C++ FreeRTOS queue from scratch.
I know that there are C++ wrapper on git hub for freeRTOS, maybe someone already developed something similar. But usually the best solution is to develop something suited to your need.
After thinking a lot, I'm using C++ queue with mutex (it is enought in my case).
0

I managed to transform unique pointer into a raw format which could be sent via a message queue. See here: https://codereview.stackexchange.com/questions/241886/using-unique-ptr-in-freertos.

Please note I posted it in code review because I am not sure whether there are really no memory leaks or this can be implemented much more cleanly. I will test whether I can actually use this for the IPC in our project.

Comments

0

I don't always agree that developing something from scratch is best option. Using something that is well tested might be best option in most occasions even-though it could require some tweaking for making it fit to your needs.

With the queue you can pass a dynamically created instance of your container. It is very rare, if it is at all, to use a queue for sending data from one task to same task, as the example above. I don't like pretty much working with dynamic allocations in embedded CPUs, the overhead can sometimes impact performance too much.

Here below is a working PoC where, instead of a raw copy, a pointer to a new container instance is passed. In this approach it is the responsibility of the receiving task to release the instance to avoid memory leaks.

extern "C" {
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
}

#include <iostream>
#include <memory>

#define PRINT_USE_COUNT(p) std::cout << "Use count: " << p.use_count() << std::endl;

class testClass {
public:
    testClass() {
        std::cout << "testClass constructed" << std::endl;
    }

    ~testClass() {
        std::cout << "testClass destructed" << std::endl;
    }
};

class myContainer {
public:
    myContainer(std::shared_ptr<testClass> p) {
        _p = p;
        std::cout << "myContainer constructed" << std::endl;
    }

    ~myContainer() {
        std::cout << "myContainer destructed" << std::endl;
    }

    std::shared_ptr<testClass>& p() {
        return _p;
    }

private:
    std::shared_ptr<testClass> _p;
};

extern "C" void app_main(void) {
    std::cout << "Start of test, creating the shared_ptr..." << std::endl;
    auto p = std::make_shared<testClass>();
    PRINT_USE_COUNT(p);

    std::cout << "Creating one container..." << std::endl;
    myContainer c(p);
    PRINT_USE_COUNT(p);

    std::cout << "Creating the queue..." << std::endl;
    auto q = xQueueCreate(1, sizeof(myContainer*));

    std::cout << "Sending a dynamically created item to the queue..."
            << std::endl;
    myContainer *cp = new myContainer(p);
    xQueueSendToBack(q, &cp, 0);
    PRINT_USE_COUNT(p);

    {
        myContainer *pc;

        xQueueReceive(q, &pc, 0);
        PRINT_USE_COUNT(p);
        std::cout << "Use count of pc->p() " << pc->p().use_count()
                << std::endl;

        std::cout << "Freeing the dynamically created item..." << std::endl;
        delete pc;
        PRINT_USE_COUNT(p);
    }

    std::cout << "end of test" << std::endl;
}

Here's the program's output:

Start of test, creating the shared_ptr...

testClass constructed

Use count: 1

Creating one container...

myContainer constructed

Use count: 2

Creating the queue...

Sending a dynamically created item to the queue...

myContainer constructed

Use count: 3

Use count: 3

Use count of pc->p() 3

Freeing the dynamically created item...

myContainer destructed

Use count: 2

end of test

myContainer destructed

testClass destructed

1 Comment

I don't know what could be the advantage of creating another container wrapping container. Maybe it could be better to send a dynamically created shared_ptr.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.