Upon trying to write to a piece of (portable) C++ code that uses shared_memory_segment
in order to write to "shared memory", I encountered boost::interprocess::bad_alloc
several times. From the Boost documentation:
This exception is thrown when a memory request can't be fulfilled.
So, I must be allocating too little memory. Here follows the code (only for writing, since reading here is irrelevant):
shared_memory.h:
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/containers/vector.hpp>
#include <boost/interprocess/containers/string.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/sync/named_mutex.hpp>
#include <string>
#include <exception>
namespace my_shared_memory
{
typedef boost::interprocess::allocator<char, boost::interprocess::managed_shared_memory::segment_manager> CharAllocator;
typedef boost::interprocess::basic_string<char, std::char_traits<char>, CharAllocator> IPCString;
typedef boost::interprocess::allocator<IPCString, boost::interprocess::managed_shared_memory::segment_manager> StringAllocator;
typedef boost::interprocess::vector<IPCString, StringAllocator> ShmVector;
bool write_to_memory(std::string wsuid, std::string loop_val, std::string should_intercept, std::string post_data) ;
const std::string shm_prefix = "shm_";
const std::string mutex_prefix = "mtx_";
}
shared_memory.cpp:
#include "shared_memory.h"
namespace apl_shared_memory
{
bool write_to_memory(std::string wsuid, std::string loop_val, std::string should_intercept, std::string post_data)
{
bool ret_val;
std::string shm_name = shm_prefix + wsuid;
std::string mtx_name = mutex_prefix + wsuid;
boost::interprocess::named_mutex named_mtx{boost::interprocess::open_or_create, mtx_name.c_str()};
size_t size = (sizeof(loop_val) + loop_val.size() + sizeof(should_intercept) + should_intercept.size() + sizeof post_data + post_data.size()) * 5;
try
{
named_mtx.lock();
boost::interprocess::shared_memory_object::remove(shm_name.c_str());
boost::interprocess::managed_shared_memory segment(boost::interprocess::create_only, shm_name.c_str(), size);
CharAllocator charallocator (segment.get_segment_manager());
StringAllocator stringallocator(segment.get_segment_manager());
IPCString shm_loop_val(charallocator);
IPCString shm_should_intercept(charallocator);
IPCString shm_intercepted_data(charallocator);
shm_loop_val = loop_val.c_str();
shm_should_intercept = should_intercept.c_str();
shm_intercepted_data = post_data.c_str();
segment.destroy<ShmVector>("ShmVector");
ShmVector *shmVector = segment.construct<ShmVector>("ShmVector")(stringallocator);
shmVector->clear();
shmVector->push_back(shm_loop_val);
shmVector->push_back(shm_should_intercept);
shmVector->push_back(shm_intercepted_data);
named_mtx.unlock();
ret_val = true;
} catch(const std::exception& ex)
{
ret_val = false;
named_mtx.unlock();
boost::interprocess::shared_memory_object::remove(shm_name.c_str());
}
named_mtx.unlock();
return ret_val;
}
}
Yes, I realize I don't need unlock
calls on all three places.
Problem seems to be in the line:
size_t size = (sizeof(loop_val) + loop_val.size() + sizeof(should_intercept) + should_intercept.size() + sizeof post_data + post_data.size()) * 5;
I thought it was overkill to add times 5
but that is apparently not the case. This code works fine on Debian 9 with gcc 6.3.0 and Windows 10 with Microsoft Visual Studio 2015 Community. However, on MacOS with Xcode 10.1, I get boost::interprocess::bad_alloc
when I try to insert even the first element in the vector. The solution seems to multiply the size with 10 instead of 5 but this just appears wasteful.
I added segment.get_free_memory
calls and determined that for a vector that contains the following three strings (without the quotes)
"true" "true" ""
I need 512 bytes with Xcode. The sizeof
operator returned 32, 24 and 32 for IPCString
, std::string
and ShmVector
, respectively. Therefore, it seems that I need 32+4
bytes for one "true" string and 32
for the empty string. Adding the ShmVector there, I need 36+36+32+32=136
bytes for the structures themselves. I calculated the size using sizeof(std::string)
, which is 24 here (an oversight), so that gives me (28+28+24)*5 = 400
, which is not enough here.
My question here is how to determine how much memory I need for the segment? The data I want to write to the segment is known when the function is called and thus is its size.
EDIT: I changed the size to be:
size_t size = (sizeof(IPCString) + loop_val.size() + sizeof(IPCString) + should_intercept.size() + sizeof(IPCString) + post_data.size() + sizeof(ShmVector)) * 2 + 500;
So far, so good. I always have less than 500
bytes of free space after I'm finished writing to the segment. I've tried with various data sizes, ranging from less than 100 bytes to 3 megabytes. I've tried without multiplying by 2
but, in that case, when I try to insert large data chunks, the program crashes as 500
additional bytes does not provide sufficient leeway.
If there is no answer in a couple of days, I'll post this edit as an answer.