Межпотоковое кидание исключений
От: adontz Грузия http://adontz.wordpress.com/
Дата: 03.06.05 12:34
Оценка: 27 (4)
Решение судя по всему кросс-компиляторное.
Что кинуть исключение в другом потоке (и вообще как метод одноразовой передачи сообщения потоку)
template <typename _type_exception>
void _throw_remote_proc(_type_exception * exception_object)
{
    throw exception_object;
}
//
template <typename _type_exception>
__declspec(noreturn) void throw_remote(DWORD thread_id, _type_exception * exception_object)
{
    HANDLE hThread = OpenThread(THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME, FALSE, thread_id);
    SuspendThread(hThread);
    CONTEXT context;
    ZeroMemory(&context, sizeof(context));
    context.ContextFlags = CONTEXT_CONTROL;
    GetThreadContext(hThread, &context);
    context.Eip = (DWORD)((void(*)(_type_exception *))&_throw_remote_proc<_type_exception>);
    *((DWORD *)context.Esp) = (DWORD)exception_object;
    context.Esp -= 4;
    SetThreadContext(hThread, &context);
    ResumeThread(hThread);
    CloseHandle(hThread);
}

тест

class ex
{
    public:
        int data;
    public:
        ex(): data(0)
        {
        }
        ex(int _data): data(_data)
        {
        }
};
//
bool ready = false;
//
DWORD CALLBACK ThreadProc(LPVOID lpParam)
{
    ready = true;
    try
    {
        for(;;)
        {
            Sleep(1);
        }
    }
    catch (ex * e)
    {
        // Попадаем сюда, то есть всё работает
        int x = e->data;
        delete e;
    }
    ready = true;
    return 0;
}
//
int main(int argc, char * argv[])
{
    DWORD thread_id;
    CloseHandle(CreateThread(NULL, 0, ThreadProc, NULL, 0, &thread_id));
    while (!ready)
    {
        Sleep(1);
    }
    ex * e = new ex(10);
    ready = false;
    throw_remote(thread_id, e);
    while (!ready)
    {
        Sleep(1);
    }
    return 0;
}

Мне такое удобно делать, чтоб остановить поток слушающий сокет, потому что message-queue там нету и по-другому сообщение не передать.
A journey of a thousand miles must begin with a single step © Lau Tsu
Re: Межпотоковое кидание исключений
От: MaximE Великобритания  
Дата: 03.06.05 12:55
Оценка: 11 (2)
adontz wrote:

> Решение судя по всему кросс-компиляторное.


Представь, что когда ты останавливаешь поток, в которой ты хочешь прокинуть исключение, этот поток находится в коде, скомпилированном с синхронной моделью обработки исключений, или в ф-ции third-party С библиотеки или в С/С++ runtime (malloc, к примеру). Тот код не ожидает исключения и не сможет его обработать, оставив структуры данных в рассогласованном состоянии...

--
Maxim Yegorushkin
Posted via RSDN NNTP Server 1.9
Re: Межпотоковое кидание исключений
От: MaximE Великобритания  
Дата: 03.06.05 12:57
Оценка: 1 (1) +3
adontz wrote:

[]

> Мне такое удобно делать, чтоб остановить поток слушающий сокет, потому что message-queue там нету и по-другому сообщение не передать.


Пусть поток слушает еще один сокет при помощи select(), в который ты будешь писать команды.

--
Maxim Yegorushkin
Posted via RSDN NNTP Server 1.9
Re: Межпотоковое кидание исключений
От: SergH Россия  
Дата: 03.06.05 13:16
Оценка:
Здравствуйте, 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 указывает на последнее пристутствующее в стеке слово, а не на первое отсутствующее.
Делай что должно, и будь что будет
Re[2]: Межпотоковое кидание исключений
От: SergH Россия  
Дата: 03.06.05 13:21
Оценка: +1
Здравствуйте, 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)
    {
    }
}


Поэтому, имхо, ты не прав.
Делай что должно, и будь что будет
Re[2]: Межпотоковое кидание исключений
От: adontz Грузия http://adontz.wordpress.com/
Дата: 03.06.05 13:30
Оценка:
Здравствуйте, MaximE, Вы писали:

ME>Пусть поток слушает еще один сокет при помощи select(), в который ты будешь писать команды.


Этим решением я раньше пользовался
A journey of a thousand miles must begin with a single step © Lau Tsu
Re[2]: Межпотоковое кидание исключений
От: adontz Грузия http://adontz.wordpress.com/
Дата: 03.06.05 13:32
Оценка:
Здравствуйте, MaximE, Вы писали:

ME>Представь, что когда ты останавливаешь поток, в которой ты хочешь прокинуть исключение, этот поток находится в коде, скомпилированном с синхронной моделью обработки исключений, или в ф-ции third-party С библиотеки или в С/С++ runtime (malloc, к примеру). Тот код не ожидает исключения и не сможет его обработать, оставив структуры данных в рассогласованном состоянии...


А ещё может быть, что код в том потоке вообще дельфийский и Си++ исключения ловить не умеет.
Конечно этот механизм надо применять с осторожностью, но это не делает его менее применимым.
A journey of a thousand miles must begin with a single step © Lau Tsu
Re: Межпотоковое кидание исключений
От: zelyony  
Дата: 03.06.05 13:33
Оценка:
// test.cpp

#include <iostream>

#define _WIN32_WINNT 0x500
#include <winsock2.h>

#include <atlbase.h>
#include <atlsync.h>

using namespace std;
using namespace ATL;


template <typename _type_exception>
void _throw_remote_proc(_type_exception * exception_object)
{
    throw exception_object;
}
//
template <typename _type_exception>
__declspec(noreturn) void throw_remote(DWORD thread_id, _type_exception * exception_object)
{
    HANDLE hThread = OpenThread(THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME, FALSE, thread_id);
    SuspendThread(hThread);
    CONTEXT context;
    ZeroMemory(&context, sizeof(context));
    context.ContextFlags = CONTEXT_CONTROL;
    GetThreadContext(hThread, &context);
    context.Esp -= 4;
    context.Eip = (DWORD)((void(*)(_type_exception *))&_throw_remote_proc<_type_exception>);
    *((DWORD *)context.Esp) = (DWORD)exception_object;
    SetThreadContext(hThread, &context);
    ResumeThread(hThread);
    CloseHandle(hThread);
}

class ex
{
    public:
        int data;
    public:
        ex(): data(0)
        {
        }
        ex(int _data): data(_data)
        {
        }
};

#define NET2( prm, err) \
    { \
        int res = (prm); \
        if (res) { \
            cout << #prm << " ERROR=" << err << " at " <<  __FILE__ << ":" << __LINE__ << endl; \
            throw (int)err; \
        } \
    }

#define NET( prm) NET2( prm, WSAGetLastError())

//
DWORD CALLBACK ThreadProc(LPVOID lpParam)
{
    try
    {
        WSADATA wd;
        NET2( WSAStartup( 0x202, &wd), res);

        SOCKET sock = socket( AF_INET, SOCK_STREAM, 0);
        NET( sock == INVALID_SOCKET);

        sockaddr_in addr = { AF_INET };
        addr.sin_addr.s_addr = INADDR_ANY;
        addr.sin_port = htons( 30000);
        NET( bind( sock, (sockaddr*)&addr, sizeof(addr)));

        NET( listen( sock, 10));
        cout << "accept..." << endl;
        int len = sizeof(addr);
        SOCKET s = accept( sock, (sockaddr*)&addr, &len);
        cout << "here" << endl;
    }
    catch (ex * e)
    {
        cout << "EXCEPTION in "__FUNCTION__ << endl;
        // Попадаем сюда, то есть всё работает
        int x = e->data;
        delete e;
    } catch( int e) { 
        cout << "ERROR" << endl;
    } catch(...) { 
        cout << "UNEXPECTED" << endl;
    }
    return 0;
}
//
int main(int argc, char * argv[])
{
    try {
    DWORD thread_id;
    CloseHandle( CreateThread( 0, 0, &ThreadProc, 0, 0, &thread_id));
    Sleep( 5000);

    ex * e = new ex(10);
    throw_remote( thread_id, e);

    Sleep( 5000);
    cout << __FUNCTION__ << endl;
    } catch(...) {
        cout << "UNEXPECTED in "__FUNCTION__ << endl;
    }
    return 0;
} 

#pragma comment( lib, "ws2_32.lib")

cl.exe -EHa -MD test.cpp

вывод
accept...
UNEXPECTED in main
UNEXPECTED in main
?
Posted via RSDN NNTP Server 1.9
Re[2]: Межпотоковое кидание исключений
От: adontz Грузия http://adontz.wordpress.com/
Дата: 03.06.05 13:37
Оценка:
Здравствуйте, SergH, Вы писали:

SH>Имхо, выделенные строчки надо поменять местами. ESP указывает на последнее пристутствующее в стеке слово, а не на первое отсутствующее.


Там ещё должен быть адрес возвращения из функции _throw_remote_proc, но так как из неё не возвращаются, то и адрес я не указываю.
Кстати __declspec(noreturn) я получается не туда поставил throw_remote как раз возвращает управления в отличие от простого throw.
A journey of a thousand miles must begin with a single step © Lau Tsu
Re[3]: Межпотоковое кидание исключений
От: MaximE Великобритания  
Дата: 03.06.05 13:47
Оценка: +1
SergH wrote:

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

>
> ME>Представь, что когда ты останавливаешь поток, в которой ты хочешь прокинуть исключение, этот поток находится в коде, скомпилированном с синхронной моделью обработки исключений, или в ф-ции third-party С библиотеки или в С/С++ runtime (malloc, к примеру). Тот код не ожидает исключения и не сможет его обработать, оставив структуры данных в рассогласованном состоянии...
>
> Насколько я знаю, такой код допустим:

[]

> Поэтому, имхо, ты не прав.


Тогда расскажи мне, как С-шный код, например код malloc, обработает не весть откуда взявшеесе С++ исключение, откатив все сделанные к этому моменту действия?

Другими словами, чтобы этот код работал и сохранял прогу в определенном состаянии (инвариант), нужно, чтобы весь код, который использует твоя прога, обеспечивал strong exception safety. Чтобы далеко не ходить, можно попробовать отыскать такой код в С — runtime твоего компилятора...

--
Maxim Yegorushkin
Posted via RSDN NNTP Server 1.9
Re[4]: Межпотоковое кидание исключений
От: SergH Россия  
Дата: 03.06.05 13:53
Оценка:
Здравствуйте, MaximE, Вы писали:

ME>Тогда расскажи мне, как С-шный код, например код malloc, обработает не весть откуда взявшеесе С++ исключение, откатив все сделанные к этому моменту действия?


Честно говоря, я вообще не очень хорошо представляю, как происходит обработка C++-ных исключений. Буду рад, если ты мне объяснишь. Или ссылку дашь.. Мои аргументы по анологии с SEH: для того чтобы обрабатывать SEH от кода не требуется ничего, любой справится. Если с C++ не так — объясни.
Делай что должно, и будь что будет
Re[3]: Межпотоковое кидание исключений
От: MaximE Великобритания  
Дата: 03.06.05 13:55
Оценка: +1
adontz wrote:

> ME>Представь, что когда ты останавливаешь поток, в которой ты хочешь прокинуть исключение, этот поток находится в коде, скомпилированном с синхронной моделью обработки исключений, или в ф-ции third-party С библиотеки или в С/С++ runtime (malloc, к примеру). Тот код не ожидает исключения и не сможет его обработать, оставив структуры данных в рассогласованном состоянии...

>
> А ещё может быть, что код в том потоке вообще дельфийский и Си++ исключения ловить не умеет.
> Конечно этот механизм надо применять с осторожностью, но это не делает его менее применимым.

На мой взгляд он вообще неприменим.

Повторюсь еще раз, что практически любая С++ программа использует в user mode C и С++ код, который не может восстанавливаться после исключений — С и С++ рантайм. Чтобы бросить твоим способом исключение, необходимо гарантировать, что в момент его выстрела, не выполняется такой "небезопасный" с точки зрения исключений код. Это можно гарантировать, но реализации, на мой взгляд, будет слишком сложна, чтобы быть надежной.

Я не вижу, как можно использовать этот код с гарантированно предсказуемым результатом.

http://rsdn.ru/forum/?mid=1205394
Автор: MaximE
Дата: 03.06.05


--
Maxim Yegorushkin
Posted via RSDN NNTP Server 1.9
Re: Межпотоковое кидание исключений
От: Tom Россия http://www.RSDN.ru
Дата: 03.06.05 14:04
Оценка:
A>Мне такое удобно делать, чтоб остановить поток слушающий сокет, потому что message-queue там нету и по-другому сообщение не передать.
А порт завершения ввода вывода — не поможет ?
... << RSDN@Home 1.1.4 beta 4 rev. 303>>
Народная мудрось
всем все никому ничего(с).
Re[2]: Межпотоковое кидание исключений
От: adontz Грузия http://adontz.wordpress.com/
Дата: 03.06.05 14:29
Оценка:
Здравствуйте, zelyony, Вы писали:

Во-первых: __declspec(noreturn) убери и поставь у _throw_remote_proc (уже писал об этом, извини перепутал)
Во-вторых: accept судя по всему использует что-то вроже WaitForSingleObject, потому что если вызов accept заменить на for(;;Sleep(1)); то всё работает.
Если кто-то знает как вывести поток из состояния ожижания буду благодарен.
A journey of a thousand miles must begin with a single step © Lau Tsu
Re[3]: Межпотоковое кидание исключений
От: SergH Россия  
Дата: 03.06.05 14:32
Оценка: +1
Здравствуйте, adontz, Вы писали:

A>Если кто-то знает как вывести поток из состояния ожижания буду благодарен.


Ожидать алертабельно и использовать APC.
Делай что должно, и будь что будет
Re[5]: Межпотоковое кидание исключений
От: adontz Грузия http://adontz.wordpress.com/
Дата: 03.06.05 14:37
Оценка: 18 (2)
Здравствуйте, SergH, Вы писали:

SH>Честно говоря, я вообще не очень хорошо представляю, как происходит обработка C++-ных исключений. Буду рад, если ты мне объяснишь.


Объясню как это делается в Microsoft Visual C++ начиная с версии 6 (может и раньше так же работало, но я не проверял)

Оператор try заменятся на засылку по адресу FS:[00000000] адреса таблицы соответсвия тип<->catch block (всё несколько сложнее, но по сути так)
Оператор throw инициирует простотр таблицы соответствия находящейся по адресу FS:[00000000] и если найдён соответствующий блок обработки то управление передайтся ему (всё несколько сложнее, но по сути так)

Оператору throw совершенно плевать откуда он вызвался если по адресу FS:[00000000] корректная таблица
Адреса деструкторов локальных объектов храниться рядом с таблицей соответсвия (которая по адресу FS:[00000000])

Таким образом вызов деструкторов и нужного блока catch выполняется вне зависимости от того откуда вызван throw.
A journey of a thousand miles must begin with a single step © Lau Tsu
Re[2]: Межпотоковое кидание исключений
От: adontz Грузия http://adontz.wordpress.com/
Дата: 03.06.05 14:37
Оценка:
Здравствуйте, Tom, Вы писали:

Tom>А порт завершения ввода вывода — не поможет ?


Может и поможет, но просто идея красивая...
A journey of a thousand miles must begin with a single step © Lau Tsu
Re: Межпотоковое кидание исключений
От: MaximE Великобритания  
Дата: 03.06.05 22:42
Оценка: 22 (3) +1
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 (конечно при условии, что указатели валидны).

     static node* prepend(node* head, node* n)
     {
         n->prev = head->prev;
         n->next = head;
         head->prev->next = n; // (*)
         return head->prev = n;
     }


Для валидного списка, этот код всегда исполняется от начала до конца, т.к. операции присвоения для валидных указателей никогда не могут бросить исключения.

Если в поток, выполняющий этот код, пробросить исключение предлагаемым варварским методом в тот момент, когда была выполнена строка (*), но не была еще выполнена следующая строка, список будет разрушен. Чтобы этот код сохранил свойство nothrow при таких новых условиях, когда исключение может возникнуть в любой момент из ниоткуда "на ровном месте", возможно необходимо либо каждую строку обернуть в __try / __except конструкцию, либо, возможно, исполнять весь код в блоке __finally (я не спец в SEH). Никто, естественно, в здравом уме этого делать не будет. Примеры подобного кода можно приводить бесконечно.

Вывод из этого следующий: ты просто не можешь прервать выполнение любого потока в произвольной точке и перевести управление при помощи исключения в другую точку, ожидая при этом, что состояние структур данных сохранит инвариант и останется в каком-либо относительно корректном состоянии.

Да, поначалу выглядит круто порулить руками регистрами и наебать остальной код и компилятор, но if you lie to the compiler, it will take its revenge...

--
Maxim Yegorushkin
Posted via RSDN NNTP Server 1.9
Re[2]: Межпотоковое кидание исключений
От: adontz Грузия http://adontz.wordpress.com/
Дата: 03.06.05 23:35
Оценка:
Здравствуйте, MaximE, Вы писали:

ME>Куря кольян, на меня снизошло понимание: брошенное таким способом исключение разрушает любой код, включая код с гарантиями strong exception safety и nothrow. Этот метод нельзя использовать никогда, когда предполагается восстановление и продолжение работы после обработки исключения.


Интересно как ты себе представляешь продолжение работы после программного исключения?

ME>Код с гарантиями strong exception safety и nothrow и весь код на С полагается на операции, которые гарантировано не бросают исключений, такие, как, например, многие простые операции над встроенными типами.


Предположим, что так.

ME>Рассмотрим операцию вставки в двусвязанный список. Эта операция является nothrow (конечно при условии, что указатели валидны).

static node* prepend(node* head, node* n)
{
  n->>prev = head->prev;
  n->>next = head;
  head->>prev->next = n; // (*)
  return head->prev = n;
}


Замечательно.

ME>Для валидного списка, этот код всегда исполняется от начала до конца, т.к. операции присвоения для валидных указателей никогда не могут бросить исключения.


Чудесно!

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


А вот отсюда уже не верно. С какой стати он будет разрушен? Квадратики и стрелки на салфетке рисовал? После выполнения указанной строки весь список от начала и до конца можно проглядеть имея указатель head. Да, return не выполнился, ну и что? Что при этом мешает корректно отработать деструкторам?

ME>Вывод из этого следующий: ты просто не можешь прервать выполнение любого потока в произвольной точке и перевести управление при помощи исключения в другую точку, ожидая при этом, что состояние структур данных сохранит инвариант и останется в каком-либо относительно корректном состоянии.


Во-первых, могу. Во-вторых, могу не только я. Требования такие же как и у Multiple Readers — Single Writer многопоточных структур. В частности весь STLPort будет на это нормально реагировать.
A journey of a thousand miles must begin with a single step © Lau Tsu
Re[3]: Межпотоковое кидание исключений
От: MaximE Великобритания  
Дата: 04.06.05 07:44
Оценка:
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 не смогут оправиться после таких исключений.

--
Maxim Yegorushkin
Posted via RSDN NNTP Server 1.9
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.