简体   繁体   中英

Simulink "Access Violation" writing to PWork variable in capture list of C++ lambda function

I have a C++ S-Function that handles a set of threaded operations through std::thread, std::async and callbacks. The thing is that one of the callbacks is an S-function that has a buffer in the capture list. This buffer is in the PWork of the Simulink model. However, it seems that Matlab crashes as soon as I try to write to it.

Below is a minimal crashing example of my S-Function (only the mdlStart function), which contains the relevant code:

static void mdlStart(SimStruct *S)
{
    ssGetPWork(S)[0] = (void *) new ThreadedDataServer();
    ssGetPWork(S)[1] = (void *) new DatagramAssembler();
    ssGetPWork(S)[2] = (void *) new MyBufferType(); // actually an std::array<char, LARGENUMBER>

    auto server          = (ThreadedDataServer *) ssGetPWork(S)[0];
    auto assembler       = (DatagramAssembler*)   ssGetPWork(S)[1];
    auto copy_buffer_ptr = (MyBufferType *)       ssGetPWork(S)[2];

    server->attachDataHandler([&copy_buffer_ptr, &assembler](const ThreadedDataServer::buffer_t & buffer, size_t num_bytes)
    {
        /* Minimal crashing action */
        copy_buffer_ptr->at(5) = 'b'; // Any index != 0
        /* Original code */
        //std::copy(buffer.begin(), buffer.begin() + num_bytes, copy_buffer_ptr->data());
        //assembler->feedData(*copy_buffer_ptr, num_bytes);
    });
}

The handler is invoked from the data server worker thread (different from Simulink main thread). Other actions inside the callback function work smoothly (reading parameters, doing other operations...).

Any hint why this happens? The same code was working in an independent executeble before integrating it in Simulink.

You are capturing copy_buffer_ptr (a stack-local variable) by reference. That reference will be dangling as soon as mdlStart returns, after which invoking the lambda is undefined behavior. (This also applies to assembler ).

The fix is to simply capture copy_buffer_ptr and assembler by value (they are simple pointers, you can just copy them without issue):

server->attachDataHandler([copy_buffer_ptr, assembler](const ThreadedDataServer::buffer_t & buffer, size_t num_bytes)
{
  /* etc. */
});

If you have a lambda that will outlive the current scope, think long and hard before capturing anything by reference - if it's stack-local, you're probably about to get burnt.

Thanks to @max-langhof answer (dangling pointer) and some additional work, I finally reached a solution:

static void mdlStart(SimStruct *S)
{
    ssGetPWork(S)[0] = (void *) new ThreadedDataServer();
    ssGetPWork(S)[1] = (void *) new DatagramAssembler();
    ssGetPWork(S)[2] = (void *) new MyBufferType(); // actually an std::array<char, LARGENUMBER>
    ssGetPWork(S)[3] = (void *) new std::mutex();

    auto server          = (ThreadedDataServer *) ssGetPWork(S)[0];
    auto assembler       = (DatagramAssembler*)   ssGetPWork(S)[1];
    auto copy_buffer_ptr = (MyBufferType *)       ssGetPWork(S)[2];
    auto assembly_mutex  = (std::mutex *)         ssGetPWork(S)[3];

    server->attachDataHandler([copy_buffer_ptr, assembler, assembly_mtx](const ThreadedDataServer::buffer_t & buffer, size_t num_bytes)
    {
        // Mutex scoped lock
        {
            std::lock_guard<std::mutex> lckg(assembly_mutex);
            std::copy(buffer.begin(), buffer.begin() + num_bytes, copy_buffer_ptr.data());
            assembler.feedData(copy_buffer_ptr, num_bytes);
        }
    });

This implementation solves two problems:

  • Variables in capture list were reference to pointer-in-the-stack, which ended as dangling pointers and caused a crash.
  • Several calls to data handler were accessing the buffer and the Assembler object concurrently, apparently causing a different Access Violation.

It's working now :-)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM