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([©_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:
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.