Обнаружить, что исключение не перехвачено пользователем без переброса - PullRequest
2 голосов
/ 14 июля 2011

У меня есть определенный класс исключений (используется как исключение отмены потока, где я делаю точки отмены, выбрасывающие его).

Я хочу иметь возможность вызывать abort (), если пользователь ловит его, но не выбрасывает. Чтобы пользователь не мог отменить отмену или непреднамеренно использовать catch (...).

Я пытался вызвать abort () в деструкторе, если не установлен частный флаг в классе исключений. Функция запуска моего потока является другом этого класса исключений и изменяет внутренний флаг. Проблема в том, что в Visual C ++ исключение уничтожается дважды (по-видимому, потому что оно копируется при выдаче).

Я решил использовать подсчет ссылок, чтобы при копировании (которое, по-видимому, происходит во время выполнения оператора throw) счетчик увеличивался, и деструктор не выбрасывал прерывание, пока есть другие копии.

К сожалению, конструктор копирования не на самом деле вызывается - я пытался попытаться от него избавиться. То же самое для операторов присваивания и перемещения конструктора и присваивания - они не вызываются. Похоже, что исключение магически продублировано, и ни один из них не вызван, поскольку деструктора дважды бросают.

Так какой обходной путь я могу использовать здесь?

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EDIT ~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~

Добавлен пример кода для объяснения:

C ++ 0x std :: thread конструктор принимает функтор или функцию и 0..n подвижные аргументы. Поскольку MSVC не поддерживает шаблоны переменных, я использовал перегруженные конструкторы для различного числа аргументов. Например, для двух аргументов:

template<typename C, typename A0, typename A1>
inline thread::thread(C const &func, A0 &&arg0, A1 &&arg1)
{
    Start(make_shared<function<void (void)>>(function<void (void)>(std::bind(forward<C>(func), forward<A0>(arg0), forward<A1>(arg1)))));
}

Далее Start () упаковывает функцию std :: function с навязчивым умным указателем, чтобы удостовериться, что функция std :: правильно уничтожена, когда она выходит за рамки как в Start (), так и в потоке Run (). функция запущена _beginthreadex. Внутри функции run, try / catch, который фактически запускает предоставленную пользователем функцию / аргументы, обычно будет:

try
{
    (*pack->runpack)();
}
catch (...)
{
    terminate();
}

Однако я хотел бы иметь возможность, в дополнение к обычной функциональности std :: thread, делать что-то вроде того, что происходит, когда в Linux вы вызываете pthread_cancel (), и, кроме того, иметь возможность выполнять любой из аналогов типа отмены из PTHREAD_CANCEL_DEFERRED и PTHREAD_CANCEL_ASYNCHRONOUS. Это важно, поскольку я пишу для приложения-киоска, где основное приложение должно иметь возможность восстанавливаться (большую часть времени) из потоков, зависших при запуске сторонних созданных модулей.

Я добавил следующее выше catch (...) выше:

catch (Cancellation const &c) // TODO: Make const & if removing _e flag from Cancellation; TODO: Catch by const & if not modifying internal state
{
    c.Exec();
}

, где Cancellation :: Exec () использует указатель на текущий std :: thread (который я храню в локальном потоке) и вызывает detach (). Затем я добавил две функции, первая из которых:

bool CancelThreadSync(std::thread &t, unsigned int ms)
{
    if (!QueueUserAPC(APCProc, t.native_handle(), 0)) THROW_LASTWINERR(runtime_error, "Could not cancel thread")
    if (ms) Wait(ms);
    if (t.joinable()) return false;
    return true;

} APCProc устанавливает некоторый флаг, чтобы я мог добавить TestCancel (), аналогично функции точки отмены pthread_testcancel (). TestCancel () выдает Cancellation, если установлен флаг (pthreads в Linux приводит к тому, что стек разматывается и деструкторы вызываются по-разному, но это делает это, так что это намного лучше, чем TerminateThread ()). Однако я также изменил все места ожидания, такие как WaitForSingleObject и Sleep, на версии этих функций с предупреждением, а также WaitForSingleObjectEx и SleepEx. Например:

inline void mutex::lock(void)
{
    while (Load(_owned) || _interlockedbittestandset(reinterpret_cast<long *>(&_waiters), 0))
    {
        unsigned long waiters(Load(_waiters) | 1);
        if (AtomicCAS(&_waiters, waiters, waiters + 512) == waiters) // Indicate sleeping
        {
            long const ret(NtWaitForKeyedEvent(_handle, reinterpret_cast<void **>(this), 1, nullptr)); // Sleep
            #pragma warning(disable : 4146) // Negating an unsigned
            AtomicAdd(&_waiters, -256); // Indicate finished waking
            #pragma warning(default : 4146)
            if (ret)
            {
                if (ret != STATUS_USER_APC) throw std::runtime_error("Failed to wait for keyed event");
                TestCancel();
            }
        }
    }
}

(оригинальный алгоритм от http://www.locklessinc.com/articles/)
Обратите внимание на проверку того, предупредил ли APC ожидание.Теперь я могу отменить запущенный поток, даже если он заблокирован.TestCancel () создает исключение Cancellation, поэтому стек разматывается деструкторами, вызываемыми до завершения потока - это именно то, что я хочу.Я также добавил асинхронное аннулирование для случаев, когда блок находится не в системном вызове, а, возможно, в бесконечном цикле или в действительно медленном вычислении или в тупике спин-блокировки.Я сделал это, приостановив поток, установив Eip (или Rip на x64), чтобы он указывал на функцию, которая выдает Cancellation, и возобновил ее.Это не совсем надежно, но работает в большинстве случаев, и для меня этого достаточно.

Я проверил что-то, и они отлично работают.Моя проблема заключается в том, чтобы пользователь не смог отменить отмену без ее повторного удаления.Я хочу, чтобы это всегда было разрешено распространяться на функцию запуска потока.Поэтому я подумал, что вызову abort () в ~ Cancellation (), если не установлен внутренний флаг (где функция запуска потока является другом Cancellation и единственной, которая может это сделать).Проблема в том, что происходит множественный вызов деструктора, и моя неспособность обойти это с помощью подсчета ссылок, поскольку дублирование исключения, по-видимому, происходит без вызова какого-либо вызываемого конструктора / назначения копирования / перемещения, насколько я могу судить (Я протестировал, добавив операторы печати ко всем этим).

Ответы [ 2 ]

1 голос
/ 14 июля 2011

Посмотрите на этот вопрос , чтобы увидеть, поможет ли это вам. Как объяснялось там, когда вы вызываете throw, вы передаете объект данных (класс в вашем случае) для выброса: он копируется и передается во все блоки перехвата, которые обрабатывают этот тип. (Если ваш catch использует тип напрямую, есть другая копия, если он использует ссылку на тип, то это не указывается.)

Таким образом, вы ожидаете, что объект исключения будет удален как минимум дважды: один раз, когда удаляется экземпляр, который вы используете для инициализации throw, один раз, когда был вызван последний обработчик исключения, и объект переданный в catch блоки удаляется.

0 голосов
/ 14 июля 2011

abort является функцией времени выполнения C и не подходит для исключений C ++ и Windows SEH.Избегай это.Он не попадет ни в catch, ни в __try / __except.Во-вторых, я предлагаю вам включить флаг /EHs (C ++ -> Генерация кода -> Включить исключение C ++).Это поможет вам перехватить исключения C ++ и Windows SEH в блоке try-catch.Это также позволило бы вызывать любые ожидающие деструкторы (как бы вложенные в callstack).Помните, что _ try / _except НЕ будет позволять вызывать деструкторы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...