pthread_cancel() и C++
С приходом в нашу жизнь реализации Linux тредов NPTL, стало доступно досрочное завершение тредов. Представьте себе ситуацию, когда у Вас есть треды с блокирующими системными вызовами или просто треды, использующие sleep() (что ещё хуже) и Вам вдруг понадобилось их завершить. В случае, если Вы программируете на С, скорей всего проблем не будет. Грабли начинают когда Вы решаете сделать тоже самое в C++.
Что может случиться плохого? Такой подход соврешенно не совместим с RAII.
Рассмотрим пример, который гарантированно падает.
Вот результат выполнения на Ubuntu 15.10:
Что же происходит в этом коде такого, что необходимо завершить выполнение? К счастью, в Ubuntu 15.10 хороший вывод ошибок и ясно, что виновен не пойманый exception. Но где?
При вызове любого системного вызова отмеченного как "Cancellation point".
Давайте разберём ещё раз, с самого начала. Мы создаём объект класса Object, внутри треда, который спустя секунду, будет отдан на растерзание pthread_cancel(). Внуртри этого же треда, мы кидаем exception, что приводит к stack unwinding и вызову деструкторов объектов на стеке и функция sleep(), переходя в kernel mode и являесь при этом cancellation point, благодаря C++ -- кидает exception, во время раскрутки стека, что и приводит к гарантированному падению нашей программы.
Не верите? :-) Попробуйте такой код:
В результате будет такой вывод:
Поражены? И это в деструкторе объекта! Как Вы понимаете, о RAII с таким подходом можно забыть. Вы наверняка захотите использовать такие системные вызовы как fclose(), close(), dlclose() в деструкторе Вашего объекта. Список cancellation points достаточно обширен!
Всё, что вы можете -- попытаться контролировать процесс вхожения в cancellation point, используя pthread_testcancel(), которая создаст cancellation point до входа в опасную область видимости. При этом, я сам не уверен, что эта тактика оправданна и вообще имеет право на жизнь.
Интересно, что сам этот "exception" добавлеяет ИМЕННО C++ и его тип определён в хедере cxxabi.h, что значит, что его вполне реально поймать.
Вывод:
На самом деле, это не столько "exception", сколько механизм для stack unwinding, о чём можно сделать вывод из названия типа "__forced_unwind". Следовательно, раз мы можем его поймать, мы можем его и кинуть далее и даже обработать!
Покрайней мере, программа уже не падает :-)
Что и требовалось доказать. Использование pthread_cancelation -- весьма сомнительная возможноcть NPTL. Возможно, использование её в языке C оправданно, но в C++, в котором наличествует огромное количество уже готовых типов, которые используют различные системные вызовы, ничего не подозревая о новшествах реализации -- лучше воспользоваться функционалом предоставляемым libstdc++ ну или вообще, BOOST.
Что может случиться плохого? Такой подход соврешенно не совместим с RAII.
Рассмотрим пример, который гарантированно падает.
- #include <unistd.h>
- #include <pthread.h>
- #include <iostream>
- #include <exception>
- class Object
- {
- public:
- ~Object() {
- std::cout << "Inside ~Object()" << std::endl;
- sleep ( 999 );
- }
- };
- void newThread()
- {
- try {
- Object regular_object_on_stack;
- std::cout << "Inside the new thread" << std::endl;
- // ... billion strings of code ...
- throw std::runtime_error ( "Random exception" );
- } catch ( const std::runtime_error& e ) {
- std::cout << "You will never see this exception: " << e.what() << std::endl;
- }
- }
- int main()
- {
- pthread_t thread;
- pthread_create ( &thread, nullptr, &newThread, nullptr );
- sleep ( 1 ); // time for new thread for correct init order
- std::cout << "Let's try to cancel our thread ..." << std::endl;
- pthread_cancel ( thread );
- pthread_join ( thread, nullptr );
- std::cout << "You have a good chance not to see this message" << std::endl;
- return 0;
- }
- Inside the new thread
- Inside ~Object()
- Let's try to cancel our thread ...
- terminate called without an active exception
- Aborted (core dumped)
Давайте разберём ещё раз, с самого начала. Мы создаём объект класса Object, внутри треда, который спустя секунду, будет отдан на растерзание pthread_cancel(). Внуртри этого же треда, мы кидаем exception, что приводит к stack unwinding и вызову деструкторов объектов на стеке и функция sleep(), переходя в kernel mode и являесь при этом cancellation point, благодаря C++ -- кидает exception, во время раскрутки стека, что и приводит к гарантированному падению нашей программы.
Не верите? :-) Попробуйте такой код:
- #include <unistd.h>
- #include <pthread.h>
- #include <iostream>
- #include <exception>
- class Object
- {
- public:
- ~Object() {
- std::cout << "Inside ~Object()" << std::endl;
- try {
- sleep ( 999 );
- } catch ( ... ) {
- std::cout << "We got something strange here ..." << std::endl;
- }
- }
- };
- void newThread()
- {
- try {
- Object regular_object_on_stack;
- std::cout << "Inside the new thread" << std::endl;
- // ... billion strings of code ...
- throw std::runtime_error ( "Random exception" );
- } catch ( std::runtime_error& e ) {
- std::cout << "You will never see this exception: " << e.what() << std::endl;
- }
- }
- int main()
- {
- pthread_t thread;
- pthread_create ( &thread, nullptr, &newThread, nullptr );
- sleep ( 1 ); // time for new thread for correct init order
- std::cout << "Let's try to cancel our thread ..." << std::endl;
- pthread_cancel ( thread );
- pthread_join ( thread, nullptr );
- std::cout << "You have a good chance not to see this message" << std::endl;
- return 0;
- }
- Inside the new thread
- Inside ~Object()
- Let's try to cancel our thread ...
- We got something strange here ...
- FATAL: exception not rethrown
- Aborted (core dumped)
Всё, что вы можете -- попытаться контролировать процесс вхожения в cancellation point, используя pthread_testcancel(), которая создаст cancellation point до входа в опасную область видимости. При этом, я сам не уверен, что эта тактика оправданна и вообще имеет право на жизнь.
Интересно, что сам этот "exception" добавлеяет ИМЕННО C++ и его тип определён в хедере cxxabi.h, что значит, что его вполне реально поймать.
- #include <unistd.h>
- #include <pthread.h>
- #include <iostream>
- #include <exception>
- #include <cxxabi.h>
- void newThread()
- {
- try {
- sleep ( 999 );
- std::cout << "Inside the new thread" << std::endl;
- // ... billion strings of code ...
- throw std::runtime_error ( "Random exception" );
- } catch ( abi::__forced_unwind& ) {
- std::cout << "abi::__forced_unwind is catched" << std::endl;
- }
- }
- int main()
- {
- pthread_t thread;
- pthread_create ( &thread, nullptr, &newThread, nullptr );
- sleep ( 1 ); // time for new thread for correct init order
- std::cout << "Let's try to cancel our thread ..." << std::endl;
- pthread_cancel ( thread );
- pthread_join ( thread, nullptr );
- std::cout << "You have a good chance not to see this message" << std::endl;
- return 0;
- }
- Let's try to cancel our thread ...
- abi::__forced_unwind is catched
- FATAL: exception not rethrown
- Aborted (core dumped)
- #include <unistd.h>
- #include <pthread.h>
- #include <iostream>
- #include <exception>
- #include <cxxabi.h>
- void newThread()
- {
- try {
- sleep ( 999 );
- std::cout << "Inside the new thread" << std::endl;
- // ... billion strings of code ...
- throw std::runtime_error ( "Random exception" );
- } catch ( abi::__forced_unwind& ) {
- std::cout << "rethrowing exception ..." << std::endl;
- throw;
- } catch ( ... ) {
- std::cout << "Catch any exception" << std::endl;
- }
- }
- int main()
- {
- pthread_t thread;
- pthread_create ( &thread, nullptr, &newThread, nullptr );
- sleep ( 1 ); // time for new thread for correct init order
- std::cout << "Let's try to cancel our thread ..." << std::endl;
- pthread_cancel ( thread );
- pthread_join ( thread, nullptr );
- std::cout << "You have a good chance not to see this message" << std::endl;
- return 0;
- }
Покрайней мере, программа уже не падает :-)- Let's try to cancel our thread ...
- rethrowing exception ...
- You have a good chance not to see this message