Исключение в другом потоке
От: T4r4sB Россия  
Дата: 15.08.25 17:12
Оценка:
Суть такова, до какого-то обновления (вот не помню, какую версию gcc я использовал) при возникновении исключения внутри потока, созданного через std::async, программа падала (я его нигде не перехватывал), причём срабатывал обработчик какого-то сигнала , который выводил трассу стека (при старте приложения вызывалась такая функция https://llvm.org/doxygen/namespacellvm_1_1sys.html#ab062fd190912d9ca714311df7cbe51d5).
После обновления то ли компилятора, то ли фреймворка (сорян за сумбур, уже не могу откатиться и перепроверить) исключения в другом потоке стали молча глотаться. Причём я пробовал их перехватить и перебросить (например вызвать .get у фьючи, которую вернул std::async), но проблема в том, что при этом я теряю трассу: выводится позиция не того места, где на самом деле возникло исключение, а позиция того места, где я его перебросил.
Как сделать чтоб "было как раньше"? Чтоб исключение в другом потоке сразу вызывало падение с правильной трассой.
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re: Исключение в другом потоке
От: sergii.p  
Дата: 19.08.25 08:37
Оценка:
Здравствуйте, T4r4sB, Вы писали:

TB>Суть такова, до какого-то обновления (вот не помню, какую версию gcc я использовал) при возникновении исключения внутри потока, созданного через std::async, программа падала (я его нигде не перехватывал), причём срабатывал обработчик какого-то сигнала , который выводил трассу стека (при старте приложения вызывалась такая функция https://llvm.org/doxygen/namespacellvm_1_1sys.html#ab062fd190912d9ca714311df7cbe51d5).

TB>После обновления то ли компилятора, то ли фреймворка (сорян за сумбур, уже не могу откатиться и перепроверить) исключения в другом потоке стали молча глотаться. Причём я пробовал их перехватить и перебросить (например вызвать .get у фьючи, которую вернул std::async), но проблема в том, что при этом я теряю трассу: выводится позиция не того места, где на самом деле возникло исключение, а позиция того места, где я его перебросил.
TB>Как сделать чтоб "было как раньше"? Чтоб исключение в другом потоке сразу вызывало падение с правильной трассой.

тяжело установить все детали. Даже тяжело представить как стек мог сохраниться. В отдельном потоке исключение должен был перехватить обработчик от std::async и сохранить его (то есть на этом этапе мы должны были потерять всю информацию о стеке). При пробросе исключения в главном потоке вызывается terminate, но опять же информация о стеке должна была исчезнуть. Можно предположить, что компилятор провёл какую-то хитрую оптимизацию, понял что исключение в главном потоке всё равно никто ловить не будет и вызвал terminate в дочернем. И вот тогда всё идёт на откуп опять же компилятора:

If no matching handler is found, std::terminate is invoked; whether or not the stack is unwound before this invocation of std::terminate is implementation-defined.

Может оставить как есть, а может размотать стек и потереть таблицу переходов. Что частично может объяснить изменения поведения после обновления компилятора.

В любом случае закладываться на такие вещи себе дороже.
Re: Исключение в другом потоке
От: ononim  
Дата: 19.08.25 15:58
Оценка:
выглядит так что ваш новый фреймворк замаскировал сигналы на всех потоках и обрабатывает их через sigwait на каком то определенном потоке
TB>Как сделать чтоб "было как раньше"? Чтоб исключение в другом потоке сразу вызывало падение с правильной трассой.
ну если не задумываться о происходящем, то sigprocmask
Как много веселых ребят, и все делают велосипед...
Re[2]: Исключение в другом потоке
От: T4r4sB Россия  
Дата: 19.08.25 16:06
Оценка:
Здравствуйте, ononim, Вы писали:

O>выглядит так что ваш новый фреймворк замаскировал сигналы на всех потоках и обрабатывает их через sigwait на каком то определенном потоке


По идее std::async так и делает — глотает исключения.
Так что я теперь не понимаю почему раньше оно нормально падало

TB>>Как сделать чтоб "было как раньше"? Чтоб исключение в другом потоке сразу вызывало падение с правильной трассой.

O>ну если не задумываться о происходящем, то sigprocmask

И как именно его надо использовать? Как оно поможет если в библиотечной реализации std::async сделан перехват исключений, например?
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[2]: Исключение в другом потоке
От: T4r4sB Россия  
Дата: 19.08.25 17:34
Оценка:
Здравствуйте, sergii.p, Вы писали:

SP>тяжело установить все детали. Даже тяжело представить как стек мог сохраниться.


Раньше стек выводится в перехватчике сигнала. Исключение вызывало сигнал, всё было хорошо

SP>При пробросе исключения в главном потоке вызывается terminate, но опять же информация о стеке должна была исчезнуть


Я тогда не понимаю вообще прикол исключений.
Вот где-то у меня хз где std::unordered_map::at кинул исключение.
И как мне понять, где именно? Как только я его катчу, инфа теряется.
Мне весь проект каждую функцию обмазать try-catch и навешивать на объект исключения инфу о том, где это произошло?
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[3]: Исключение в другом потоке
От: ononim  
Дата: 19.08.25 18:21
Оценка:
TB>И как мне понять, где именно? Как только я его катчу, инфа теряется.
TB>Мне весь проект каждую функцию обмазать try-catch и навешивать на объект исключения инфу о том, где это произошло?
не каждую функцию, а функцию каждого потока оберни в try catch(чтотам у тебя за exception)
Как много веселых ребят, и все делают велосипед...
Re[4]: Исключение в другом потоке
От: T4r4sB Россия  
Дата: 19.08.25 18:31
Оценка:
Здравствуйте, ononim, Вы писали:

O>не каждую функцию, а функцию каждого потока оберни в try catch(чтотам у тебя за exception)


То есть
std::async([&](){
  try {
    // тут много работы и где-то хз где произошло исключение в std::unordered_map::at
  } catch (...) {
    // ???
  }
})

?

И как мне получить трассу?
Что я должен написать в катче?
llvm::sys::PrintStackTrace выводит трассу до катча, а не до места, где произошло исключение
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re: Исключение в другом потоке
От: watchmaker  
Дата: 19.08.25 19:03
Оценка:
Здравствуйте, T4r4sB, Вы писали:

TB>Суть такова, до какого-то обновления (вот не помню, какую версию gcc я использовал) при возникновении исключения внутри потока, созданного через std::async, программа падала (я его нигде не перехватывал), причём срабатывал обработчик какого-то сигнала , который выводил трассу стека (при старте приложения вызывалась такая функция https://llvm.org/doxygen/namespacellvm_1_1sys.html#ab062fd190912d9ca714311df7cbe51d5).

TB>После обновления то ли компилятора, то ли фреймворка (сорян за сумбур, уже не могу откатиться и перепроверить) исключения в другом потоке стали молча глотаться.

Ни падение программы, ни проглатывание исключения не является валидным поведением для std::async.
На самом деле исключение, вылетевшее из вызываемой функции, сохраняется в future (и его можно потом перевыбросить при вызове get-метода).

Скорее всего, ты что-то путаешь, и либо раньше использовал другие примитивы (например, явно сам запускал thread и выставлял promise), либо твоя программа падала не из-за исключения, а, скажем, из-за связанного SIGSEGV. В обоих случаях обработчики terminate, сигналов или отдадчики вполне могут выдавать stacktrace.

TB>Как сделать чтоб "было как раньше"? Чтоб исключение в другом потоке сразу вызывало падение с правильной трассой.

Вспоминай, что использовал до этого :)
Потому что вызов std::async служит, чтобы выполнить функцию и получить её исключение и результат. Если это не подходит, то нужно не использовать async.


TB> Причём я пробовал их перехватить и перебросить (например вызвать .get у фьючи, которую вернул std::async), но проблема в том, что при этом я теряю трассу: выводится позиция не того места, где на самом деле возникло исключение, а позиция того места, где я его перебросил.


Тут тоже не понятно, на что ты смотришь. Одно дело, если ты видел statcktrace из terminate после неудачной раскрутки, а другое дело, если ты хочешь в объекте исключения хранить ассоциированный с ним путь и инспектировать его в программе...

Технически возможно собирать полный путь раскрутки и сохранять его в объекте исключения. Но дорого. Например, есть неприятные случаи, связанные тем, что сохранённое в exception_ptr исключение может быть перевыброшено несколько раз, и в памяти стектрейсы представляют собой дерево, а не список. И желательно не тормозить.

Впрочем, это решаемые проблемы, и хотя предложение P2370 Stacktrace from exception стагнирует, у него есть пара реализаций, которые в большинстве случаев включают запись пути через границы exception_ptr/future.
Отредактировано 19.08.2025 19:09 watchmaker . Предыдущая версия . Еще …
Отредактировано 19.08.2025 19:05 watchmaker . Предыдущая версия .
Re[2]: Исключение в другом потоке
От: T4r4sB Россия  
Дата: 19.08.25 20:06
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>Скорее всего, ты что-то путаешь, и либо раньше использовал другие примитивы (например, явно сам запускал thread и выставлял promise),


Мб дело в том, что я не напрямую зову std::async, а через фреймворк, и мб в предыдущей версии фреймворка поток создавался как-то по-другому

W>либо твоя программа падала не из-за исключения, а, скажем, из-за связанного SIGSEGV


Она именно на явное
throw std::runtime_error("MyError")

реагировала падением с полной выдачей трассы

W>и его можно потом перевыбросить при вызове get-метода


И потерять трассу

W>которые в большинстве случаев включают запись пути через границы exception_ptr/future.


Мне пока не удалось. Потому что уже когда я делаю катч — я уже теряю трассу
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Отредактировано 19.08.2025 20:08 T4r4sB . Предыдущая версия .
Re[3]: Исключение в другом потоке
От: ononim  
Дата: 20.08.25 06:08
Оценка:
TB>И потерять трассу
W>>которые в большинстве случаев включают запись пути через границы exception_ptr/future.
TB>Мне пока не удалось. Потому что уже когда я делаю катч — я уже теряю трассу
Вместо std::runtime_error сделай свой класс исключений, который в своем конструкторе будет делать backtrace и запоминать его в себе.
Чтоб меньше все менять — отнаследуй его от std::runtime_error
Как много веселых ребят, и все делают велосипед...
Отредактировано 20.08.2025 6:12 ononim . Предыдущая версия .
Re[4]: Исключение в другом потоке
От: T4r4sB Россия  
Дата: 20.08.25 06:53
Оценка:
Здравствуйте, ononim, Вы писали:

O>Вместо std::runtime_error сделай свой класс исключений, который в своем конструкторе будет делать backtrace и запоминать его в себе.

O>Чтоб меньше все менять — отнаследуй его от std::runtime_error

Ок, для своих исключений это должно прокатить.
А что делать с std::out_of_range, и прочими вещами, кидаемыми библиотеками?
Ну либо какой флаг указать при сборке, чтоб at не кидал исключение, а валил программу?
Кстати, что вызывается в at в режиме -fno-exceptions?
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[5]: Исключение в другом потоке
От: ononim  
Дата: 20.08.25 07:37
Оценка:
TB>Ок, для своих исключений это должно прокатить.
TB>А что делать с std::out_of_range, и прочими вещами, кидаемыми библиотеками?
Похоже нужно заниматься грязным хукингом: https://libstdcpp.gcc.gnu.narkive.com/rEOaWepF/is-it-possible-to-set-a-hook-in-exception-throwing
Как много веселых ребят, и все делают велосипед...
Re[6]: Исключение в другом потоке
От: T4r4sB Россия  
Дата: 20.08.25 08:49
Оценка:
Здравствуйте, ononim, Вы писали:

TB>>Ок, для своих исключений это должно прокатить.

TB>>А что делать с std::out_of_range, и прочими вещами, кидаемыми библиотеками?
O>Похоже нужно заниматься грязным хукингом: https://libstdcpp.gcc.gnu.narkive.com/rEOaWepF/is-it-possible-to-set-a-hook-in-exception-throwing

Там ссылка на codesourcery какая-то нерабочая
А как хукать? Просто объявить такую функцию нельзя, линкер ругается. Че за ерунда, для new разрешили глобальное переопределение а для исключений нет.
Компилировать отдельную либу и говорить пользователям "запускать через прелод" тоже не вариант
Вот это свинью подложили конечно в std async тем что без спросу перехватывают исключения. Спасибо, чо
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[7]: Исключение в другом потоке
От: so5team https://stiffstream.com
Дата: 20.08.25 08:59
Оценка: +1 :)
Здравствуйте, T4r4sB, Вы писали:

TB>Вот это свинью подложили конечно в std async тем что без спросу перехватывают исключения.


Если вам нужно, чтобы исключение в std::async валило все приложение, то можно попробовать через std::async запускать noexcept-функцию.
Re[3]: Исключение в другом потоке
От: sergii.p  
Дата: 20.08.25 10:00
Оценка:
Здравствуйте, T4r4sB, Вы писали:

TB>Я тогда не понимаю вообще прикол исключений.


я тоже

TB>Вот где-то у меня хз где std::unordered_map::at кинул исключение.

TB>И как мне понять, где именно? Как только я его катчу, инфа теряется.

cо стандартными исключениями не получится. Но в своём коде можно кидать что-то такое

class stack_exception : public std::runtime_error {
public:
    stack_exception(const std::string& msg)
        : std::runtime_error(msg), trace_(std::stacktrace::current()) {}

    const std::stacktrace& trace() const noexcept {
        return trace_;
    }

private:
    std::stacktrace trace_;
};
Re[7]: Исключение в другом потоке
От: ononim  
Дата: 20.08.25 10:40
Оценка:
TB>А как хукать? Просто объявить такую функцию нельзя, линкер ругается. Че за ерунда, для new разрешили глобальное переопределение а для исключений нет.
Ну както так:
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>
#include <time.h>
#include <stdlib.h>

#include <exception>


extern "C"
{
    __attribute__ ((visibility ("default"))) void __cxa_throw(void *a1, void *a2, void (*a3)(void*))
    {
        int frame = 0;
        typedef void (*t_orig__cxa_throw)(void *, void *, void (*)(void*));
        static t_orig__cxa_throw s_orig__cxa_throw = NULL;
        printf("__cxa_throw: &frame=%p\n", &frame);
        if (!s_orig__cxa_throw) {
            s_orig__cxa_throw = (t_orig__cxa_throw)dlsym(RTLD_NEXT, "__cxa_throw");
        }
        if (s_orig__cxa_throw) {
            s_orig__cxa_throw(a1, a2, a3);
        }
        abort();
    }
}

struct Exception
{
    Exception()
    {
        int frame = 0;
        printf("Exception: &frame=%p\n", &frame);
    }
    ~Exception()
    {
        int frame = 0;
        printf("~Exception: &frame=%p\n", &frame);
    }
};


void __attribute__ ((noinline)) Second()
{
    int frame = 0;
    printf("Second: &frame=%p\n", &frame);
    if (time(NULL)) {
        throw Exception();
    }
}

void __attribute__ ((noinline)) First()
{
    int frame = 0;
    printf("First: &frame=%p\n", &frame);
    Second();
}


int main(int argc, char **argv)
{
    try {
        int frame = 0;
        printf("Main: &frame=%p\n", &frame);
        First();
    } catch (Exception &e) {
        int frame = 0;
        printf("Catch: &frame=%p\n", &frame);
    }
    return 0;
}


Работает в линуксе, а для винды нужна будет другая магия.
А ну и еще важно чтобы эта __cxa_throw была в файле исполняемого процесса, если будет в библиотеке (.so) то чудо не случится.
Как много веселых ребят, и все делают велосипед...
Отредактировано 20.08.2025 10:44 ononim . Предыдущая версия .
Re[8]: Исключение в другом потоке
От: T4r4sB Россия  
Дата: 20.08.25 11:16
Оценка:
Здравствуйте, so5team, Вы писали:

S>Здравствуйте, T4r4sB, Вы писали:


TB>>Вот это свинью подложили конечно в std async тем что без спросу перехватывают исключения.


S>Если вам нужно, чтобы исключение в std::async валило все приложение, то можно попробовать через std::async запускать noexcept-функцию.


И тогда оно падает, но трасса идет ровно до той функции которую я пометил как noexcept. То есть где там в ее дебрях у меня действительно кинулось исключение — не понять
Весь проект обмазывать noexcept?
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[8]: Исключение в другом потоке
От: T4r4sB Россия  
Дата: 20.08.25 11:25
Оценка:
Здравствуйте, ononim, Вы писали:

TB>>А как хукать? Просто объявить такую функцию нельзя, линкер ругается. Че за ерунда, для new разрешили глобальное переопределение а для исключений нет.

O>Ну както так:

Линкер не дает, говорит что __cxa_throw уже определена в libstd++.a(eh_throw.o)
Кажется проще треды по-своему создавать, без навязанной услуги перехвата исключений
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[9]: Исключение в другом потоке
От: so5team https://stiffstream.com
Дата: 20.08.25 11:30
Оценка:
Здравствуйте, T4r4sB, Вы писали:

S>>Если вам нужно, чтобы исключение в std::async валило все приложение, то можно попробовать через std::async запускать noexcept-функцию.


TB>И тогда оно падает, но трасса идет ровно до той функции которую я пометил как noexcept. То есть где там в ее дебрях у меня действительно кинулось исключение — не понять

TB>Весь проект обмазывать noexcept?

У меня есть ощущение, что вы нам чего-то важного недоговариваете, поскольку я еще не встречался с runtime-ом для C++, который бы при крахе из-за вылетевшего из треда исключения (или при вызове std::terminate) выдавал бы стек-трейс.

Возможно, вы пользуетесь какой-то сторонней либой, которая берет на себя выдачу подобных стек-трейсов. Если это так, то вопросы нужно задавать по этой либе.
Re[9]: Исключение в другом потоке
От: ononim  
Дата: 20.08.25 11:53
Оценка:
TB>>>А как хукать? Просто объявить такую функцию нельзя, линкер ругается. Че за ерунда, для new разрешили глобальное переопределение а для исключений нет.
O>>Ну както так:
TB>Линкер не дает, говорит что __cxa_throw уже определена в libstd++.a(eh_throw.o)
это при статической линковке, с динамической все ок
Как много веселых ребят, и все делают велосипед...
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.