I am currently working on a UDP socket client. I am currently noticing a memory leak and I've tried several things in hopes to squash it, but it still prevails. In my main, I have a char*
that has been malloc
'd. I then call the below function to send the data:
void Send(const char* data, const int size) {
Socket.async_send_to(boost::asio::buffer(data, size), Endpoint, boost::bind(&MulticastSender::HandleSendTo, this, boost::asio::placeholders::error));
}
If I run this code, it will always leak memory. However, if I comment out the async_send_to
call, the memory stays consistent.
I have tried several variations(see below) on this, but they all only appear to speed up the memory leak.
A couple notes, there is a chance that the char*
that gets passed to Send may get free
'd before the call completes. However, in my variations, I have taken precaution to do handle that.
Variation 1:
void Send(const char* data, const int size) {
char* buf = (char*)malloc(size);
memcpy(buf, data, size);
Socket.async_send_to(boost::asio::buffer(buf, size), Endpoint, boost::bind(&MulticastSender::HandleSendTo, this, boost::asio::placeholders::error, buf));
}
void HandleSendTo(const boost::system::error_code& ec, const char* buf) {
free(buf);
}
Variation 2:
class MulticastSender {
char* Buffer;
public:
void Send(const char* data, const int size) {
Buffer = (char*)malloc(size);
memcpy(Buffer, data, size);
Socket.async_send_to(boost::asio::buffer(Buffer, size), Endpoint, boost::bind(&MulticastSender::HandleSendTo, this, boost::asio::placeholders::error));
}
void HandleSendTo(const boost::system::error_code& ec) {
free(Buffer);
}
}
However, both variations seem to only speed up the memory leak. I have also tried removing the async_send_to
and just calling boost::asio::buffer(data, size)
, but as has been explained in other questions, the buffer does not own the memory and thus it is up to the user to safely manage it. Any thoughts on what could be causing this issue and how to resolve it?
EDIT 1:
As suggested in the comments, I have preallocated a single buffer (for test purposes) and I am never deallocating it, however, the memory leak still persists.
class MulticastSender {
char* Buffer;
const int MaxSize = 16384;
public:
MulticastSender() {
Buffer = (char*)malloc(MaxSize);
}
void Send(const char* data, const int size) {
memcpy(Buffer, data, size);
Socket.async_send_to(boost::asio::buffer(Buffer, size), Endpoint, boost::bind(&MulticastSender::HandleSendTo, this, boost::asio::placeholders::error));
}
void HandleSendTo(const boost::system::error_code& ec) {
}
}
EDIT 2: As requested here is an MCVE of the problem. In making this I have also observed an interesting behavior that I will explain below.
#include <string>
#include <iostream>
#include <functional>
#include <thread>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
class MulticastSender {
private:
boost::asio::io_service IOService;
const unsigned short Port;
const boost::asio::ip::address Address;
boost::asio::ip::udp::endpoint Endpoint;
boost::asio::ip::udp::socket Socket;
boost::asio::streambuf Buffer;
void HandleSendTo(const boost::system::error_code& ec) {
if(ec) {
std::cerr << "Error writing data to socket: " << ec.message() << '\n';
}
}
void Run() {
IOService.run();
}
public:
MulticastSender(const std::string& address,
const std::string& multicastaddress,
const unsigned short port) : Address(boost::asio::ip::address::from_string(address)),
Port(port),
Endpoint(Address, port),
Socket(IOService, Endpoint.protocol()) {
std::thread runthread(&MulticastSender::Run, this);
runthread.detach();
}
void Send(const char* data, const int size) {
std::ostreambuf_iterator<char> out(&Buffer);
std::copy(data, data + size, out);
Socket.async_send_to(Buffer.data(), Endpoint, boost::bind(&MulticastSender::HandleSendTo, this, boost::asio::placeholders::error));
}
};
const int SIZE = 8192;
int main() {
MulticastSender sender("127.0.0.1", "239.255.0.0", 30000);
while(true) {
char* data = (char*)malloc(SIZE);
std::memset(data, 0, SIZE);
sender.Send(data, SIZE);
usleep(250);
free(data);
}
}
The above code still produces a memory leak. I should mention that I am running this on CentOS 6.6 with kernel Linux dev 2.6.32-504.el6.x86_64
and running Boost 1.55.0
. I am observing this simply by watching the process in top
.
However, if I simply move the creation of the MulticastSender
into the while
loop, I no longer observe the memory leak. I am concerned about the speed of the application though, so this is not a valid option.
Memory is not leaking, as there is still a handle to the allocated memory. However, there will be continual growth because:
io_service
is not running because run()
is returning as there is no work. This results in completion handlers being allocated, queued into the io_service
, but neither executed nor freed. Additionally, any cleanup that is expected to occur within the completion handler is not occurring. It is worth noting that during the destruction of the io_service
, completion handlers will be destroyed and not invoked; hence, one cannot depend on only performing cleanup within the execution of the completion handler. For more details as to when io_service::run()
blocks or unblocks, consider reading this question.streambuf
's input sequence is never being consumed. Each iteration in the main loop will append to the streambuf
, which will then send the prior message content and the newly appended data. See this answer for more details on the overall usage of streambuf
.A few other points:
async_send_to()
, where ownership of the underlying buffer memory is retained by the caller, who must guarantee that it remains valid until the handler is called. In this case, when copying into the streambuf
via the ostreambuf_iterator
, the streambuf
's input sequence is modified and invalidates the buffer returned from streambuf.data()
.io_service
. Otherwise, undefined behavior may be invoked.To resolve these issues, consider:
boost::asio::io_service::work
to ensure that the io_service
object's run()
does not exit when there is no work remaining.std::shared_ptr
or another class that will manage the memory via resource acquisition is initialization (RAII) idiom. This will allow for proper cleanup and meet the requirement's of the buffer validity for async_send_to()
.Here is a complete example based on the original that demonstrates these changes:
#include <string>
#include <iostream>
#include <thread>
#include <boost/asio.hpp>
class multicast_sender
{
public:
multicast_sender(
const std::string& address,
const std::string& multicast_address,
const unsigned short multicast_port)
: work_(io_service_),
multicast_endpoint_(
boost::asio::ip::address::from_string(multicast_address),
multicast_port),
socket_(io_service_, boost::asio::ip::udp::endpoint(
boost::asio::ip::address::from_string(address),
0 /* any port */))
{
// Start running the io_service. The work_ object will keep
// io_service::run() from returning even if there is no real work
// queued into the io_service.
auto self = this;
work_thread_ = std::thread([self]()
{
self->io_service_.run();
});
}
~multicast_sender()
{
// Explicitly stop the io_service. Queued handlers will not be ran.
io_service_.stop();
// Synchronize with the work thread.
work_thread_.join();
}
void send(const char* data, const int size)
{
// Caller may delete before the async operation finishes, so copy the
// buffer and associate it to the completion handler's lifetime. Note
// that the completion may not run in the event the io_servie is
// destroyed, but the the completion handler will be, so managing via
// a RAII object (std::shared_ptr) is ideal.
auto buffer = std::make_shared<std::string>(data, size);
socket_.async_send_to(boost::asio::buffer(*buffer), multicast_endpoint_,
[buffer](
const boost::system::error_code& error,
std::size_t bytes_transferred)
{
std::cout << "Wrote " << bytes_transferred << " bytes with " <<
error.message() << std::endl;
});
}
private:
boost::asio::io_service io_service_;
boost::asio::io_service::work work_;
boost::asio::ip::udp::endpoint multicast_endpoint_;
boost::asio::ip::udp::socket socket_;
std::thread work_thread_;
};
const int SIZE = 8192;
int main()
{
multicast_sender sender("127.0.0.1", "239.255.0.0", 30000);
char* data = (char*) malloc(SIZE);
std::memset(data, 0, SIZE);
sender.send(data, SIZE);
free(data);
// Give some time to allow for the async operation to complete
// before shutting down the io_service.
std::this_thread::sleep_for(std::chrono::seconds(2));
}
Output:
Wrote 8192 bytes with Success
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With