C++ boost threadgroup.interrupt_all() causing main thread to exit too - c++

I'm using the below code to create threads and add them to a thread pool. The threads load fine and each perform a simple looping routine until the main thread calls ResetWorkerThreads a second time and kills off the sub threads. The sub threads are interrupted however the main thread exits also. There are no errors written to console. I can't wrap my head around it as it doesn't appear to have any exception and the main thread has not been added to the vecThreads thread pool. Also the second time this function is all the "All Threads Killed" is not outputted as if it never reaches that point.
std::string strPreviousSettings = "0";
std::string strPreviousAgentSettings = "0";
bool boolResetWorkers;
std::string strIP;
std::string strMACAddress;
boost::thread_group vecThreads;
std::string GetIP()
{
std::string strIP;
try
{
using namespace boost::network;
std::string strRequest;
http::client client;
http::client::request request("http://test.com/ip.php");
http::client::response response = client.get(request);
strIP = body(response);
}
catch(...)
{
cout << "GetLocalIP - Error: " << endl;
}
return strIP;
}
std::string getMacAddress()
{
std::string strMACAddress = GetFileContents("/sys/class/net/eth0/address");
boost::replace_all(strMACAddress, ":", "");
boost::replace_all(strMACAddress, "\n", "");
return strMACAddress;
}
void ThreadSettingsWorker()
{
int x = 1;
strIP = GetIP();
strMACAddress = getMacAddress();
do {
CheckEventSettings();
CheckAgentSettings();
if(boolResetWorkers==true)
{
ResetWorkerThreads();
} else {
boost::this_thread::sleep(boost::posix_time::milliseconds(3000));
}
} while ( x != 0 );
}
void ResetWorkerThreads()
{
cout << "Resetting Workers Threads\n";
boolResetWorkers = false;
int intWorkerCount = 10; //Spawn 10 workers
int X = 0;
int intI = 1;
cout << "Kill All Threads\n";
try
{
vecThreads.interrupt_all();
}
catch(...)
{
//std::cerr << "Kill All Threads: " << std::endl;
}
cout << "All Threads Killed\n";
for (int i = 0; i < intWorkerCount; ++i)
{
cout << "Starting Worker: " << (i + 1) << "\n";
boost::thread tWorker(&ThreadWorker, (i + 1));
vecThreads.add_thread(&tWorker);
}
}
void TestRequest()
{
try
{
using namespace boost::network;
std::stringstream ss;
http::client client;
ss << "http://test.com/sadasdasd.html";
http::client::request request(ss.str());
http::client::response response = client.get(request);
std::string strOutput = body(response);
cout << "Test Request Out: " << strOutput << "\n";
}
catch(...)
{
cout << "TestRequest - Error: " << endl;
return;
}
}
void ThreadWorker(int intThread)
{
try
{
int X = 0;
do {
cout << "Thread " << intThread << "\n";
TestRequest();
} while ( X != 55 );
}
catch(...)
{
}
}
void CheckEventSettings()
{
try
{
using namespace boost::network;
std::string strRequest;
http::client client;
http::client::request request("http://test.com/events.php");
http::client::response response = client.get(request);
std::string strOutput = body(response);
if(strPreviousSettings==strOutput)
{
cout << "No Event Settings Changes\n";
} else {
cout << "Applying New Event Settings\n";
strPreviousSettings = strOutput;
std::string strDividerLine = "<br>";
std::string strDividerField = "<field>";
std::vector<std::string> vEvents;
vEvents = EasySplit(strOutput, strDividerLine);
for(std::vector<std::string>::const_iterator iEvent = vEvents.begin(); iEvent != vEvents.end() - 1; ++iEvent) {
}
}
}
catch(...)
{
cout << "CheckEventSettings - Error: " << endl;
return;
}
}
void CheckAgentSettings()
{
try
{
using namespace boost::network;
std::stringstream ss;
http::client client;
ss << "http://test.com/checksettings.php";
http::client::request request(ss.str());
http::client::response response = client.get(request);
std::string strOutput = body(response);
if(strPreviousAgentSettings==strOutput)
{
cout << "No Agent Settings Changes\n";
} else {
cout << "Applying New Agent Settings\n";
strPreviousAgentSettings = strOutput;
boolResetWorkers = true;
}
}
catch(...)
{
cout << "CheckAgentSettings - Error: " << endl;
return;
}
}
int main()
{
// Start thread
boost::thread tCheckSettings(&ThreadSettingsWorker);
// Ask thread to stop
//tCheckSettings.interrupt();
// Join - wait when thread actually exits
tCheckSettings.join();
return 0;
}

You have an error here:
boost::thread tWorker(&ThreadWorker, (i + 1));
vecThreads.add_thread(&tWorker);
You create a local object tWorker that is deleted just after call to add_thread(). So vecThreads contains the dangling pointers to threads. When you call vecThreads.interrupt_all() you get undefined behavior because vecThreads tries to access the deleted thread objects and I suppose your program just terminates because of access violation or something.
You have to change your code to something like this:
boost::thread* ptWorker = new boost::thread(&ThreadWorker, (i + 1));
vecThreads.add_thread(ptWorker);
Please note that you don't need to delete those thread objects yourself. thread_group will delete them itself.
ADDITION:
The problem with terminate() may be caused by destructor of http::client throwing an exception. Please try this to possibly eliminate that problem in TestRequest():
try{
http::client client;
try{
// other code
}
catch (){}
}
catch(){}
Also I'd suggest resetting vecThreads after interrupt_all(). For example you can define it as boost::scoped_ptr and then do pvecThreads.reset(new boost::thread_group()) after the call to interrupt_all().
At present the interrupted threads still remain in the thread_group after the interruption and then you try to interrupt them again along with the new threads added to the thread_group later in ResetWorkerThreads().

Inside of ResetWorkerThreads You have:
for (int i = 0; i < intWorkerCount; ++i)
{
cout << "Starting Worker: " << (i + 1) << "\n";
// One issue is here, don't create a thread on the stack
// and pass it to the thread group use new instead!
boost::thread tWorker(&ThreadWorker, (i + 1));
vecThreads.add_thread(&tWorker);
}
You are adding a thread created on the stack to the thread group. As soon as you iterate over the loop that threads memory is invalidated. You will need to new the thread and pass that pointer to add_thread.

Related

Sandard way of implementing c++ multi-threading for collecting data streams and processing

I'm new to c++ development. I'm trying to run infinite functions that are independent of each other.
Problem statement is smiliar to this:
The way I'm trying to implement this is
#include <iostream>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>
#include <mutex>
int g_i = 0;
std::mutex g_i_mutex; // protects g_i
// increment g_i by 1
void increment_itr()
{
const std::lock_guard<std::mutex> lock(g_i_mutex);
g_i += 1;
}
void *fun(void *s)
{
std::string str;
str = (char *)s;
std::cout << str << " start\n";
while (1)
{
std::cout << str << " " << g_i << "\n";
if(g_i > 1000) break;
increment_itr();
}
pthread_exit(NULL);
std::cout << str << " end\n";
}
void *checker(void *s) {
while (1) {
if(g_i > 1000) {
std::cout<<"**********************\n";
std::cout << "checker: g_i == 100\n";
std::cout<<"**********************\n";
pthread_exit(NULL);
}
}
}
int main()
{
int itr = 0;
pthread_t threads[3];
pthread_attr_t attr;
void *status;
// Initialize and set thread joinable
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
int rc1 = pthread_create(&threads[0], &attr, fun, (void *)&"foo");
int rc2 = pthread_create(&threads[1], &attr, fun, (void *)&"bar");
int rc3 = pthread_create(&threads[2], &attr, checker, (void *)&"checker");
if (rc1 || rc2 || rc3)
{
std::cout << "Error:unable to create thread," << rc1 << rc2 << rc3 << std::endl;
exit(-1);
}
pthread_attr_destroy(&attr);
std::cout << "main func continues\n";
for (int i = 0; i < 3; i++)
{
rc1 = pthread_join(threads[i], &status);
if (rc1)
{
std::cout << "Error:unable to join," << rc1 << std::endl;
exit(-1);
}
std::cout << "Main: completed thread id :" << i;
std::cout << " exiting with status :" << status << std::endl;
}
std::cout << "main end\n";
return 0;
}
This works, but I want to know if this implementation is a standard approach to do this or this can be done in any better way?
You correctly take a lock inside increment_itr, but your fun function is accessing g_i without acquiring the lock.
Change this:
void increment_itr()
{
const std::lock_guard<std::mutex> lock(g_i_mutex);
g_i += 1;
}
To this
int increment_itr()
{
std::lock_guard<std::mutex> lock(g_i_mutex); // the const wasn't actually needed
g_i = g_i + 1;
return g_i; // return the updated value of g_i
}
This is not thread safe:
if(g_i > 1000) break; // access g_i without acquiring the lock
increment_itr();
This this is better:
if (increment_itr() > 1000) {
break;
}
Similar fix is needed in checker:
void *checker(void *s) {
while (1) {
int i;
{
std::lock_guard<std::mutex> lock(g_i_mutex);
i = g_i;
}
if(i > 1000) {
std::cout<<"**********************\n";
std::cout << "checker: g_i == 100\n";
std::cout<<"**********************\n";
break;
}
return NULL;
}
As to your design question. Here's the fundamental issue.
You're proposing a dedicated thread that continuously takes a lock and would does some sort checking on a data structure. And if a certain condition is met, it would do some additional processing such as writing to a database. The thread spinning in an infinite loop would be wasteful if nothing in the data structure (the two maps) has changed. Instead, you only want your integrity check to run when something changes. You can use a condition variable to have the checker thread pause until something actually changes.
Here's a better design.
uint64_t g_data_version = 0;
std::conditional_variable g_cv;
void *fun(void *s)
{
while (true) {
<< wait for data from the source >>
{
std::lock_guard<std::mutex> lock(g_i_mutex);
// update the data in the map while under a lock
// e.g. g_n++;
//
// increment the data version to signal a new revision has been made
g_data_version += 1;
}
// notify the checker thread that something has changed
g_cv.notify_all();
}
}
Then your checker function only wakes up when it fun signals it to say something has changed.
void *checker(void *s) {
while (1) {
// lock the mutex
std::unique_lock<std::mutex> lock(g_i_mutex);
// do the data comparison check here
// now wait for the data version to change
uint64_t version = g_data_version;
while (version != g_data_version) { // check for spurious wake up
cv.wait(lock); // this atomically unlocks the mutex and waits for a notify() call on another thread to happen
}
}
}

Mutlithreading: synchronize threads to perform several steps race condition

I want to create 15 threads and have them performed 4 successive steps (that I call Init, Process, Terminate and WriteOutputs).
For each step I want all threads to finish it before passing to the following step.
I am trying to implement it (cf code below) using a std::condition_variable and calling the wait() and notify_all() methods but somehow I do not manage to do it
and even worse I have a race condition
when counting the number of operations done (which should be 15*4 = 60) I sometimes have some prints that are indeed not printed and the m_counter in my class at the end is less than 60 which should not be the case
I use two std::mutex objects: one for printing messages and another one for the step synchronization
Could someone explain to me the problem?
What would be a solution ?
Many thanks in advance
#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<vector>
#include<functional>
class MTHandler
{
public:
MTHandler(){
// 15 threads
std::function<void(int)> funcThread = std::bind(&MTHandler::ThreadFunction, this, std::placeholders::_1);
for (int i=0; i<15; i++){
m_vectThreads.push_back(std::thread(funcThread,i));
}
for (std::thread & th : m_vectThreads) {
th.join();
}
std::cout << "m_counter = " << m_counter << std::endl;
}
private:
enum class ManagerStep{
Init,
Process,
Terminate,
WriteOutputs,
};
std::vector<ManagerStep> m_vectSteps = {
ManagerStep::Init,
ManagerStep::Process,
ManagerStep::Terminate,
ManagerStep::WriteOutputs
};
unsigned int m_iCurrentStep = 0 ;
unsigned int m_counter = 0;
std::mutex m_mutex;
std::mutex m_mutexStep;
std::condition_variable m_condVar;
bool m_finishedAllSteps = false;
unsigned int m_nThreadsFinishedStep = 0;
std::vector<std::thread> m_vectThreads = {};
void ThreadFunction (int id) {
while(!m_finishedAllSteps){
m_mutex.lock();
m_counter+=1;
m_mutex.unlock();
switch (m_vectSteps[m_iCurrentStep])
{
case ManagerStep::Init:{
m_mutex.lock();
std::cout << "thread " << id << " --> Init step" << "\n";
m_mutex.unlock();
break;
}
case ManagerStep::Process:{
m_mutex.lock();
std::cout << "thread " << id << " --> Process step" << "\n";
m_mutex.unlock();
break;
}
case ManagerStep::Terminate:{
m_mutex.lock();
std::cout << "thread " << id << " --> Terminate step" << "\n";
m_mutex.unlock();
break;
}
case ManagerStep::WriteOutputs:{
m_mutex.lock();
std::cout << "thread " << id << " --> WriteOutputs step" << "\n";
m_mutex.unlock();
break;
}
default:
{
break;
}
}
unsigned int iCurrentStep = m_iCurrentStep;
bool isCurrentStepFinished = getIsFinishedStatus();
if (!isCurrentStepFinished){
// wait for other threads to finish current step
std::unique_lock<std::mutex> lck(m_mutexStep);
m_condVar.wait(lck, [iCurrentStep,this]{return iCurrentStep != m_iCurrentStep;});
}
}
}
bool getIsFinishedStatus(){
m_mutexStep.lock();
bool isCurrentStepFinished = false;
m_nThreadsFinishedStep +=1;
if (m_nThreadsFinishedStep == m_vectThreads.size()){
// all threads have completed the current step
// pass to the next step
m_iCurrentStep += 1;
m_nThreadsFinishedStep = 0;
m_finishedAllSteps = (m_iCurrentStep == m_vectSteps.size());
isCurrentStepFinished = true;
}
if (isCurrentStepFinished){m_condVar.notify_all();}
m_mutexStep.unlock();
return isCurrentStepFinished;
}
};
int main ()
{
MTHandler mt;
return 0;
}

Making my function which calls async_read asynchronous Boost::asio

I am building an networking application, and being a newbie to Boost asio and networking as a whole had this doubt which might be trivial. I have this application which reads from a file and calls apis accordingly. I am reading json (example):
test.json
{
"commands":
[
{
"type":"login",
"Username": 0,
"Password": "kk"
}
]
}
My main program looks like this :
int main() {
ba::io_service ios;
tcp::socket s(ios);
s.connect({{},8080});
IO io;
io.start_read(s);
io.interact(s);
ios.run();
}
void start_read(tcp::socket& socket) {
char buffer_[MAX_LEN];
socket.async_receive(boost::asio::null_buffers(),
[&](const boost::system::error_code& ec, std::size_t bytes_read) {
(void)bytes_read;
if (likely(!ec)) {
boost::system::error_code errc;
int br = 0;
do {
br = socket.receive(boost::asio::buffer(buffer_, MAX_LEN), 0, errc);
if (unlikely(errc)) {
if (unlikely(errc != boost::asio::error::would_block)) {
if (errc != boost::asio::error::eof)
std::cerr << "asio async_receive: error " << errc.value() << " ("
<< errc.message() << ")" << std::endl;
interpret_read(socket,nullptr, -1);
//close(as);
return;
}
break; // EAGAIN
}
if (unlikely(br <= 0)) {
std::cerr << "asio async_receive: error, read " << br << " bytes" << std::endl;
interpret_read(socket,nullptr, br);
//close(as);
return;
}
interpret_read(socket,buffer_, br);
} while (br == (int)MAX_LEN);
} else {
if (socket.is_open())
std::cerr << "asio async_receive: error " << ec.value() << " (" << ec.message() << ")"
<< std::endl;
interpret_read(socket,nullptr, -1);
//close(as);
return;
}
start_read(socket);
});
}
void interpret_read(tcp::socket& s,const char* buf, int len) {
if(len<0)
{
std::cout<<"some error occured in reading"<<"\n";
}
const MessageHeaderOutComp *obj = reinterpret_cast<const MessageHeaderOutComp *>(buf);
int tempId = obj->TemplateID;
//std::cout<<tempId<<"\n";
switch(tempId)
{
case 10019: //login
{
//const UserLoginResponse *obj = reinterpret_cast<const UserLoginResponse *>(buf);
std::cout<<"*********[SERVER]: LOGIN ACKNOWLEDGEMENT RECEIVED************* "<<"\n";
break;
}
}
std::cout << "RX: " << len << " bytes\n";
if(this->input_type==2)
interact(s);
}
void interact(tcp::socket& s)
{
if(this->input_type == -1){
std::cout<<"what type of input you want ? option 1 : test.json / option 2 : manually through command line :";
int temp;
std::cin>>temp;
this->input_type = temp;
}
if(this->input_type==1)
{
//std::cout<<"reading from file\n";
std::ifstream input_file("test.json");
Json::Reader reader;
Json::Value input;
reader.parse(input_file, input);
for(auto i: input["commands"])
{
std::string str = i["type"].asString();
if(str=="login")
this->login_request(s,i);
}
std::cout<<"File read completely!! \n Do you want to continue or exit?: ";
}
}
The sending works fine, the message is sent and the server responds in a correct manner, but what I need to understand is why is the control not going to on_send_completed (which prints sent x bytes). Neither it prints the message [SERVER]: LOGIN ACKNOWLEDGEMENT RECEIVED, I know I am missing something basic or am doing something wrong, please correct me.
login_request function:
void login_request(tcp::socket& socket,Json::Value o) {
/*Some buffer being filled*/
async_write(socket, boost::asio::buffer(&info, sizeof(info)), on_send_completed);
}
Thanks in advance!!
From a cursory scan it looks like you redefined buffer_ that was already a class member (of IO, presumably).
It's hidden by the local in start_read, which is both UB (because the lifetime ends before the async read operation completes) and also makes it so the member _buffer isn't used.
I see a LOT of confusing code though. Why are you doing synchronous reads from within completion handlers?
I think you might be looking for the composed-ooperation reads (boost::asio::async_read and boost::asio::async_until)

Program with a separate thread is being killed

After declaring an object in my main and running its function in a separate thread, my program crashes.
I have read other question on SO but due to my lack of knowledge in multithreading, I cannot understand what is my specific problem.
Here is my class called UART (without header files and only showing the required cpp declaration):
void UART::run()
{
while(true)
{
_letter = _serial.read();
if (_letter == "!")
{
_line = _serial.readline();
_words.clear();
std::istringstream f(_line);
std::string s;
//std::cout << _letter << std::endl;
while (getline(f,s,'\t'))
{
_words.push_back(s);
}
this->fillVars();
}
}
}
void UART::fillVars()
{
if (_words[0] == "s")
{
_effort[0] = std::stoi(_words[1]);
_effort[1] = std::stoi(_words[2]);
}
else if (_words[0] == "e")
{
this->convertToMeters();
}
}
void UART::convertToMeters()
{
std::cout << _position[0];
_position[0] = std::stod(_words[1]); // / _tick_meters;
_position[1] = std::stod(_words[2]) / _tick_meters;
}
double UART::getPosition(std::string wheel)
{
if (wheel == "LEFT") return _position[0];
else return _position[1];
}
And my main cpp looks like this:
int main(int argc, char** argv)
{
ros::init(argc, argv, "joint_node");
std::string port("/dev/ttyACM0");
unsigned long baud = 115200;
try
{
serial::Serial my_serial(port, baud, serial::Timeout::simpleTimeout(1000));
if(my_serial.isOpen()) ROS_INFO("Serial is %s", "open");
genius::UART uart(my_serial, 380);
std::thread uart_run(&genius::UART::run, uart);
std::cout << uart.getPosition("LEFT") <<std::endl;
} catch (std::exception &e)
{
std::cerr << "Unhandled Exception: " << e.what() << std::endl;
}
return 0;
}
My understanding is that after creating an object uart, I want to run its function run() in a separate thread as I want its values to be updated with no interruption on a background. So whenever I access its values like using its function uart.getPosition("LEFT") I will get the last up-to date data. I guess I do not need .join() this thread as I do not want to wait for it as it never ends.
But for some reason after calling the uart.getPosition("LEFT") my program crashes and also function getPosition() never gets executed and I always get value of 0.

What's wrong with this boost::asio and boost::coroutine usage pattern?

In this question I described boost::asio and boost::coroutine usage pattern which causes random crashes of my application and I published extract from my code and valgrind and GDB output.
In order to investigate the problem further I created smaller proof of concept application which applies the same pattern. I saw that the same problem arises in the smaller program which source I publish here.
The code starts a few threads and creates a connection pool with a few dummy connections (user supplied numbers). Additional arguments are unsigned integer numbers which plays the role of fake requests. The dummy implementation of sendRequest function just starts asynchronous timer for waiting number of seconds equal to the input number and yileds from the function.
Can someone see the problem with this code and can he propose some fix for it?
#include "asiocoroutineutils.h"
#include "concurrentqueue.h"
#include <iostream>
#include <thread>
#include <boost/lexical_cast.hpp>
using namespace std;
using namespace boost;
using namespace utils;
#define id this_thread::get_id() << ": "
// ---------------------------------------------------------------------------
/*!
* \brief This is a fake Connection class
*/
class Connection
{
public:
Connection(unsigned connectionId)
: _id(connectionId)
{
}
unsigned getId() const
{
return _id;
}
void sendRequest(asio::io_service& ioService,
unsigned seconds,
AsioCoroutineJoinerProxy,
asio::yield_context yield)
{
cout << id << "Connection " << getId()
<< " Start sending: " << seconds << endl;
// waiting on this timer is palceholder for any asynchronous operation
asio::steady_timer timer(ioService);
timer.expires_from_now(chrono::seconds(seconds));
coroutineAsyncWait(timer, yield);
cout << id << "Connection " << getId()
<< " Received response: " << seconds << endl;
}
private:
unsigned _id;
};
typedef std::unique_ptr<Connection> ConnectionPtr;
typedef std::shared_ptr<asio::steady_timer> TimerPtr;
// ---------------------------------------------------------------------------
class ConnectionPool
{
public:
ConnectionPool(size_t connectionsCount)
{
for(size_t i = 0; i < connectionsCount; ++i)
{
cout << "Creating connection: " << i << endl;
_connections.emplace_back(new Connection(i));
}
}
ConnectionPtr getConnection(TimerPtr timer,
asio::yield_context& yield)
{
lock_guard<mutex> lock(_mutex);
while(_connections.empty())
{
cout << id << "There is no free connection." << endl;
_timers.emplace_back(timer);
timer->expires_from_now(
asio::steady_timer::clock_type::duration::max());
_mutex.unlock();
coroutineAsyncWait(*timer, yield);
_mutex.lock();
cout << id << "Connection was freed." << endl;
}
cout << id << "Getting connection: "
<< _connections.front()->getId() << endl;
ConnectionPtr connection = std::move(_connections.front());
_connections.pop_front();
return connection;
}
void addConnection(ConnectionPtr connection)
{
lock_guard<mutex> lock(_mutex);
cout << id << "Returning connection " << connection->getId()
<< " to the pool." << endl;
_connections.emplace_back(std::move(connection));
if(_timers.empty())
return;
auto timer = _timers.back();
_timers.pop_back();
auto& ioService = timer->get_io_service();
ioService.post([timer]()
{
cout << id << "Wake up waiting getConnection." << endl;
timer->cancel();
});
}
private:
mutex _mutex;
deque<ConnectionPtr> _connections;
deque<TimerPtr> _timers;
};
typedef unique_ptr<ConnectionPool> ConnectionPoolPtr;
// ---------------------------------------------------------------------------
class ScopedConnection
{
public:
ScopedConnection(ConnectionPool& pool,
asio::io_service& ioService,
asio::yield_context& yield)
: _pool(pool)
{
auto timer = make_shared<asio::steady_timer>(ioService);
_connection = _pool.getConnection(timer, yield);
}
Connection& get()
{
return *_connection;
}
~ScopedConnection()
{
_pool.addConnection(std::move(_connection));
}
private:
ConnectionPool& _pool;
ConnectionPtr _connection;
};
// ---------------------------------------------------------------------------
void sendRequest(asio::io_service& ioService,
ConnectionPool& pool,
unsigned seconds,
asio::yield_context yield)
{
cout << id << "Constructing request ..." << endl;
AsioCoroutineJoiner joiner(ioService);
ScopedConnection connection(pool, ioService, yield);
asio::spawn(ioService, bind(&Connection::sendRequest,
connection.get(),
std::ref(ioService),
seconds,
AsioCoroutineJoinerProxy(joiner),
placeholders::_1));
joiner.join(yield);
cout << id << "Processing response ..." << endl;
}
// ---------------------------------------------------------------------------
void threadFunc(ConnectionPool& pool,
ConcurrentQueue<unsigned>& requests)
{
try
{
asio::io_service ioService;
while(true)
{
unsigned request;
if(!requests.tryPop(request))
break;
cout << id << "Scheduling request: " << request << endl;
asio::spawn(ioService, bind(sendRequest,
std::ref(ioService),
std::ref(pool),
request,
placeholders::_1));
}
ioService.run();
}
catch(const std::exception& e)
{
cerr << id << "Error: " << e.what() << endl;
}
}
// ---------------------------------------------------------------------------
int main(int argc, char* argv[])
{
if(argc < 3)
{
cout << "Usage: ./async_request poolSize threadsCount r0 r1 ..."
<< endl;
return -1;
}
try
{
auto poolSize = lexical_cast<size_t>(argv[1]);
auto threadsCount = lexical_cast<size_t>(argv[2]);
ConcurrentQueue<unsigned> requests;
for(int i = 3; i < argc; ++i)
{
auto request = lexical_cast<unsigned>(argv[i]);
requests.tryPush(request);
}
ConnectionPoolPtr pool(new ConnectionPool(poolSize));
vector<unique_ptr<thread>> threads;
for(size_t i = 0; i < threadsCount; ++i)
{
threads.emplace_back(
new thread(threadFunc, std::ref(*pool), std::ref(requests)));
}
for_each(threads.begin(), threads.end(), mem_fn(&thread::join));
}
catch(const std::exception& e)
{
cerr << "Error: " << e.what() << endl;
}
return 0;
}
Here are some helper utilities used by the above code:
#pragma once
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/spawn.hpp>
namespace utils
{
inline void coroutineAsyncWait(boost::asio::steady_timer& timer,
boost::asio::yield_context& yield)
{
boost::system::error_code ec;
timer.async_wait(yield[ec]);
if(ec && ec != boost::asio::error::operation_aborted)
throw std::runtime_error(ec.message());
}
class AsioCoroutineJoiner
{
public:
explicit AsioCoroutineJoiner(boost::asio::io_service& io)
: _timer(io), _count(0) {}
void join(boost::asio::yield_context yield)
{
assert(_count > 0);
_timer.expires_from_now(
boost::asio::steady_timer::clock_type::duration::max());
coroutineAsyncWait(_timer, yield);
}
void inc()
{
++_count;
}
void dec()
{
assert(_count > 0);
--_count;
if(0 == _count)
_timer.cancel();
}
private:
boost::asio::steady_timer _timer;
std::size_t _count;
}; // AsioCoroutineJoiner class
class AsioCoroutineJoinerProxy
{
public:
AsioCoroutineJoinerProxy(AsioCoroutineJoiner& joiner)
: _joiner(joiner)
{
_joiner.inc();
}
AsioCoroutineJoinerProxy(const AsioCoroutineJoinerProxy& joinerProxy)
: _joiner(joinerProxy._joiner)
{
_joiner.inc();
}
~AsioCoroutineJoinerProxy()
{
_joiner.dec();
}
private:
AsioCoroutineJoiner& _joiner;
}; // AsioCoroutineJoinerProxy class
} // utils namespace
For completeness of the code the last missing part is ConcurrentQueue class. It is too long to paste it here, but if you want you can find it here.
Example usage of the application is:
./connectionpooltest 3 3 5 7 8 1 0 9 2 4 3 6
where the first number 3 are fake connections count and the second number 3 are the number of used threads. Numbers after them are fake requests.
The output of valgrind and GDB is the same as in the mentioned above question.
Used version of boost is 1.57. The compiler is GCC 4.8.3. The operating system is CentOS Linux release 7.1.1503
It seems that all valgrind errors are caused because of BOOST_USE_VALGRIND macro is not defined as Tanner Sansbury points in comment related to this question. It seems that except this the program is correct.