FreeBSD, GCC, Epic fail
Jan. 26th, 2013 12:11 am![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Цілий тиждень, починаючи з минулої неділі, я кожного вечора після роботи сідав за комп, логінився через два шелла на віддалений сервер і боровся з багом.
Є у мене один секретний проект який потихеньку починає виходити у люди, і от треба було запустити його на FreeBSD. На лінуксах все працювало нормально і нічого не віщало біди...
Проект використовує моднявий нині протокол серіалізації і RPC Thrift (розроблений у Facebook і відданий суспільству у Apache). Реалізація протоколу виявилась гнилою, паскудною, і нагадує вихлоп трудів сотень макак яких випадково пустили за клавіатури. Чого тільки варте змішання стилів, використання std::tr1::function, std::auto_ptr, boost::shared_ptr? А низькорівнева робота з мережею, при живому Boost.ASIO? І все це на фоні сотень фабрик і адапторів. Та що там казати...
Але, принаймні, все те кубло змій, жаб і червів працювало. А під FreeBSD раптом відмовило. Точніше не зовсім відмовило, воно продовжило працювати рівно до тих пір поки клієнт залишається під’єднаним. Як тільки клієнт від’єднуєьтся — все. Хана. Приблизно така:
— там ще близько двадцяти фреймів стеку. Ну, власне, зрозуміло в чому проблема — неловлений exception. Та от тільки... exception насправді ловлений. Спочатку строго по типу, потім по std::exception і в кінці кінців по трьом крапкам:
Розумієте? Exception пролітає через три крапки як вода крізь сито! І навіть коли я ззовні поставив ще один try/catch — нічого не змінилось! Звісно я грішив на кубло змій. В такій каші я варився п’ять днів. І зварився. Вчора прийшов і просто опустив руки. Бо перевірив усе і навіть під valgrind поганяв. Ніц не знайшов. Все чисто. І багтрекери всі перерив, і багато схожих проблем бачив, але жодна із них не мала до мене ніякого відношення. Я просто не знав що робити.
При чому exceptions із мого коду ловились нормально. І я почав гратися — весь цей thrift жив у плагіні у вигляді shared object, я почав туди додавати свої функції і методи які просто кидали exception. І дивитись що вийде. І якось доекспериментувався до того, що якщо просто викликати функції що кидає exception то не зважаючи на try/catch матиму abort. А якщо одразу після (sic!) цієї функції кинути ще одне exception — все починає працювати! Просто чорна магія.
Сьогодні продовжив гратись і експериментувати. І знову таки випадково визначив, що все падає коли на рівні вище від функції що кидає exception є boost::shared_ptr. Почав грішити на Boost (ну тіпа там exception у деструкторі під час розгортання стеку), колупатись в коді і багтреккері. Без толку. Потім після багатьох невдалих спроб мені вдалось відтворити проблему не на великому проекті (який я на той час уже добряче пошматував), а на відносно невеликому тестовому. І я визначив що падає не тільки коли маю boost::shared_ptr а взагалі коли маю будь-який об’єкт з custom destructor. Навіть якщо той destructor пустий. З дефолтовим не падає.
Enjoy:
Зі шлангом теж, звісно, все в порядку. Але там свої заморочки...
Вся ця історія коштувала мені недосипання, червоних очей зранку і відчуття власного безсилля. Реалізація exceptions у C++ на низькому рівні досі для мене є загадкою.
Все, завтра поїду до друга сніг кидати і варити глінтвейн у казанку.
А з нового тижня займусь проблемою Boost + Clang + C++11.
Є у мене один секретний проект який потихеньку починає виходити у люди, і от треба було запустити його на FreeBSD. На лінуксах все працювало нормально і нічого не віщало біди...
Проект використовує моднявий нині протокол серіалізації і RPC Thrift (розроблений у Facebook і відданий суспільству у Apache). Реалізація протоколу виявилась гнилою, паскудною, і нагадує вихлоп трудів сотень макак яких випадково пустили за клавіатури. Чого тільки варте змішання стилів, використання std::tr1::function, std::auto_ptr, boost::shared_ptr? А низькорівнева робота з мережею, при живому Boost.ASIO? І все це на фоні сотень фабрик і адапторів. Та що там казати...
Але, принаймні, все те кубло змій, жаб і червів працювало. А під FreeBSD раптом відмовило. Точніше не зовсім відмовило, воно продовжило працювати рівно до тих пір поки клієнт залишається під’єднаним. Як тільки клієнт від’єднуєьтся — все. Хана. Приблизно така:
Thread 7 (Thread 803009400 (LWP 100514)): #0 0x0000000801f6f54c in thr_kill () from /lib/libc.so.7 #1 0x0000000802011c9b in abort () from /lib/libc.so.7 #2 0x0000000801d0d965 in _Unwind_Resume () from /lib/libgcc_s.so.1 #3 0x00000008040d8d4d in apache::thrift::TDispatchProcessor::process (this=0x80302f820, in=..., out=..., connectionContext=0x0) at TDispatchProcessor.h:121 ...
— там ще близько двадцяти фреймів стеку. Ну, власне, зрозуміло в чому проблема — неловлений exception. Та от тільки... exception насправді ловлений. Спочатку строго по типу, потім по std::exception і в кінці кінців по трьом крапкам:
try { for (;;) { if (eventHandler_ != NULL) { eventHandler_->processContext(connectionContext, client); } if (!processor->process(inputProtocol, outputProtocol, connectionContext) || // Peek ahead, is the remote side closed? !inputProtocol->getTransport()->peek()) { break; } } } catch (const TTransportException& ttx) { string errStr = string("TSimpleServer client died: ") + ttx.what(); GlobalOutput(errStr.c_str()); } catch (const std::exception& x) { GlobalOutput.printf("TSimpleServer exception: %s: %s", typeid(x).name(), x.what()); } catch (...) { GlobalOutput("TSimpleServer uncaught exception."); }
Розумієте? Exception пролітає через три крапки як вода крізь сито! І навіть коли я ззовні поставив ще один try/catch — нічого не змінилось! Звісно я грішив на кубло змій. В такій каші я варився п’ять днів. І зварився. Вчора прийшов і просто опустив руки. Бо перевірив усе і навіть під valgrind поганяв. Ніц не знайшов. Все чисто. І багтрекери всі перерив, і багато схожих проблем бачив, але жодна із них не мала до мене ніякого відношення. Я просто не знав що робити.
При чому exceptions із мого коду ловились нормально. І я почав гратися — весь цей thrift жив у плагіні у вигляді shared object, я почав туди додавати свої функції і методи які просто кидали exception. І дивитись що вийде. І якось доекспериментувався до того, що якщо просто викликати функції що кидає exception то не зважаючи на try/catch матиму abort. А якщо одразу після (sic!) цієї функції кинути ще одне exception — все починає працювати! Просто чорна магія.
Сьогодні продовжив гратись і експериментувати. І знову таки випадково визначив, що все падає коли на рівні вище від функції що кидає exception є boost::shared_ptr. Почав грішити на Boost (ну тіпа там exception у деструкторі під час розгортання стеку), колупатись в коді і багтреккері. Без толку. Потім після багатьох невдалих спроб мені вдалось відтворити проблему не на великому проекті (який я на той час уже добряче пошматував), а на відносно невеликому тестовому. І я визначив що падає не тільки коли маю boost::shared_ptr а взагалі коли маю будь-який об’єкт з custom destructor. Навіть якщо той destructor пустий. З дефолтовим не падає.
Enjoy:
[faust@test /usr/home/faust/testex]$ cat testex.cpp #include <string> void bar() { throw 1; } void foo() { std::string client; bar(); } int main() { try { foo(); } catch(...) { } return 0; } [faust@test /usr/home/faust/testex]$ /usr/local/bin/g++46 testex.cpp -o testex [faust@test /usr/home/faust/testex]$ ./testex Abort trap: 6 (core dumped) [faust@test /usr/home/faust/testex]$ g++ testex.cpp -o testex [faust@test /usr/home/faust/testex]$ ./testex [faust@test /usr/home/faust/testex]$ [faust@test /usr/home/faust/testex]$ /usr/local/bin/g++46 --version g++46 (FreeBSD Ports Collection) 4.6.3 Copyright (C) 2011 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. [faust@test /usr/home/faust/testex]$ g++ --version g++ (GCC) 4.2.1 20070831 patched [FreeBSD] Copyright (C) 2007 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. [faust@test /usr/home/faust/testex]$
Зі шлангом теж, звісно, все в порядку. Але там свої заморочки...
Вся ця історія коштувала мені недосипання, червоних очей зранку і відчуття власного безсилля. Реалізація exceptions у C++ на низькому рівні досі для мене є загадкою.
Все, завтра поїду до друга сніг кидати і варити глінтвейн у казанку.
А з нового тижня займусь проблемою Boost + Clang + C++11.