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.

val - disappointed in SE
  • 1,475
  • 3
  • 16
  • 40
  • 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 – Clonk Aug 01 '18 at 11:55
  • @Clonk Is there enought details for now? – val - disappointed in SE Aug 01 '18 at 11:58
  • Try to provide a [MCVE](https://stackoverflow.com/help/mcve). Remove unnecessary attribute member to struct. It's really important to show HOW you allocate your object and store it and how you move reference around. – Clonk Aug 01 '18 at 12:04
  • @Clonk I made complete example. Can you check it now? – val - disappointed in SE Aug 01 '18 at 12:47
  • I'll take a look ! Thanks for your effort in making a quality question :) – Clonk Aug 01 '18 at 12:47
  • 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. – Dan M. Aug 01 '18 at 12:56
  • My best guess is, that RTOS needs a raw pointer and you pass it, but then the destructor of shared_ptr will clean up the pointer regardless of the passed raw pointer. You have to that manually I'm afraid. – hellow Aug 01 '18 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. – Alex F Aug 01 '18 at 12:58
  • When you feed the queue with your `container` object, I suspect it just does a `memcpy`, assuming the object is a POD of size `sizeof (container)`. This doesn't do well at all with object oriented design and RAII – Rerito Aug 01 '18 at 12:59
  • Thank you for all your comments! It proves my suggestions, but is there any solution to that? – val - disappointed in SE Aug 01 '18 at 12:59
  • Use a C++ friendly serialization framework? – Rerito Aug 01 '18 at 13:00
  • 4
    Pass plain pointers and manage object lifetime yourself, don't expect this from FREERTOS. – Alex F Aug 01 '18 at 13:00

3 Answers3

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

Clonk
  • 2,025
  • 1
  • 11
  • 26
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.

Spacefish
  • 305
  • 4
  • 11
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

V.Lorz
  • 293
  • 2
  • 8
  • 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. – V.Lorz May 09 '21 at 17:50