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.
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:
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:
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.
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:
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:
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.