Handling exceptions and errors

Sometimes, our code will throw an exception or error at runtime. As you may remember in our discussion of the lexical.cpp in Chapter 3, Introducing the Boost C++ Libraries, we must sometimes use exception handling in our code, and we will now dig it up to delve into exception and error handling.

Handling an exception

An exception is a way of reacting to a situation in which the code has exceptional circumstances by transferring control to the handler. To handle the exception, we need to use the try-catch block in our code; then, if an exceptional circumstance arises, an exception will be thrown to the exception handler.

Now, take a look at the following code to see how exception handling is used:

/* exception.cpp */
#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/bind.hpp>
#include <iostream>

boost::mutex global_stream_lock;

void WorkerThread(boost::shared_ptr<boost::asio::io_service> iosvc, int counter) {
  global_stream_lock.lock();
  std::cout << "Thread " << counter << " Start.
";
  global_stream_lock.unlock();

  try {
    iosvc->run();

    global_stream_lock.lock();
    std::cout << "Thread " << counter << " End.
";
    global_stream_lock.unlock();
  }
  catch(std::exception & ex) {
    global_stream_lock.lock();
    std::cout << "Message: " << ex.what() << ".
";
    global_stream_lock.unlock();
  }
}

void ThrowAnException(boost::shared_ptr<boost::asio::io_service> iosvc, int counter) {
  global_stream_lock.lock();
  std::cout << "Throw Exception " << counter << "
" ;
  global_stream_lock.unlock();

  throw(std::runtime_error("The Exception !!!"));
}

int main(void) {
  boost::shared_ptr<boost::asio::io_service> io_svc(
    new boost::asio::io_service
  );

  boost::shared_ptr<boost::asio::io_service::work> worker(
    new boost::asio::io_service::work(*io_svc)
  );

  global_stream_lock.lock();
  std::cout << "The program will exit once all work has finished.
";
  global_stream_lock.unlock();

  boost::thread_group threads;
  for(int i=1; i<=2; i++)
    threads.create_thread(boost::bind(&WorkerThread, io_svc, i));

  io_svc->post(boost::bind(&ThrowAnException, io_svc, 1));
  io_svc->post(boost::bind(&ThrowAnException, io_svc, 2));
  io_svc->post(boost::bind(&ThrowAnException, io_svc, 3));
  io_svc->post(boost::bind(&ThrowAnException, io_svc, 4));
  io_svc->post(boost::bind(&ThrowAnException, io_svc, 5));

  threads.join_all();

  return 0;
}

Save the preceding code as exception.cpp and run the following command to compile it:

g++ -Wall -ansi -I ../boost_1_58_0 exception.cpp -o exception -L ../boost_1_58_0/stage/lib -l boost_system-mgw49-mt-1_58 -l ws2_32 -l libboost_thread-mgw49-mt-1_58

Then, run the program and you should get the following output:

Handling an exception

As we can see, we are not shown the line from std::cout << "Thread " << counter << " End. "; because of the exception. When the work of the io_service object is run, it always throws an exception by using the throw keyword so that the exception will be caught by the catch block within the WorkerThread function, since the iosvc->run() function is inside the try block.

We can also see that although we post work for the io_service object five times, the exception handling only handle two exceptions because once the thread has finished, the join_all() function in the thread will finish the thread and exit the program. In other words, we can say that once the exception is handled, the thread exits to join the call. Additional code that might have thrown an exception will never be called.

How about if we put in the io_service object's work invocation recursively? Will it lead to an infinitely running program? Let us try to throw the exception infinitely. The code will look like the following:

/* exception2.cpp */
#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/bind.hpp>
#include <iostream>

boost::mutex global_stream_lock;

void WorkerThread(boost::shared_ptr<boost::asio::io_service> iosvc, int counter) {
  global_stream_lock.lock();
  std::cout << "Thread " << counter << " Start.
";
  global_stream_lock.unlock();

  try {
    iosvc->run();

    global_stream_lock.lock();
    std::cout << "Thread " << counter << " End.
";
    global_stream_lock.unlock();
  }
  catch(std::exception &ex) {
    global_stream_lock.lock();
    std::cout << "Message: " << ex.what() << ".
";
    global_stream_lock.unlock();
  }
}

void ThrowAnException(boost::shared_ptr<boost::asio::io_service> iosvc) {
  global_stream_lock.lock();
  std::cout << "Throw Exception
" ;
  global_stream_lock.unlock();

  iosvc->post(boost::bind(&ThrowAnException, iosvc));

  throw(std::runtime_error("The Exception !!!"));
}

int main(void) {
  boost::shared_ptr<boost::asio::io_service> io_svc(
    new boost::asio::io_service
  );

  boost::shared_ptr<boost::asio::io_service::work> worker(
    new boost::asio::io_service::work(*io_svc)
  );

  global_stream_lock.lock();
  std::cout << "The program will exit once all work has finished.
";
  global_stream_lock.unlock();

  boost::thread_group threads;
  for(int i=1; i<=5; i++)
    threads.create_thread(boost::bind(&WorkerThread, io_svc, i));

  io_svc->post(boost::bind(&ThrowAnException, io_svc));

  threads.join_all();

  return 0;
}

Save the preceding code as exception2.cpp and compile it by using the following command:

g++ -Wall -ansi -I ../boost_1_58_0 exception2.cpp -o exception2 -L ../boost_1_58_0/stage/lib -l boost_system-mgw49-mt-1_58 -l ws2_32 -l libboost_thread-mgw49-mt-1_58

Now, let us examine the code:

iosvc->post(boost::bind(&ThrowAnException, iosvc));

We add the preceding code snippet inside the ThrowAnException function. Every time the ThrowAnException function is called, it will call itself. Then, it should be an infinite program since there is a recursive function. Let us run the program to prove this by typing the exception2 command in the console window. The output will be like the following:

Handling an exception

Fortunately, the program was able to finish successfully. This happened because the exception propagated through the run() function and the worker threads exited. After that, all the threads finished and the join_all() function was called. That is why the program exits even though there is still work left in the io_service object.

Handling an error

In our previous example, we used the run() function without any parameters, but in fact, the function has two overload methods, std::size_t run() and std::size_t run(boost::system::error_code & ec). The latter method has an error code parameter that will be set if an error occurs.

Now, let us try to use an error code as an input parameter in the run() function. Take a look at the following code:

/* errorcode.cpp */
#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/bind.hpp>
#include <iostream>

boost::mutex global_stream_lock;

void WorkerThread(boost::shared_ptr<boost::asio::io_service> iosvc, int counter) {
  global_stream_lock.lock();
  std::cout << "Thread " << counter << " Start.
";
  global_stream_lock.unlock();

  boost::system::error_code ec;
  iosvc->run(ec);

  if(ec) {
    global_stream_lock.lock();
    std::cout << "Message: " << ec << ".
";
    global_stream_lock.unlock();
  }

  global_stream_lock.lock();
  std::cout << "Thread " << counter << " End.
";
  global_stream_lock.unlock();
}

void ThrowAnException(boost::shared_ptr<boost::asio::io_service> iosvc) {
  global_stream_lock.lock();
  std::cout << "Throw Exception
" ;
  global_stream_lock.unlock();

  iosvc->post(boost::bind(&ThrowAnException, iosvc));

  throw(std::runtime_error("The Exception !!!"));
}

int main(void) {
  boost::shared_ptr<boost::asio::io_service> io_svc(
    new boost::asio::io_service
  );

  boost::shared_ptr<boost::asio::io_service::work> worker(
    new boost::asio::io_service::work(*io_svc)
  );

  global_stream_lock.lock();
  std::cout << "The program will exit once all work has finished.
";
  global_stream_lock.unlock();

  boost::thread_group threads;
  for(int i=1; i<=5; i++)
    threads.create_thread(boost::bind(&WorkerThread, io_svc, i));

  io_svc->post(boost::bind(&ThrowAnException, io_svc));

  threads.join_all();

  return 0;
}

Save the preceding code as errorcode.cpp and use the following command to compile the code:

g++ -Wall -ansi -I ../boost_1_58_0 errorcode.cpp -o errorcode -L ../boost_1_58_0/stage/lib -l boost_system-mgw49-mt-1_58 -l ws2_32 -l libboost_thread-mgw49-mt-1_58

Now, run the program by typing the errorcode command in the console. As a result of doing so, the program will crash. The following screenshot shows the output:

Handling an error

We intend to retrieve the error code by using the following code:

iosvc->run(ec);
 

And we can catch the error by using the if block, as follows:

if(ec)
 

However, in error variable approach, user exceptions translate to boost::asio exceptions; thus, the error variable ec does not interpret the user exception as an error so the exception is not caught by the handler. If the Boost.Asio library needs to throw an error, it will become an exception if there is no error variable, or it will be converted into an error variable. It is better if we keep using the try-catch block to catch any exceptions or errors.

Also, we have to examine the type of exception, which is either system failure or context failure. If it is system failure, then we have to invoke the stop() function in the io_service class to ensure the work object has been destroyed in order for the program to be able to exit. In contrast, if the exception is context failure, we need the worker thread to call the run() function once more in order to prevent the thread from dying. Now, take a look at the following code to understand the concept:

/* errorcode2.cpp */
#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/bind.hpp>
#include <iostream>

boost::mutex global_stream_lock;

void WorkerThread(boost::shared_ptr<boost::asio::io_service> iosvc, int counter) {
  global_stream_lock.lock();
  std::cout << "Thread " << counter << " Start.
";
  global_stream_lock.unlock();

  while(true) {
    try {
      boost::system::error_code ec;
      iosvc->run(ec);
      if(ec) {
        global_stream_lock.lock();
        std::cout << "Error Message: " << ec << ".
";
        global_stream_lock.unlock();
      }
      break;
    }
    catch(std::exception &ex) {
      global_stream_lock.lock();
      std::cout << "Exception Message: " << ex.what() << ".
";
      global_stream_lock.unlock();
    }
  }

  global_stream_lock.lock();
  std::cout << "Thread " << counter << " End.
";
  global_stream_lock.unlock();
}

void ThrowAnException(boost::shared_ptr<boost::asio::io_service> iosvc) {
  global_stream_lock.lock();
  std::cout << "Throw Exception
" ;
  global_stream_lock.unlock();

  iosvc->post(boost::bind(&ThrowAnException, iosvc));

  throw(std::runtime_error("The Exception !!!"));
}

int main(void) {
  boost::shared_ptr<boost::asio::io_service> io_svc(
    new boost::asio::io_service
  );

  boost::shared_ptr<boost::asio::io_service::work> worker(
    new boost::asio::io_service::work(*io_svc)
  );

  global_stream_lock.lock();
  std::cout << "The program will exit once all work has finished.
";
  global_stream_lock.unlock();

  boost::thread_group threads;
  for(int i=1; i<=5; i++)
    threads.create_thread(boost::bind(&WorkerThread, io_svc, i));

  io_svc->post(boost::bind(&ThrowAnException, io_svc));

  threads.join_all();

  return 0;
}

Save the preceding code as errorcode2.cpp and then compile it by executing the following command:

g++ -Wall -ansi -I ../boost_1_58_0 errorcode2.cpp -o errorcode2 -L ../boost_1_58_0/stage/lib -l boost_system-mgw49-mt-1_58 -l ws2_32 -l libboost_thread-mgw49-mt-1_58

If we run the program, we will see that it will not exit, and we have to press Ctrl + C to stop the program:

Handling an error

If we see the following code snippet:

while(true) {
  try {
    . . .
    iosvc->run(ec);
    if(ec)
    . . .
  }
  catch(std::exception &ex) {
    . . .
  }
}

The worker thread is looping. This is also the case when an exception occurs in the output result (indicated by the Throw Exception and the Exception Message: The Exception!!! output). Call the run() function again so it will post a new event to the queue. Of course, we don't want this situation to occur in our application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset