adontz wrote:
> Решение судя по всему кросс-компиляторное.
Представь, что когда ты останавливаешь поток, в которой ты хочешь прокинуть исключение, этот поток находится в коде, скомпилированном с синхронной моделью обработки исключений, или в ф-ции third-party С библиотеки или в С/С++ runtime (malloc, к примеру). Тот код не ожидает исключения и не сможет его обработать, оставив структуры данных в рассогласованном состоянии...
Здравствуйте, adontz, Вы писали:
A>Решение судя по всему кросс-компиляторное. A>Что кинуть исключение в другом потоке (и вообще как метод одноразовой передачи сообщения потоку) A>
A>template <typename _type_exception>
A>void _throw_remote_proc(_type_exception * exception_object)
A>{
A> throw exception_object;
A>}
A>//
A>template <typename _type_exception>
A>__declspec(noreturn) void throw_remote(DWORD thread_id, _type_exception * exception_object)
A>{
A> HANDLE hThread = OpenThread(THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME, FALSE, thread_id);
A> SuspendThread(hThread);
A> CONTEXT context;
A> ZeroMemory(&context, sizeof(context));
A> context.ContextFlags = CONTEXT_CONTROL;
A> GetThreadContext(hThread, &context);
A> context.Eip = (DWORD)((void(*)(_type_exception *))&_throw_remote_proc<_type_exception>);
A> *((DWORD *)context.Esp) = (DWORD)exception_object;
A> context.Esp -= 4;
A> SetThreadContext(hThread, &context);
A> ResumeThread(hThread);
A> CloseHandle(hThread);
A>}
A>
Имхо, выделенные строчки надо поменять местами. ESP указывает на последнее пристутствующее в стеке слово, а не на первое отсутствующее.
Здравствуйте, MaximE, Вы писали:
ME>Представь, что когда ты останавливаешь поток, в которой ты хочешь прокинуть исключение, этот поток находится в коде, скомпилированном с синхронной моделью обработки исключений, или в ф-ции third-party С библиотеки или в С/С++ runtime (malloc, к примеру). Тот код не ожидает исключения и не сможет его обработать, оставив структуры данных в рассогласованном состоянии...
Насколько я знаю, такой код допустим:
int third_party_c_function(int (* mycallback)())
{
return mycallback();
}
int func()
{
throw some(123);
}
int main()
{
try
{
third_party_c_function(func);
}
catch (some& s)
{
}
}
Здравствуйте, MaximE, Вы писали:
ME>Представь, что когда ты останавливаешь поток, в которой ты хочешь прокинуть исключение, этот поток находится в коде, скомпилированном с синхронной моделью обработки исключений, или в ф-ции third-party С библиотеки или в С/С++ runtime (malloc, к примеру). Тот код не ожидает исключения и не сможет его обработать, оставив структуры данных в рассогласованном состоянии...
А ещё может быть, что код в том потоке вообще дельфийский и Си++ исключения ловить не умеет.
Конечно этот механизм надо применять с осторожностью, но это не делает его менее применимым.
Здравствуйте, SergH, Вы писали:
SH>Имхо, выделенные строчки надо поменять местами. ESP указывает на последнее пристутствующее в стеке слово, а не на первое отсутствующее.
Там ещё должен быть адрес возвращения из функции _throw_remote_proc, но так как из неё не возвращаются, то и адрес я не указываю.
Кстати __declspec(noreturn) я получается не туда поставил throw_remote как раз возвращает управления в отличие от простого throw.
SergH wrote:
> Здравствуйте, MaximE, Вы писали: > > ME>Представь, что когда ты останавливаешь поток, в которой ты хочешь прокинуть исключение, этот поток находится в коде, скомпилированном с синхронной моделью обработки исключений, или в ф-ции third-party С библиотеки или в С/С++ runtime (malloc, к примеру). Тот код не ожидает исключения и не сможет его обработать, оставив структуры данных в рассогласованном состоянии... > > Насколько я знаю, такой код допустим:
[]
> Поэтому, имхо, ты не прав.
Тогда расскажи мне, как С-шный код, например код malloc, обработает не весть откуда взявшеесе С++ исключение, откатив все сделанные к этому моменту действия?
Другими словами, чтобы этот код работал и сохранял прогу в определенном состаянии (инвариант), нужно, чтобы весь код, который использует твоя прога, обеспечивал strong exception safety. Чтобы далеко не ходить, можно попробовать отыскать такой код в С — runtime твоего компилятора...
Здравствуйте, MaximE, Вы писали:
ME>Тогда расскажи мне, как С-шный код, например код malloc, обработает не весть откуда взявшеесе С++ исключение, откатив все сделанные к этому моменту действия?
Честно говоря, я вообще не очень хорошо представляю, как происходит обработка C++-ных исключений. Буду рад, если ты мне объяснишь. Или ссылку дашь.. Мои аргументы по анологии с SEH: для того чтобы обрабатывать SEH от кода не требуется ничего, любой справится. Если с C++ не так — объясни.
adontz wrote:
> ME>Представь, что когда ты останавливаешь поток, в которой ты хочешь прокинуть исключение, этот поток находится в коде, скомпилированном с синхронной моделью обработки исключений, или в ф-ции third-party С библиотеки или в С/С++ runtime (malloc, к примеру). Тот код не ожидает исключения и не сможет его обработать, оставив структуры данных в рассогласованном состоянии... > > А ещё может быть, что код в том потоке вообще дельфийский и Си++ исключения ловить не умеет. > Конечно этот механизм надо применять с осторожностью, но это не делает его менее применимым.
На мой взгляд он вообще неприменим.
Повторюсь еще раз, что практически любая С++ программа использует в user mode C и С++ код, который не может восстанавливаться после исключений — С и С++ рантайм. Чтобы бросить твоим способом исключение, необходимо гарантировать, что в момент его выстрела, не выполняется такой "небезопасный" с точки зрения исключений код. Это можно гарантировать, но реализации, на мой взгляд, будет слишком сложна, чтобы быть надежной.
Я не вижу, как можно использовать этот код с гарантированно предсказуемым результатом.
A>Мне такое удобно делать, чтоб остановить поток слушающий сокет, потому что message-queue там нету и по-другому сообщение не передать.
А порт завершения ввода вывода — не поможет ?
Во-первых: __declspec(noreturn) убери и поставь у _throw_remote_proc (уже писал об этом, извини перепутал)
Во-вторых: accept судя по всему использует что-то вроже WaitForSingleObject, потому что если вызов accept заменить на for(;;Sleep(1)); то всё работает.
Если кто-то знает как вывести поток из состояния ожижания буду благодарен.
Здравствуйте, SergH, Вы писали:
SH>Честно говоря, я вообще не очень хорошо представляю, как происходит обработка C++-ных исключений. Буду рад, если ты мне объяснишь.
Объясню как это делается в Microsoft Visual C++ начиная с версии 6 (может и раньше так же работало, но я не проверял)
Оператор try заменятся на засылку по адресу FS:[00000000] адреса таблицы соответсвия тип<->catch block (всё несколько сложнее, но по сути так)
Оператор throw инициирует простотр таблицы соответствия находящейся по адресу FS:[00000000] и если найдён соответствующий блок обработки то управление передайтся ему (всё несколько сложнее, но по сути так)
Оператору throw совершенно плевать откуда он вызвался если по адресу FS:[00000000] корректная таблица
Адреса деструкторов локальных объектов храниться рядом с таблицей соответсвия (которая по адресу FS:[00000000])
Таким образом вызов деструкторов и нужного блока catch выполняется вне зависимости от того откуда вызван throw.
On Fri, 03 Jun 2005 16:34:53 +0400, adontz <2053@users.rsdn.ru> wrote:
> Решение судя по всему кросс-компиляторное. > Что кинуть исключение в другом потоке (и вообще как метод одноразовой передачи сообщения потоку)
[]
> Мне такое удобно делать, чтоб остановить поток слушающий сокет, потому что message-queue там нету и по-другому сообщение не передать.
Куря кольян, на меня снизошло понимание: брошенное таким способом исключение разрушает любой код, включая код с гарантиями strong exception safety и nothrow. Этот метод нельзя использовать никогда, когда предполагается восстановление и продолжение работы после обработки исключения.
Код с гарантиями strong exception safety и nothrow и весь код на С полагается на операции, которые гарантировано не бросают исключений, такие, как, например, многие простые операции над встроенными типами.
Рассмотрим операцию вставки в двусвязанный список. Эта операция является nothrow (конечно при условии, что указатели валидны).
Для валидного списка, этот код всегда исполняется от начала до конца, т.к. операции присвоения для валидных указателей никогда не могут бросить исключения.
Если в поток, выполняющий этот код, пробросить исключение предлагаемым варварским методом в тот момент, когда была выполнена строка (*), но не была еще выполнена следующая строка, список будет разрушен. Чтобы этот код сохранил свойство nothrow при таких новых условиях, когда исключение может возникнуть в любой момент из ниоткуда "на ровном месте", возможно необходимо либо каждую строку обернуть в __try / __except конструкцию, либо, возможно, исполнять весь код в блоке __finally (я не спец в SEH). Никто, естественно, в здравом уме этого делать не будет. Примеры подобного кода можно приводить бесконечно.
Вывод из этого следующий: ты просто не можешь прервать выполнение любого потока в произвольной точке и перевести управление при помощи исключения в другую точку, ожидая при этом, что состояние структур данных сохранит инвариант и останется в каком-либо относительно корректном состоянии.
Да, поначалу выглядит круто порулить руками регистрами и наебать остальной код и компилятор, но if you lie to the compiler, it will take its revenge...
Здравствуйте, MaximE, Вы писали:
ME>Куря кольян, на меня снизошло понимание: брошенное таким способом исключение разрушает любой код, включая код с гарантиями strong exception safety и nothrow. Этот метод нельзя использовать никогда, когда предполагается восстановление и продолжение работы после обработки исключения.
Интересно как ты себе представляешь продолжение работы после программного исключения?
ME>Код с гарантиями strong exception safety и nothrow и весь код на С полагается на операции, которые гарантировано не бросают исключений, такие, как, например, многие простые операции над встроенными типами.
Предположим, что так.
ME>Рассмотрим операцию вставки в двусвязанный список. Эта операция является nothrow (конечно при условии, что указатели валидны).
Замечательно.
ME>Для валидного списка, этот код всегда исполняется от начала до конца, т.к. операции присвоения для валидных указателей никогда не могут бросить исключения.
Чудесно!
ME>Если в поток, выполняющий этот код, пробросить исключение предлагаемым варварским методом в тот момент, когда была выполнена строка (*), но не была еще выполнена следующая строка, список будет разрушен.
А вот отсюда уже не верно. С какой стати он будет разрушен? Квадратики и стрелки на салфетке рисовал? После выполнения указанной строки весь список от начала и до конца можно проглядеть имея указатель head. Да, return не выполнился, ну и что? Что при этом мешает корректно отработать деструкторам?
ME>Вывод из этого следующий: ты просто не можешь прервать выполнение любого потока в произвольной точке и перевести управление при помощи исключения в другую точку, ожидая при этом, что состояние структур данных сохранит инвариант и останется в каком-либо относительно корректном состоянии.
Во-первых, могу. Во-вторых, могу не только я. Требования такие же как и у Multiple Readers — Single Writer многопоточных структур. В частности весь STLPort будет на это нормально реагировать.
On Sat, 04 Jun 2005 03:35:16 +0400, adontz <2053@users.rsdn.ru> wrote:
[]
> ME>Если в поток, выполняющий этот код, пробросить исключение предлагаемым варварским методом в тот момент, когда была выполнена строка (*), но не была еще выполнена следующая строка, список будет разрушен. > > А вот отсюда уже не верно. С какой стати он будет разрушен? Квадратики и стрелки на салфетке рисовал? После выполнения указанной строки весь список от начала и до конца можно проглядеть имея указатель head. Да, return не выполнился, ну и что? Что при этом мешает корректно отработать деструкторам?
Ты потерял меня: список разрушен.
> ME>Вывод из этого следующий: ты просто не можешь прервать выполнение любого потока в произвольной точке и перевести управление при помощи исключения в другую точку, ожидая при этом, что состояние структур данных сохранит инвариант и останется в каком-либо относительно корректном состоянии. > > Во-первых, могу. Во-вторых, могу не только я. Требования такие же как и у Multiple Readers — Single Writer многопоточных структур. В частности весь STLPort будет на это нормально реагировать.
Элементарно показать, что контейнеры из STLPort не смогут оправиться после таких исключений.