Версия для печати
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум на Исходниках.RU > C/C++: Сетевое программирование > I/O Completition Port проблемы с удалением OVERLAPPED объектов


Автор: progman 24.03.16, 13:30
Ситуация такая - есть объект унаследованный от OVERLAPPED, который ассоциируется портом завершения.

Я столкнулся с ситуацией разрулить которую никак не могу:
объекты уходят на удаление в двух случаях:
- GetQueuedCompletionStatus вернул FALSE или *lpNumberOfBytes вернулось ноль
- внутренняя серверная логика приняла решение что клиент должен быть дисконнектнут

Схема удаления объекта выглядит так:
- все что подлежит удалению заносится в список на удаление.
- по тику таймера этот список проверяется
- если у объекта из списка макрос HasOverlappedIoCompleted возвращает FALSE такой объект не удаляется, список проверяется дальше.
- если HasOverlappedIoCompleted возвращает TRUE то сначала закрывается сокет ( вызов closesocket ) и потом удаление самого объекта и стирание его из списка.

проблема в том что накапливается очень быстро большое число объектов для которых HasOverlappedIoCompleted всегда возвращает FALSE
вызов CancelIO не помогает.

Автор: shm 24.03.16, 14:11
А если через GetQueuedCompletionStatus попробовать?

Автор: Pacific 24.03.16, 14:13
Цитата progman @
вызов CancelIO не помогает.

CancelIO надо вызывать из того же потока, который эту IO инициировал.

Добавлено
И да, если закрыть сокет, то все pending IO обязательно отменятся с соответствующим кодом ошибки.

Добавлено
Цитата progman @
проблема в том что накапливается очень быстро большое число объектов для которых HasOverlappedIoCompleted всегда возвращает FALSE

Это скорее всего клиенты с невыполненным WSARecv() на сервере, которые не торопятся слать байты, или вообще отвалились.

Автор: progman 24.03.16, 14:39
Цитата shm @
А если через GetQueuedCompletionStatus попробовать?

Она крутится в рабочем потоке. не уверен что приемлемо ее вызывать из потока в котором объекты киляются для определения статуса.

Цитата Pacific @
CancelIO надо вызывать из того же потока, который эту IO инициировал.

я в курсе. и есть еще CancelIOEx
но все равно не решает проблему (

Цитата Pacific @
И да, если закрыть сокет, то все pending IO обязательно отменятся с соответствующим кодом ошибки.

Это рождает каскад других побочных проблем - в частности оъект на удаление может и дважды и трижды попасть в список.
в общем начинается адЪ с многопоточностью - я отказался от этого и закрываю сокет непосредственно перед удалением объекта

Автор: shm 24.03.16, 14:44
Цитата progman @
не уверен что приемлемо ее вызывать из потока в котором объекты киляются для определения статуса.

Почему?

Автор: Pacific 24.03.16, 14:57
Цитата progman @
в частности оъект на удаление может и дважды и трижды попасть в список.

Вот с этого и надо начать все разгребать. Простой атомарный флаг "объект в расстрельном списке" решает проблему. Удалось атомарно поменять 0 на 1 - значить можно спокойно вызвать closesocket прямо там же и поместить объект в список.

Автор: progman 24.03.16, 15:03
Цитата Pacific @
Цитата progman @
в частности оъект на удаление может и дважды и трижды попасть в список.

Вот с этого и надо начать все разгребать. Простой атомарный флаг "объект в расстрельном списке" решает проблему. Удалось атомарно поменять 0 на 1 - значить можно спокойно вызвать closesocket прямо там же и поместить объект в список.

ситуация:
внутри объекта произошла ошибка и он помещается в список на "расстрел" при этом вызывается закрытие сокета
далее три варианта:
- все будет нормально и объект удалится без последствий для системы и вызов GetQueuedCompletionStatus не произойдет
- после закрытия сокета но до момента удаления сработает GetQueuedCompletionStatus и выдаст что у этого объекта ноль байт в приемнике и он снова будет помещен в очередь на удаление
- после закрытия сокета и после удаления сработает GetQueuedCompletionStatus и выдаст или ошибку по этому объекту или то что у него в приемнике ноль байт и уже удаленный объект будет помещен в очередь на удаление

Автор: shm 24.03.16, 15:04
Цитата progman @
Это рождает каскад других побочных проблем - в частности оъект на удаление может и дважды и трижды попасть в список.

А пометить его никак нельзя?

Автор: progman 24.03.16, 15:08
Цитата shm @
Цитата progman @
Это рождает каскад других побочных проблем - в частности оъект на удаление может и дважды и трижды попасть в список.

А пометить его никак нельзя?

что значит пометить? факт помещения объекта в очередь на удаление разве не метка?
или имеется ввиду после удаление хранить его адрес в списке удаленных?

вся проблема исключительно в том что закрытие сокета у объекта иногда приводит иногда не приводит к срабатыванию GetQueuedCompletionStatus в рабочем потоке для этого объекта.

Автор: shm 24.03.16, 15:13
Цитата progman @
факт помещения объекта в очередь на удаление разве не метка?

Ну раз у тебя один и тот же объект 2 раза в очередь попадает, то значит нет.

Автор: Pacific 24.03.16, 15:13
А, теперь понятно. В моем типовом коде IOCP сервере есть счетчик pending IO операций. Когда вызываю WSASend/WSARecv/AcceptEx/DisconnectEx, счетчик увеличивается, когда получаю результат через GetQueuedCompletionStatus, счетчик уменьшается. Если счетчик стал равен 0 и объект помечен на удаление, то он удаляется (на самом деле не удаляется, а зачищается от старых данных и помещается в пул свободных объектов).

Добавлено
Так что просто помечай на удаление, вызывай closesocket + считай, сколько там осталось pending IO.

Автор: progman 24.03.16, 15:31
Цитата shm @
Цитата progman @
факт помещения объекта в очередь на удаление разве не метка?

Ну раз у тебя один и тот же объект 2 раза в очередь попадает, то значит нет.

у меня есть хак - есть список всех когда либо созданных в системе объектов
после удаления он из списка выбывает. Перед попыткой удалить идет проверка - есть ли объект в списке или нет.

жопа в том что после удаления уже нет никакой возможности понять что это повторное удаление объекта

Добавлено
Цитата Pacific @
А, теперь понятно. В моем типовом коде IOCP сервере есть счетчик pending IO операций. Когда вызываю WSASend/WSARecv/AcceptEx/DisconnectEx, счетчик увеличивается, когда получаю результат через GetQueuedCompletionStatus, счетчик уменьшается. Если счетчик стал равен 0 и объект помечен на удаление, то он удаляется (на самом деле не удаляется, а зачищается от старых данных и помещается в пул свободных объектов).

Добавлено
Так что просто помечай на удаление, вызывай closesocket + считай, сколько там осталось pending IO.

чем это будет отличаться от проверки HasOverlappedIoCompleted которая у меня есть и которая не надежно работает ?

Более того мы можем 10 раз вызвать WSARecv и получить все данные за дин вызов. Как корректно счетчик тогда вести?

Автор: Pacific 24.03.16, 15:36
progman
HasOverlappedIoCompleted вообще не необходимости использовать. GetQueuedCompletionStatus в любом случае вернет результат операции, либо когда она сама завершится, либо будет отменена из-за closesocket. Кстати, у тебя на один объект только один OVERLAPPED буфер ("объект унаследованный от OVERLAPPED")? Тогда тебе категорически нельзя вызывать любые overlapped операции с объектом, если текущая не завершилась. Может в этом проблема?

Добавлено
Цитата
Any pending overlapped send and receive operations ( WSASend/ WSASendTo/ WSARecv/ WSARecvFrom with an overlapped socket) issued by any thread in this process are also canceled. Any event, completion routine, or completion port action specified for these overlapped operations is performed. The pending overlapped operations fail with the error status WSA_OPERATION_ABORTED.

Все должно работать со связкой closesocket -> GetQueuedCompletionStatus. Если не работает - у тебя баг где-то значит.

Добавлено
Цитата progman @
Более того мы можем 10 раз вызвать WSARecv и получить все данные за дин вызов. Как корректно счетчик тогда вести?

Тогда GetQueuedCompletionStatus вернется 10 раз, по одному на каждый вызов. На каждый вызов (если они идут параллельно) надо передавать отдельную структуру OVERLAPPED!

Автор: Oleg2004 24.03.16, 15:50
Цитата progman @
чем это будет отличаться от проверки HasOverlappedIoCompleted которая у меня есть и которая не надежно работает ?

Возможно HasOverlappedIoCompleted ненадежно работает поэтому:
Цитата
Do not call this macro unless the call to GetLastError returns ERROR_IO_PENDING, indicating that the overlapped I/O has started.

Особенно для этого случая:
Цитата
- внутренняя серверная логика приняла решение что клиент должен быть дисконнектнут

Автор: progman 24.03.16, 15:52
Цитата Pacific @
progman
HasOverlappedIoCompleted вообще не необходимости использовать. GetQueuedCompletionStatus в любом случае вернет результат операции, либо когда она сама завершится, либо будет отменена из-за closesocket. Кстати, у тебя на один объект только один OVERLAPPED буфер ("объект унаследованный от OVERLAPPED")? Тогда тебе категорически нельзя вызывать любые overlapped операции с объектом, если текущая не завершилась. Может в этом проблема?


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

Как мне дальше правильно поступить?

Добавлено
Цитата Oleg2004 @
Цитата progman @
чем это будет отличаться от проверки HasOverlappedIoCompleted которая у меня есть и которая не надежно работает ?

Возможно HasOverlappedIoCompleted ненадежно работает поэтому:
Цитата
Do not call this macro unless the call to GetLastError returns ERROR_IO_PENDING, indicating that the overlapped I/O has started.

да. но как мне гарантированно знать что закрывать сокет можно и объект можно убивать?
чтобы не произошел при этом вызов GetQueuedCompletionStatus

от этого все мои проблемы (

Автор: shm 24.03.16, 15:53
progman, я привольно понимаю, что у тебя сейчас HasOverlappedIoCompleted может бесконечно вызываться ДО closesocket?

Автор: Oleg2004 24.03.16, 15:56
Цитата progman @
Вот у меня есть поток в который приходит команда - объект номер 1234 нужно убить.

Что за объект - поточнее:
1. Это одиночная оверлапнутая структура?
2. Это буфер нескольких таких структур?
3. ???варианты...
Определив что за объект, однозначно четко распознать статус самого объекта или что с ним еще может быть связано.
После этого что приемлемо по статусу - убивать...
Где-то так...мож что не въехал... :huh:

Автор: shm 24.03.16, 15:57
Цитата Oleg2004 @
Do not call this macro unless the call to GetLastError returns ERROR_IO_PENDING, indicating that the overlapped I/O has started.

Ну если хотя бы 1 одна операция с этой структурой overlapped была инициализирована до этого, то этот макрос должен работать нормально. Но я вообще никогда не видел в коде IOCP ее использования. И согласен с Pacific, что можно достаточно следить только за GetQueuedCompletionStatus.

Автор: Pacific 24.03.16, 15:58
Цитата progman @
Вот у меня есть поток в который приходит команда - объект номер 1234 нужно убить.

Как мне дальше правильно поступить?

Ну как - помечаешь на удаление, вызываешь closesocket, потом (возможно в другом потоке) GetQueuedCompletionStatus вернет ошибку для текущей overlapped операции, обработчик ошибки увидит, что для этого объекта уже вызван closesocket и он помечен на удаление, и удалит его.

P.S. А разве GetQueuedCompletionStatus у тебя вызываются не из выделенных под это воркеров, а из рабочего потока? Тогда это просто плохая архитектура. Под GetQueuedCompletionStatus нужно обязательно отдельные потоки, т.к. он может вернуть управление через произвольное время.

Автор: shm 24.03.16, 15:58
Цитата progman @
Как мне дальше правильно поступить?

А что за нужда убивать объект в отдельном потоке?

Добавлено
Цитата Pacific @
т.к. он может вернуть управление через произвольное время.

Кстати да, я этот момент упустил.

Автор: Oleg2004 24.03.16, 16:06
Цитата shm @
Ну если хотя бы 1 одна операция с этой структурой overlapped была инициализирована до этого, то этот макрос должен работать нормально. Но я вообще никогда не видел в коде IOCP ее использования.

Она применяется в основном только для целей синхронизации
Цитата
Synchronization Macros

The following macros are used with synchronization:
HasOverlappedIoCompleted
MemoryBarrier
PreFetchCacheLine
YieldProcessor


Цитата
И согласен с Pacific, что можно достаточно следить только за GetQueuedCompletionStatus.

Я тоже к этому склоняюсь :)

Автор: progman 24.03.16, 16:10
Цитата Oleg2004 @
Цитата progman @
Вот у меня есть поток в который приходит команда - объект номер 1234 нужно убить.

Что за объект - поточнее:
1. Это одиночная оверлапнутая структура?
2. Это буфер нескольких таких структур?
3. ???варианты...
Определив что за объект, однозначно четко распознать статус самого объекта или что с ним еще может быть связано.
После этого что приемлемо по статусу - убивать...
Где-то так...мож что не въехал... :huh:

под каждого законенктившегося клиента создается объект
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    class ovpConnection: public OVERLAPPED

каждый объект заносится в список
создается N рабочих потоков в которых вызывается GetQueuedCompletionStatus и обрабатываются события.

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

Этот объект я заношу в очередь ожидания на удаление.
В отдельном потоке эта очередь обрабатывается.
и объекты удаляются

дальше две ситуации:
1. я НЕ использую макрос HasOverlappedIoCompleted и ореинтируюсь исключительно на критические секции. При этом после закрытия сокета и удаления объекта 1234 в рабочем потоке может произойти вызов GetQueuedCompletionStatus а может и не произойти - я закономерность не понимаю Она по разному себя ведет.
GetQueuedCompletionStatus выдаст в поле *lpOverlapped только что удаленный объект. "только что" понятие тут растяжимое может вызваться через миллисекунду а может через 3 секунды ( у меня рекорд 5 секунд был )
То что объект удален я определить уже никак не могу в этот момент - и мы падаем.

2. В потоке который обрабатывает очередь объектов на удаление я у каждого объекта уточняю статус с помощью макроса HasOverlappedIoCompleted и удаляю только те у кого нет в обработке операций
Падений нет но куча объектов никогда не удаляются и очередь растет.

Автор: Oleg2004 24.03.16, 16:31
Цитата progman @
под каждого законенктившегося клиента создается объект

Честно говоря, я не понимаю внутренней причины для создания такого объекта. :no-sad: Может быть доцент был тупой :D , но в сокетных операциях оверлапедная структура указывается только в конкретных вызовах send/recv, причем для каждого конкретного вызова отдельная своя структура.
Цитата progman @
я закономерность не понимаю Она по разному себя ведет.
GetQueuedCompletionStatus выдаст в поле *lpOverlapped только что удаленный объект. "только что" понятие тут растяжимое может вызваться через миллисекунду а может через 3 секунды ( у меня рекорд 5 секунд был )

Ну вот надыбал что то со временными задержками (Микрософт пишет):
Цитата
Проблема
После асинхронного ввода-вывода на жесткий диск, HasOverlappedIoCompleted возвращает TRUE, а также GetOverlappedResult() возвращает 0 байт, переданных проблемы приложения (например, Microsoft SQL Server).

Примечание: После нескольких микросекунды, GetOverlappedResult() возвращает правильное значение.
Причина
В первую очередь эта проблема наблюдалась на компьютерах, симметричная многопроцессорная обработка (SMP), где завершения ввода/вывода обновляет поля в структуре OVERLAPPED в неверном порядке.

Цитата progman @
Падений нет но куча объектов никогда не удаляются и очередь растет.

Объекты на мой взгляд какие-то левые <_< потому и ведут себя криво

Автор: shm 24.03.16, 16:33
Цитата progman @
в рабочем потоке может произойти вызов GetQueuedCompletionStatus а может и не произойти - я закономерность не понимаю Она по разному себя ведет.

А есть гарантия, что на момент закрвтия сокета есть активные операции ввода-вывода?
Тут мне видится 2 ситуации:
1. Операций нет и все можно сделать прямо на месте.
2. Есть операции, объект надо пометить, сокет закрыть и ждать когда завершится последняя операция.
Тут только с синхронизацией надо аккуратно.

Автор: progman 24.03.16, 16:40
Цитата shm @
Цитата progman @
в рабочем потоке может произойти вызов GetQueuedCompletionStatus а может и не произойти - я закономерность не понимаю Она по разному себя ведет.

А есть гарантия, что на момент закрвтия сокета есть активные операции ввода-вывода?
Тут мне видится 2 ситуации:
1. Операций нет и все можно сделать прямо на месте.
2. Есть операции, объект надо пометить, сокет закрыть и ждать когда завершится последняя операция.
Тут только с синхронизацией надо аккуратно.

все замечательно - но как я узнаю что операция есть? и как я узнаю что она закончена?

Автор: shm 24.03.16, 16:42
Цитата progman @
все замечательно - но как я узнаю что операция есть?

Ты можешь для отладки сделать счетчики, как Pacific советует?
Цитата progman @
и как я узнаю что она закончена?

Если пришло по ней событие - значит закончена, счетчик уменьшился на 1.

Автор: progman 24.03.16, 16:44
Цитата shm @
Цитата progman @
все замечательно - но как я узнаю что операция есть?

Ты можешь для отладки сделать счетчики, как Pacific советует?
Цитата progman @
и как я узнаю что она закончена?

Если пришло по ней событие - значит закончена, счетчик уменьшился на 1.

Надо попробовать но есть скептицизм
щас займусь - как будет результат я отпишусь

Автор: shm 24.03.16, 16:59
Тут есть есть нехорошие моменты с синхронизацией:
поток 1 установил флаг удаления объекта и закрыл сокет
произошло переключения контекста на поток 2
поток 2 уменьшил значения счетчика и он стал 0. флаг удаления установлен, поэтому объект удаляется
произошло переключение контекста на поток 1
поток 1 пытается проверить значения счетчика у уже разрушенного объекта со всеми вытекающими.

Автор: Oleg2004 24.03.16, 17:04
Цитата progman @
При этом после закрытия сокета и удаления объекта 1234 в рабочем потоке может произойти вызов GetQueuedCompletionStatus а может и не произойти - я закономерность не понимаю Она по разному себя ведет.

Вот обратил внимание на эту фишку - как это понять - может произойти, а может и нет?
Есть код, где прописан вызов GetQueuedCompletionStatus - и в этом месте он должен произойти...как иначе то???
Или есть условие - в одном случае - вызываем, в другом - нет. но ведь и это детерминировано...
Или поток сконструирован так - что гуляет по коду как хочет? :wacko:
Чето я архитектуру приложения недопонимаю...
PS
А можно кусочек кода с этими объектами
class ovpConnection: public OVERLAPPED
- где хранятся, как место хранения описано, как доступ к этому месту и откуда он возможен и прочее...чуть чуть...
А вообще у меня большое недоверие - можно ли эту уникальную структуру объявлять в виде класса, да еще и паблик. :( Пару полей в ней уникальны и используются только ядром...потому и проблемы бывают, как я выше писал.

Автор: progman 24.03.16, 17:48
Цитата Oleg2004 @
Цитата progman @
При этом после закрытия сокета и удаления объекта 1234 в рабочем потоке может произойти вызов GetQueuedCompletionStatus а может и не произойти - я закономерность не понимаю Она по разному себя ведет.

Вот обратил внимание на эту фишку - как это понять - может произойти, а может и нет?
Есть код, где прописан вызов GetQueuedCompletionStatus - и в этом месте он должен произойти...как иначе то???
Или есть условие - в одном случае - вызываем, в другом - нет. но ведь и это детерминировано...
Или поток сконструирован так - что гуляет по коду как хочет? :wacko:
Чето я архитектуру приложения недопонимаю...
PS
А можно кусочек кода с этими объектами
class ovpConnection: public OVERLAPPED
- где хранятся, как место хранения описано, как доступ к этому месту и откуда он возможен и прочее...чуть чуть...
А вообще у меня большое недоверие - можно ли эту уникальную структуру объявлять в виде класса, да еще и паблик. :( Пару полей в ней уникальны и используются только ядром...потому и проблемы бывают, как я выше писал.

А я не знаю.
на стороне сервера закрытие клиентского сокета может привести к срабатыванию вызова GetQueuedCompletionStatus в рабочем потоке а может нет
если вызов произошел то GetQueuedCompletionStatus может вернуть FALSE и как валидный ( еще не удаленный ) указатель на OVERLAPPED так и вернуть удаленный пару секунд назад объект OVERLAPPED
А может вернуть TRUE но кол-во байт 0 прочитанных в lpNumberOfBytes и аналогично или валидный указатель на OVERLAPPED или указательна только что удаленный объект.

вот такая херь у меня. и как разгребать я хз.

Добавлено
Цитата shm @
Цитата progman @
все замечательно - но как я узнаю что операция есть?

Ты можешь для отладки сделать счетчики, как Pacific советует?
Цитата progman @
и как я узнаю что она закончена?

Если пришло по ней событие - значит закончена, счетчик уменьшился на 1.

Получается что почти все время жизни объекта счетчик операций в 1 стоит.
Потому как после успешного завершения чтения или посылки данных объект вызывает чтение данных с порта и ждет когда они придут.

Автор: Oleg2004 24.03.16, 19:19
Цитата progman @
А я не знаю.
на стороне сервера закрытие клиентского сокета может привести к срабатыванию вызова GetQueuedCompletionStatus в рабочем потоке а может нет

:wall: :wall: :wall:
Вот этого я никак не могу понять.
Что вы понимаете под НЕсрабатыванием вызова GetQueuedCompletionStatus? :blink:
Решил почитать свой конспект лекций:
Цитата
Каждая завершившаяся тем или иным образом операция ввода/вывода, назначенная на данный порт завершения, сообщает о своем возврате с помощью специального системного пакета, который поступает в очередь завершенных запросов порта. Для обработки этой очереди и предназначены наши рабочие потоки. Достигается это с использованием функции GetQueuedCompletionStatus(), которая пытается извлечь из очереди пакет завершения соответствующей операции

Таким образом, другого способа определения статуса операции ввода/вывода просто нет.
Эту функцию мы просто обязаны вызывать.
Как же можно говорить о несрабатывании ВЫЗОВА?????? :wall: :'(

Цитата progman @
если вызов произошел то GetQueuedCompletionStatus может вернуть FALSE и как валидный ( еще не удаленный ) указатель на OVERLAPPED так и вернуть удаленный пару секунд назад объект OVERLAPPED
А может вернуть TRUE но кол-во байт 0 прочитанных в lpNumberOfBytes и аналогично или валидный указатель на OVERLAPPED или указатель на только что удаленный объект.
вот такая херь у меня. и как разгребать я хз.

Почитал у себя еще:
Цитата
При корректном завершении работы порта завершения главное - не освобождать память со структурой OVERLAPPED, пока выполняется какая-нибудь операция на сокете. Так же надо знать, что закрытие сокета с помощью closesocket() прерывает любые продолжающиеся операции на данном порту завершения.

И еще
Цитата
Пусть в нашу систему прибыло 100 байтов, в результате возникло событие FD_READ. За этими данными пришел сегмент с уведомлением о закрытии соединения на той стороне. В стек сетевых событий этого сокета добавился FD_CLOSE. Мы считываем 30 байтов – остается непрочитанными 70 байтов, потому снова возникает событие FD_READ. В стеке теперь расположены FD_CLOSE, за которым следует FD_READ. Если мы теперь обработаем FD_CLOSE и закроем сокет, мы потеряем оставшиеся в буфере приема данные. О чем это говорит? Полагаться на сетевое событие здесь не совсем логично – надо полагаться только на возврат функцией recv() значения 0.

В вашем случае это будет скорее всего lpNumberOfBytesTransferred=0

В общем если это ваш собственный сервер, то надо менять архитектуру и использовать структуру
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    typedef struct {
        OVERLAPPED Overlapped;
        //любые полезные нам данные:
        WSABUF DataBuf;
        CHAR Buffer[DATA_BUFSIZE];
        DWORD BytesSend;
        DWORD BytesRecv;
        DWORD OperationType;
        DWORD TotalBytes;
        .....
    } PER_IO_OPERATION_DATA;

КУда в //любые полезные нам данные: и можно будет вставить признаки всякие или счетчики.
Это есть в примере у меня в конспекте как это делать.

Автор: Pacific 24.03.16, 19:42
Цитата Oleg2004 @
Вот этого я никак не могу понять.
Что вы понимаете под НЕсрабатыванием вызова GetQueuedCompletionStatus?

Мне на ум пришел только один вариант - когда клиент отваливается так, что даже RST пакет не доходит до сервера, и сервер думает, что клиент все еще подключен. Но таких клиентов сервер сам должен отбрасывать по таймауту - либо getsockopt(socket, SOL_SOCKET, SO_CONNECT_TIME, ...), если соединение в стиле либо "вкл-запрос-ответ-выкл", либо вручную отслеживать, когда пришел последний пакет.

Автор: Oleg2004 24.03.16, 21:25
Цитата Pacific @
Мне на ум пришел только один вариант - когда клиент отваливается так, что даже RST пакет не доходит до сервера, и сервер думает, что клиент все еще подключен. Но таких клиентов сервер сам должен отбрасывать по таймауту - либо getsockopt(socket, SOL_SOCKET, SO_CONNECT_TIME, ...), если соединение в стиле либо "вкл-запрос-ответ-выкл", либо вручную отслеживать, когда пришел последний пакет.

Ну, прямо скажем - это единственный вариант, когда сервер ждет данных (любых, как и FIN-cегмента или RST) - слишком долго. Как долго он может ждать? До 2-х часов - это стандарт TCP.
Если рассматривать такую ситуацию с несрабатыванием - то вы правы. И способов грамотной организации таймаутов такого рода - достаточно много - это и проверка пульса с помощью срочных данных, и select(), и таймаут WSAWaitForMultipleEvents() и простой даже sleep().
Что то я думаю в архитектуре все-таки не то...
Но ведь автор пишет:
Цитата progman @
объекты уходят на удаление в двух случаях:
- GetQueuedCompletionStatus вернул FALSE или *lpNumberOfBytes вернулось ноль
- внутренняя серверная логика приняла решение что клиент должен быть дисконнектнут

Значит у этой внутренней логики какой-то есть внутренний критерий?
И если он срабатывает, то очевидно что вызов GetQueuedCompletionStatus() в этом случае в принципе может быть лишним...

Автор: progman 25.03.16, 02:23
Цитата Oleg2004 @
Что вы понимаете под НЕсрабатыванием вызова GetQueuedCompletionStatus?

Какое то недопонимание или я конкретно туплю.
Есть у меня 16 потоков и там крутится GetQueuedCompletionStatus и там происходит прием/передача и обработка пакетов
Есть еще два потока: номер 17 удаляющий и номер 18, который слушающий сервер управления.

Объект подлежащий удалению сливается в список на удаление который обрабатывается в потоке 17
Так вот, если в потоке 17 вызвать closesocket то в одном из потоков 1-16 GetQueuedCompletionStatus срабатывает и возвращает значение TRUE или FALSE а также lpNumberOfBytes вернет ноль но самое страшное что в поле lpOverlapped будет указание на структуру сокет который был закрыт и объект удален
и самое нехороше что это происходит не всегда иногда вызывается обработчик в GetQueuedCompletionStatus а иногда нет.

Понимаете? Я закрываю в потоке 17 сокет и удаляю OVERLAPPED структуру а она херакс и через некоторое время высплывает в обработчике в GetQueuedCompletionStatus
Я не знаю как гарантированно и безопасно удалить OVERLAPPED структуру чтобы она на 146% не была возвращена как результат работы функции GetQueuedCompletionStatus в одном из рабочих потоков 1-16 после ее удаления
:wall: :wall: :wall: :wall: :wall: :wall:

Автор: progman 25.03.16, 09:32
Сделал грязный хак - когда происходит занесение объекта в лист на удаление я закрываю сокет объекта и добавил 20 секундный таймер ожидания.
Через 20 секунд объект удаляется.

Автор: Pacific 25.03.16, 15:47
Цитата progman @
Я закрываю в потоке 17 сокет и удаляю OVERLAPPED структуру а она херакс и через некоторое время высплывает в обработчике в GetQueuedCompletionStatus

Так мы про что талдычим уже 3 страницы? Нельзя просто взять и удалить OVERLAPPED структуру, пока она используется системой. Надо ждать возврата ее из GetQueuedCompletionStatus, потом только удалять.

Автор: Oleg2004 25.03.16, 19:18
Цитата Pacific @
Нельзя просто взять и удалить OVERLAPPED структуру, пока она используется системой. Надо ждать возврата ее из GetQueuedCompletionStatus, потом только удалять.

Именно так и есть.
Если в некотором потоке закрывается сокет с помощью closesoket(), в очередь системных пакетов порта завершения ставится пакет, посылающий FIN клиенту. Каким по очереди он будет - сие науке неизвестно, и когда завершится - тем более. Система переключает на другие потоки, они роются в этой очереди, обрабатывают другие пакеты, и когда-то потом система доберется до сегмента FIN. Именно поэтому GetQueuedCompletionStatus срабатывает и возвращает значение какое уж там получится - то TRUE то FALSE. Ведь до отсылки пакета дело могло еще и не дойти - потоков то много, они все переключаются.
Отсюда и этот разброс по времени:
Цитата
GetQueuedCompletionStatus выдаст в поле *lpOverlapped только что удаленный объект. "только что" понятие тут растяжимое может вызваться через миллисекунду а может через 3 секунды ( у меня рекорд 5 секунд был )

Именно поэтому надо дожидаться реального возврата GetQueuedCompletionStatus и только после этого удалять все что ассоциировано программой с закрытым сокетом.
Именно поэтому то и срабатывает идея со слипом, о которой я писал раньше.
PS
Вообще то мелкомягкие ступили. Если сокет был создан для режима перекрытия, для которого обязательна оверлапная структура, то можно было поставить флаг автоматического или ручного ее освобождения при закрытии сокета. Это решило бы проблему. Скажем в нашем случае это должно быть ручное.
Еще одно замечание...
Сокет ассоциируется с портом...а вот отсоединения использованного сокета - увы...Опять мелкомягкие не додумали...

Автор: progman 04.04.16, 09:12
Цитата Pacific @
Цитата progman @
Я закрываю в потоке 17 сокет и удаляю OVERLAPPED структуру а она херакс и через некоторое время высплывает в обработчике в GetQueuedCompletionStatus

Так мы про что талдычим уже 3 страницы? Нельзя просто взять и удалить OVERLAPPED структуру, пока она используется системой. Надо ждать возврата ее из GetQueuedCompletionStatus, потом только удалять.

А я о чем талдычу? Я понятия не имею используется она сейчас или нет ( да это архитектурный просчет ).
Более того закрытие сокета как показали опыты не всегда ведет к вызову GetQueuedCompletionStatus в одном из рабочих потоков! Вот так. Я сокет закрыл а в GetQueuedCompletionStatus тишина, 5 секунд, 10 секунд, 20 секунд...
Я структуру удалил и секунд через 30 произошел вызов GetQueuedCompletionStatus а может и не произойти и все происходит нормально.
Я не нашел никакой зависимости.

Сейчас онлайн 7 тысяч постоянный. Раз в сутки описанная ситуация срабатывает и сервер падает.
То есть я закрываю сокет и жду 20 секунд перед тем как удалить структуру. И раз в 24 часа 20 секундного ожидания оказывается недостаточно!

PS CancelIoEx проблемы не решает ((( от нее не тепло не холодно

Добавлено
Цитата Oleg2004 @
Именно поэтому надо дожидаться реального возврата GetQueuedCompletionStatus и только после этого удалять все что ассоциировано программой с закрытым сокетом.

у меня был опыт из одного тестового клиента подключенного к серверу.
сокет клиента был закрыт по инициативе сервера. GetQueuedCompletionStatus не вызвалась вообще.
Ждал около часа. Keep-alive на 5 секунд сокету был назначен.
Клиент физически был закрыт на клиентской машине и машине был сделан резет.
все равно - тишина в GetQueuedCompletionStatus

я не понимаю ее логику работы при закрытии клиентского сокета на стороне сервера. совсем не понимаю.

PS если делать как вы мне тут советуете и удалять объекты ТОЛЬКО после вызова GetQueuedCompletionStatus то копится оч большая утечка памяти потому что для оч большого кол-ва соединений срабатывание не происходит

Автор: ter_nk_ 04.04.16, 09:19
Цитата progman @
да. но как мне гарантированно знать что закрывать сокет можно и объект можно убивать?
чтобы не произошел при этом вызов GetQueuedCompletionStatus


Добавь CRITICAL_SECTION

Автор: progman 04.04.16, 09:27
Цитата ter_nk_ @
Цитата progman @
да. но как мне гарантированно знать что закрывать сокет можно и объект можно убивать?
чтобы не произошел при этом вызов GetQueuedCompletionStatus


Добавь CRITICAL_SECTION

:D
куда??? в рабочий поток? и парализовать работу всего сервера?

Автор: Oleg2004 04.04.16, 19:09
Нашел кое-что почти по вашей теме.
Там тоже говорят о задержке - аж до 4 минут, но shutdown()вроде помог.
Т.е. о чем я думаю.
GetQueuedCompletionStatus() напрямую предназначена для отслеживания операций ввода-вывода - т.е. типа recv() и send().
Операция closesocket - тоже вроде бы I/O, да специфическая. В ней приложение и сокетные буфера в модуле TCP не используются - closesocket только лишь приказ модулю TCP послать служебный сегмент с флагом FIN. Видимо в ядре формирование системных пакетов порта завершения, относящихся к закрытию сокета, микрософт сделала криво. Отсюда и проблемы.
Ну и конечно же не соответствие вашей архитектуры рекомендациям:
Цитата
Обычно рекомендуется, чтобы функция CreateIoCompletionPort() задавала один поток для каждого отдельного процессора во избежание переключения контекста потока.

О чем речь?
О том, что вообще то порт завершения сделан специально под многопроцессорные системы. Один процессор - один рабочий поток, и никаких переключений контекстов. Сокетов сколько угодно, а рабочий поток - один. Есть варианты, что и на однопроцессорной машине можно несколько рабочих процессов иметь, но только со специальной архитектурой.
У вас же рабочих потоков как грязи, однако...а машина скорее всего однопроцессорная

Добавлено
Ну и еще вдогонку - что на самом деле представляет собой вызов GetQueuedCompletionStatus()
Цитата
После завершения асинхронной операции ввода/вывода для ассоциированного файла диспетчер ввода/вывода создает пакет запроса из структуры OVERLAPPED и ключа завершения и помещает его в очередь с помощью вызова KeInsertQueue(). Когда поток вызывает функцию GetQueuedCompletionStatus(), на самом деле вызывается функция NtRemoveIoCompletion(). NtRemoveIoCompletion() проверяет параметры и вызывает функцию KeRemoveQueue(), которая блокирует поток, если в очереди отсутствуют запросы, или поле CurrentCount структуры KQUEUE больше или равно MaximumCount. Если запросы есть, и число активных потоков меньше максимального, KeRemoveQueue() удаляет вызвавший ее поток из очереди ожидающих потоков и увеличивает число активных потоков на 1. При занесении потока в очередь ожидающих потоков поле Queue структуры KTHREAD устанавливается равным адресу порта завершения. Когда запрос помещается в порт завершения функцией PostQueuedCompletionStatus(), на самом деле вызывается функция NtSetIoCompletion(), которая после проверки параметров и преобразования хендла порта в указатель вызывает KeInsertQueue().

Куча вызовов, и не дай Бог закончится квант времени с последующим переключением контекста...мало не покажется.

Автор: progman 05.04.16, 05:09
Цитата Oleg2004 @
Ну и конечно же не соответствие вашей архитектуры рекомендациям:

Я в другой литературе встречал рекомендацию 2 рабочих потока на ядро.
У меня в сервере Intel® Xeon® E5-1650 v3 Hexa-Core Haswell - всего 12 рабочих потоков с GetQueuedCompletionStatus крутится
Плюс три служебных потока с мониторингаоми дополнительных служб.

Цитата Oleg2004 @
но shutdown()вроде помог.

спасибо - попробую

Автор: Oleg2004 05.04.16, 06:44
Посмотрел спецификацию Intel® Xeon® E5-1650 v3 Hexa-Core Haswel - это однопроцессорная 6-ти ядерная платформа.
Неплохо, но это все равно один процессор. :)

Цитата progman @
Я в другой литературе встречал рекомендацию 2 рабочих потока на ядро.

Ссылочку не дадите?
Но, как мне кажется, переключений контекстов даже в вашей конфигурации не избежать.
И конечно же совершенно недопустимо чтобы сервер падал. Это самая большая неприятность, остальное как бы можно пережить, а вот падение...
Решений наверно много. Можно ведь и не самописный сервер использовать, а пробовать коммерческую версию, если деньги есть.
Или поднять еще один сервер и балансировать нагрузку.
Можно и попробовать продумать какую-то новую архитектуру серверного проекта...
И тд...

Автор: Pacific 05.04.16, 09:01
2 рабочих потока на ядро нужно, чтобы если один из них на чем-то застрянет (файловые операции, обращение к базе данных и т.д.), то второй сможет дальше обрабатывать запросы. И не 2 потока на ядро, а N*2 потоков на N ядер, не привязанных к конкретным ядрам. Механизм IOCP все равно поставит ограничение в максимум N активных потоков (по умолчанию).

Автор: ЫукпШ 05.04.16, 09:14
Цитата progman @
Понимаете? Я закрываю в потоке 17 сокет и удаляю OVERLAPPED структуру а она херакс и через некоторое время высплывает в обработчике в GetQueuedCompletionStatus
Я не знаю как гарантированно и безопасно удалить OVERLAPPED структуру чтобы она на 146% не была возвращена как результат работы функции GetQueuedCompletionStatus в одном из рабочих потоков 1-16 после ее удаления

Один из возможных вариантов практического решения
проблемы буквально содержится в вопросе.
Для этого можно изменить структуру объектов, обслуживающих внешние запросы.
---
1. Допустим, поместили объект в список на уничтожение.
2. уничтожаем всё, кроме OVERLAPPED-структуры, которую
засылаем в отстойный список.
3. Если она будет получена как результат в GetQueuedCompletionStatus,
то будет законно удалена.
4. Если нет - уничтожим её по TOUT. Только не 20 секунд, а 20 часов. Или 50..
Памяти структура занимает не много. Такое "подвешивание" ресурсов, вероятно, можно допустить.
---
Исследования по правильной зачистке объектов надо продолжить.
Возможно, проблемы прекратятся после уничтожения порта завершения
и создания вместо него нового. Или существует какой-то баг, который
просто проявляется таким образом.

Автор: progman 05.04.16, 10:35
Цитата ЫукпШ @
2. уничтожаем всё, кроме OVERLAPPED-структуры, которую
засылаем в отстойный список.

я уже думал за сабж и тоже скланяюсь к такому решению
но сначала shutdown() попробую

Цитата Oleg2004 @
Ссылочку не дадите?

увы... было много лет назад. но читал в какой то толстой книге по сетевому программированию в виндовс.

Цитата Oleg2004 @
Можно ведь и не самописный сервер использовать, а пробовать коммерческую версию, если деньги есть.

я не смог найти.

Цитата Oleg2004 @
Или поднять еще один сервер и балансировать нагрузку.

у меня их уже 5 штук. по 7 тысяч онлайн на каждом.
я бы взял еще больше но тупа нету в стойке места. Мы и так 20 серверов отжали )

Автор: shm 05.04.16, 10:51
progman, а почему ты сделал все на голом API? Чем варинат с boost.asion не устроил?

Автор: ЫукпШ 05.04.16, 12:14
Цитата progman @
Цитата Oleg2004 @
Ссылочку не дадите?

увы... было много лет назад. но читал в какой то толстой книге по сетевому программированию в виндовс.

Вероятно, речь идёт о:
"Программирование серверных приложений для Windows 2000".
Джеффри Рихтер, Джейсон Кларк.
Можно найти в электронном виде.

Автор: Oleg2004 05.04.16, 14:27
Цитата progman @
у меня их уже 5 штук. по 7 тысяч онлайн на каждом.

Ну ни хрена себе втихую от нас :D такие проекты девелопите...респект.
Цитата ЫукпШ @
Вероятно, речь идёт о:
"Программирование серверных приложений для Windows 2000".

Спасибо буду искать.
Нашел - правда качество жуть, пришлось помучиться с преобразованием в doc.
Много интересного для себя нашел.
Здесь только о порте завершения самое главное.
IOCP.doc (, : 177)
Файлик малость подправил - даже оригинал в дэжавю такого качества что порой не разберешь.
Самое главное, что здесь Рихтер рассказал - это поведение рабочих потоков, выполняющих
GetQueuedCompletionStatus в цикле. И там есть свои заморочки однако.

Автор: progman 06.04.16, 10:56
Цитата shm @
progman, а почему ты сделал все на голом API? Чем варинат с boost.asion не устроил?

6 лет назад планировался маленький сервер на 1000 коннектов ( три раза ога ) и про буст я не знал.
когда все уже разраслось я тупо не смог найти адекватного серверного прогера который все переписал бы по человечески.
Была попытка на node.js уйти но тоже провалилась - не нашел квалифицированных кадров.

в итоге плюнул и серверную часть тяну сам на сях на голом апи. Хотя задолбался уже.

Автор: Oleg2004 22.04.16, 08:07
Цитата progman @
Объект подлежащий удалению сливается в список на удаление который обрабатывается в потоке 17
Так вот, если в потоке 17 вызвать closesocket то в одном из потоков 1-16 GetQueuedCompletionStatus срабатывает и возвращает значение TRUE или FALSE а также lpNumberOfBytes вернет ноль но самое страшное что в поле lpOverlapped будет указание на структуру сокета, который был закрыт и объект удален и самое нехорошее что это происходит не всегда. Иногда вызывается обработчик в GetQueuedCompletionStatus а иногда нет.

Все последнее время посвятил разбирательству механизмов связанных с IOCP.
Тем более что отслеживаю ветку progman'a про буст, и там тоже не все так просто.
Поэтому возвращаюсь опять в эту тему.
Попробую еще раз порассуждать на эту тему.
1. Итак, в 16-ти рабочих потоках крутятся (бесконечно висим)на GetQueuedCompletionStatus() - ну, если поток не работает (т.е. не проскочил уже свой вызов и не осуществляет обработку.)
2. Если во время обработки вызова GetQueuedCompletionStatus() в каком-то из потоков 1-16 lpNumberOfBytes вернет ноль, то этот же рабочий поток должен cам закрыть сокет - так как возвращенный нуль всегда трактуется как пришедший с той стороны FIN. Именно такой вариант закрытия сокета просматривается во всех примерах исходников.
3. В случае варианта progman'a решение о закрытии сокета принимается специальным 17-тым потоком. Т.е. это внешнее закрытие, происходящее вне компетенции объекта IOCP. И это скорее всего как раз таки решение о закрытии сокета из-за таймаута, так как клиент наверное отпал. Но тут одна загвоздка. Сокет был ассоциирован с портом завершения, и сlosesocket(), выполненный не в рабочем потоке, ассоциацию эту по видимому не удаляет.
4. Нашел в сети такую последовательность закрытия сокета:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    shutdown(sock->socket,SD_BOTH);
    closesocket(sock->socket);
    sock->socket = INVALID_SOCKET;

Может именно ее надо применять?
И теперь структура overlapped, указатель на которую может по каким-то причинам остаться в системном пакете очереди завершения, будет содержать инвалидный сокет, что и даст понимание для GetQueuedCompletionStatus() и она сработает совсем с другой ошибкой, что позволит правильно идентифицировать проблему.
Вот кстати и косвенное подтверждение моих предположений:
Цитата
The IOCP documentation says that application needs to call CloseHandle() to unregister the socket from the IOCP, and PJLIB does this in ioqueue_winnt.c.
We assumed that a call to CloseHandle() should close the socket (just like closesocket()), since a socket should just be another handle in Windows NT.
But turns out this is not the case. A call to CloseHandle() doesn't seem to close the socket, as the socket still appears in "netstat" output. Also further down it will cause "Invalid handle" exception to be raised in debug mode when WSACleanup() is called.
Checking at MSDN documentation about CloseHandle() (see ​http://msdn.microsoft.com/en-us/library/ms...11(VS.85).aspx), actually it does suggest application to call both for socket, i.e. always follow closesocket() call with CloseHandle(). This is what it says:
After closing a socket using the closesocket function, ensure that you release the socket by calling CloseHandle. For more information, see ​Socket Closure.

Автор: ЫукпШ 22.04.16, 09:07
Цитата Oleg2004 @
...
4. Нашел в сети такую последовательность закрытия сокета:
shutdown(sock->socket,SD_BOTH);
closesocket(sock->socket);
sock->socket = INVALID_SOCKET;
Может именнно ее надо применять?

Тут сразу такое предложение:
если "sock" в данном случае экземпляр класса-сокета,
оболочка вокруг "socket", тогда просто напрашивается
решение делать эти операции функциями самого класса.
Которые будут вызваны функцией "Uninstall".
Которая может быть вызвана из деструктора.

Автор: Oleg2004 22.04.16, 16:23
И еще одно рассуждение на эту тему.
Цитата progman @
если в потоке 17 вызвать closesocket то в одном из потоков 1-16 GetQueuedCompletionStatus срабатывает

Как closesoсket()??? в потоке №17 может вызвать срабатывание одной из функций GetQueuedCompletionStatus, которые крутятся в рабочих потоках 1-16, ассоциированных с IOCP и ПОЛНОСТЬЮ! им же управляемых?
Есть четкий перечень функций АПИ, которые могут сформировать системный пакет о завершении I/O c четырьмя полями:
dwBytesTransferred
dwCompletionKey
pOverlapped
dwError
в очередь IOCP
Это только
• ConnectNamedPipe()
• DeviceIoControl()
• LockFileEx()
• ReadDirectoryChangesW()
• ReadFile()
• TransactNamedPipe()
• WaitCommEvent()
• WriteFile()
• WSASendMsg()
• WSASendTo()
• WSASend()
• WSARecvFrom()
• WSARecvMsg()
• WSARecv()
Только ЭТИ - функции способны по своем завершении создать системный пакет о завершении.
Как видим, closesocket()в списке нет :)
Эта функция вообще не имеет отношения к операциям ввода-вывода на пользовательском уровне.
Так в чем же дело?
Это просто предположение.
GetQueuedCompletionStatus и closesocket исполняются в разных потоках. Рабочие потоки полностью управляются объектом IOCP, а closesocket выполняется в полностью отдельном потоке. Но и там и там - есть один и тот же объект и его характеристики - это клиентский сокет, он является так сказать общим ресурсом. Как этот доступ осуществляется - сие науке неизвестно. Вполне возможен побочный эффект в виде потоконебезопасности, который проявляется при определенном стечении обстоятельств.
И теперь про это самое стечение.
progman ничего не сообщил о том, в результате каких условий поток №17 принимает решение о закрытии клиентского сокета. Предположим, что это решение принимается по таймауту, когда клиентский сокет вообще заснул (или отвалился вообще).
Что это означает в отношении IOCP?
Это означает, что ни один из рабочих потоков не обрабатывал долгое время свои же вызовы WSARecv() (они наверняка понатыканы в каждом рабочем потоке). Нет вызова WSARecv() в каком то рабочем потоке - IOCP не назначит другой ожидающий поток на обработку записи о завершении этой операции - ее просто нет. Тем не менее есть пул рабочих потоков - которые висят в цикле GetQueuedCompletionStatus, но и им пакет завершения не может достаться, потому что его просто не существует.
Ну блин и где разгадка этого пазла???
Думаю все-таки где-то программно-архитектурная клизма...
По теории так быть не может.

Автор: Oleg2004 22.04.16, 22:34
Короче, прошу progman'a не оставить нас без своего участия в поиске столь интересной :wall: :wall: :wall: проблемы...

Автор: ЫукпШ 23.04.16, 10:02
Цитата Oleg2004 @
Ну блин и где разгадка этого пазла???

Для начала, я бы попробовал уничтожать сокет не из 17-го потока.
Эта ничтожная операция не требует больших затрат, а значит
можно использовать 16 используемых потоков.
В случае принудительной ликвидации сокета, я бы использовал
"PostQueuedCompletionStatus" и затем уничтожал сокет в обработчике
рабочего потока.

Автор: progman 23.04.16, 11:45
Цитата Oleg2004 @
progman ничего не сообщил о том, в результате каких условий поток №17 принимает решение о закрытии клиентского сокета. Предположим, что это решение принимается по таймауту, когда клиентский сокет вообще заснул (или отвалился вообще).
Что это означает в отношении IOCP?

Ну например вход второго клиента под таким же логином.
В этом случае оба клиента активны и "гонят" трафик.
По логике системы оба должны быть дисконнектнуты.

с переходом на boost.asion проблема рассосалась.

Автор: shm 23.04.16, 11:54
Цитата progman @
boost.asion

asio, не размножай мою опечатку. :)

Автор: Oleg2004 23.04.16, 16:16
Цитата progman @
с переходом на boost.asion проблема рассосалась.

У кого рассосалась, а у кого и нет... :(
Цитата progman @
В этом случае оба клиента активны и "гонят" трафик.
По логике системы оба должны быть дисконнектнуты.

Ну вот после этого стало более менее ясно.
Клиентский трафик через сокет идет, а поток 17 решает убить сокет с активными операциями.
Теперь после такого разъяснения все вроде бы встает на место.
Скорее всего это баг архитектуры приложения, не учитывающий взаимоотношения пакетов завершения рабочих потоков с проблемами закрытия (принудительного по сути) сокетов с активным трафиком. Ясен день, что в HTTP такой ситуации не может возникнуть по определению. Все решает пользовательский протокол. А его знает только
progman :yes:
Так что виндовское АПИ тут не причем... :yes: :no-sad:
Ух...а то я уж стал грешить на микрософт. Не, у них все Ок.

Автор: ЫукпШ 23.04.16, 21:35
Цитата Oleg2004 @
Скорее всего это баг архитектуры приложения, не учитывающий взаимоотношения пакетов завершения рабочих потоков с проблемами закрытия (принудительного по сути) сокетов с активным трафиком. Ясен день, что в HTTP такой ситуации не может возникнуть по определению. Все решает пользовательский протокол.

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

Автор: progman 24.04.16, 09:57
Цитата ЫукпШ @
другими словами, отсутствует синхронизация между работой очереди запросов обслуживания сокета
и принудительным уничтожением сокета.

собственно ее и сейчас нет.
в потоке 17 просто закрываю порт. Буст видимо внутри сам все разруливает как то - но деструктор коннектора вызывается практически мгновенно.

Автор: ЫукпШ 24.04.16, 10:56
Цитата progman @
собственно ее и сейчас нет.
в потоке 17 просто закрываю порт. Буст видимо внутри сам все разруливает как то ...

А он (Буст) точно использует порт завершения ?
Насколько я понимаю, это кросс-платформенная библиотека.
Это значит, что все исходники и под Линукс (теоретически) можно откомпилировать
и собрать версию сервера для Линукс. А там нет порта завершения а-ля Виндус,
значит всё придётся мастерить как-то иначе. Тогда какой смысл вообще использовать
эти технологии ?
---
В этой истории постоянно преследует чувство
глубокого неудовлетворения.. :huh:
Во первых, одна и та-же функция, "CreateIoCompletionPort", используется для
разных операций.
Второе - ещё хуже.
После установки ассоциации порта завершения и хэндла объекта, совершенно
не понятно, как правильно разорвать эту ассоциацию.
Как я понял, это и есть проблема данного обсуждения - сокет закрыт,
а его хэндл остался где-то в списке порта. И приходят запросы на обслуживание
уже давно закрытого сокета.

Автор: Oleg2004 24.04.16, 11:08
Цитата ЫукпШ @
Посредством размещения запроса на уничтожение сокета в очереди запросов порта завершения.

Такого запроса не существует, увы, для порта завершения.
Даже PostQueuedCompletionStatus, который просто эмулирует пакет завершения.
Цитата progman @
в потоке 17 просто закрываю порт.

Это каким таким образом вы закрываете порт? Чей порт?
Порт приложения? :wacko:
Или вы удаляете запись из таблицы TCP-соединений с помощью SetTcpEntry() и переводите TCP-соединение в MIB_TCP_STATE_DELETE_TCB – т.е. удалить запись из таблицы, тем самым разорвать соединение.
Потому что я честно не понимаю как вы "просто закрываете порт." <_<
Можете привести кусочек кода?

Автор: shm 24.04.16, 11:14
Цитата ЫукпШ @
значит всё придётся мастерить как-то иначе

На пользовательском уровне ничего менять не надо. Внутри буст сам подключит нужные библиотеки для нужной ОС.

Автор: Oleg2004 24.04.16, 11:59
Цитата ЫукпШ @
Как я понял, это и есть проблема данного обсуждения - сокет закрыт, а его хэндл остался где-то в списке порта. И приходят запросы на обслуживание уже давно закрытого сокета.

Приходится работать телепатом, потому что progman выдает не секреты своего приложения, которое обслуживает 17000 пользователей???.
Я квалифицирую проблему несколько иначе.
Как написал ТС, у приложения в потоке 17 вдруг возникло желание убить какого-то активного - т.е. обменивающегося запросами пользователя.
Раз очередь запросов I/O в порте завершения огромна - не зря ее обслуживают 16 рабочих потоков, после убиения клиента и его сокета в очереди порта завершения находятся еще не обслуженные запросы от этого пользователя. Как пишет сам ТС,
Цитата progman @
В данный момент этот 1234 может из порта что то читать или что то писать или вообще заниматься обработкой своих внутренних данных

далее он же пишет
Цитата progman @
При этом после закрытия сокета и удаления объекта 1234 в рабочем потоке может произойти вызов GetQueuedCompletionStatus а может и не произойти - я закономерность не понимаю. Она по разному себя ведет.

На самом деле она ведет себя везде и всегда одинаково.
Есть только два варианта.
1. Сокет закрыли - случайно удачно - тогда, когда от пользователя ничего не было. В очереди пакетов завершения от этого пользователя нет ничего. Тогда все проходит Ок, сокет убивается и больше от этого пользователя мы в очереди ничего не увидим.
2. Сокет убили - но в очереди пакетов завершения - совершенно в непонятном месте длиннейшей очереди - остался хоть один пакет завершения для этого сокета. И это моментально создает проблему. Пакет завершения к примеру WSARecv() для этого клиента в очереди еще есть, и когда-то какой-то рабочий поток выполнит вызов GetQueuedCompletionStatus - для этого пакета уже убитого сокета.
И как пишет progman,
Цитата
GetQueuedCompletionStatus выдаст в поле *lpOverlapped только что удаленный объект. "только что" понятие тут растяжимое может вызваться через миллисекунду а может через 3 секунды ( у меня рекорд 5 секунд был ).

Именно поэтому время проявления проблемы совершенно случайно - до 5 секунд.
Как я понимаю, progman эту проблему в общем то понял, и пытался ее как-то решить:
Цитата progman @
В потоке который обрабатывает очередь объектов на удаление я у каждого объекта уточняю статус с помощью макроса HasOverlappedIoCompleted и удаляю только те у кого нет в обработке операций
, он пытается понять, есть ли в очереди пакеты для убитого клиента.
Но почему-то это у него не получилось.
К примеру и поэтому может не произойти - HasOverlappedIoCompleted вызывается один раз наверно, а в очереди к примеру болтаются в разных местах 2 пакета...
В общем, пока архитектура приложения и даже его предназначение нам неизвестно, помочь ТС-у будет практически невозможно, пишем 5-ю страницу уже и все мимо.
Хотя мне в общем то проблема понятна - убивать живого клиента можно лишь убедившись что это возможно.
PS
И, насколько я знаю, закрыть порт, на котором работает сервер - это просто его выключить. Потому как порт однозначно определяет приложение. О каком порте идет речь у ТС?

Автор: progman 25.04.16, 02:50
Цитата ЫукпШ @
А он (Буст) точно использует порт завершения ?
Насколько я понимаю, это кросс-платформенная библиотека.

под виндой 146% - я под отладчиком видел внутри вызовы виндовых функций.

Цитата ЫукпШ @
Как я понял, это и есть проблема данного обсуждения - сокет закрыт,
а его хэндл остался где-то в списке порта. И приходят запросы на обслуживание
уже давно закрытого сокета.

ага. Это и было непонятным местом и из-за этого я свалил на boost.asio

Цитата Oleg2004 @
Это каким таким образом вы закрываете порт? Чей порт?
Порт приложения?

некорректно выразился. В потоке 17 я просто вызываю closesocket для сокетов двух клиентов которые внезапно залогинились род одним и тем же логином.
Или пришел сигнал от модератора кикнуть из онлайна определенную группу аккаунтов.

Добавлено
Oleg2004 в #64 вы все правильно описали
Цитата Oleg2004 @
Хотя мне в общем то проблема понятна - убивать живого клиента можно лишь убедившись что это возможно.

к своему стыду мне не удалось придумать надежного механизма как это обеспечить. В итоге и стал шаманить с сначала 30 секундной задержкой перед убиванием сокета а потом и с пятиминутной.
Ну и в итоге как вы знаете уже махнул рукой и за пару часов переписал на boost.asio получилось. 168 часов без сбоев сейчас сервак висит.

Автор: Oleg2004 25.04.16, 08:05
Цитата progman @
168 часов без сбоев сейчас сервак висит.

Во блин...невезуха. :(
Но разрулить это на бусте будет посложнее.

Автор: progman 25.04.16, 09:10
Цитата Oleg2004 @
Во блин...невезуха.

а я думаю наобород - это же прекрасно что больше недели ни одного сбоя )))))

Автор: Oleg2004 25.04.16, 13:06
Не понял. :wacko:
Вы же написали, что:
Цитата progman @
сейчас сервак висит.

Завис, повис - т.е. не пашет :blink:
Наверно опять неточно выразились :D
Ну а по теме - проблема решается достаточно просто.
Для того сокета, который надо закрыть, сначала выполняется shutdown на прием, таким образом от кривых клиентов ничего больше приниматься не будет.
И если в системе вдруг еще остались пакеты завершения для операции WSASend() по этому сокету, то после небольшого таймаута можно делать shutdown на передачу и затем - если надо, еще махонький таймаут - и closesocket.
Конечно же было эффективнее отловить точно момент обработки последнего WSASend() по этому сокету - и потом сразу делать closesocket.
Я бы делал как-то так примерно... :D

Автор: progman 26.04.16, 04:20
Цитата Oleg2004 @
Завис, повис - т.е. не пашет

висит работает. проблем нет )
сорри за кривой русский. :crazy:

Цитата Oleg2004 @
Для того сокета, который надо закрыть, сначала выполняется shutdown на прием, таким образом от кривых клиентов ничего больше приниматься не будет.

я делал. Вы мне пару страниц назад это посоветовали.
только я и на прием и на отправку сразу шотдаунил.
потом таймаут 5 минут и удалял
и вот после шотдауна 5 минут раз в день не хватало.....

Автор: Oleg2004 26.04.16, 07:10
Цитата progman @
и вот после шотдауна 5 минут раз в день не хватало.....

Ничего себе...это сколько же надо пакетов иметь в очереди порта, чтобы 16 рабочих потоков не могли разрулить пару пакетов за 5 минут :wall:
Не могу себе представить...где-то еще какая-то клизма... :(

Автор: progman 26.04.16, 08:10
Цитата Oleg2004 @
Цитата progman @
и вот после шотдауна 5 минут раз в день не хватало.....

Ничего себе...это сколько же надо пакетов иметь в очереди порта, чтобы 16 рабочих потоков не могли разрулить пару пакетов за 5 минут :wall:
Не могу себе представить...где-то еще какая-то клизма... :(

не знаю.
По счетчикам пакетов у меня больше 10 тысяч входящих пакетов в минуту не было.
В среднем 7-8 тысяч входящих в минуту. При среднем времени на обработку одного пакета в 7-10мс.
Собственно это ниачем для IOCP

поэтому я не знаю wtf

Автор: ЫукпШ 26.04.16, 08:28
Цитата shm @
Цитата ЫукпШ @
значит всё придётся мастерить как-то иначе

На пользовательском уровне ничего менять не надо. Внутри буст сам подключит нужные библиотеки для нужной ОС.

В этом сомнений нет.
Это разработчикам Буста придётся решать вопрос иначе, те фактически
на сокетах беркли. А раз такое возможно, зачем нужен порт завершения ?
---
Делаем приблизительно так:
1. Первый поток принимает входящие соединения.
Принимает решение о создании объекта-клиента и создаёт его.
Заносит указатель в оболочку из потоко-безопасного шаред-пойнтера.
2. Второй поток - диспетчер. Выполняет (частично) функции порта завершения.
Экземпляр клиента размещается в его списке.
3. N объектов-рабочих потоков созданы заранее. Рабочий поток ожидает семафора функцией ожидания.
Имеется очередь (потокобезопасная) шаред-пойнтеров на клиенты.
Максимальное значение счётчика семафора равно размеру очереди запросов
на обслуживание.
4. Диспетчер определяет статусы клиентов функцией select.
Если имеется активность у конкретного клиента, клиент передаётся потоку.
В очередь. С увеличением счётчика семафора.
Поток выбираем из массива потоков по минимально загруженной очереди.
5. Рабочий поток просыпается семафором, выбирает клиента (тоже в шаред-пойнтер),
запускает ему функцию Run. После выполнения задачи уничтожает клиента и выполняет "wait".
Поскольку число ссылок на объект ещё не 0, фактически клиен ещё не будет уничтожен.
6. Если клиента надо уничтожить принудительно, сделаем запрос об этом диспетчеру.
Тут тоже должна быть очередь - очередь запросов на уничтожение.
Диспетчер уничтожит объект, синхронно со своими манипуляциями над сокетами.
Если объект ещё в очереди рабочего потока или даже в обработке,
он будет обслуживаться обычным образом. Но по окончании будет уничтожен рабочим потоком.
Тоже синхронно.
Это всё.
---
Это задача на шаред-пойнтер и очередь.
Чтобы иметь возможность использовать сервер для клиентов другого типа,
мастерим его как шаблон.

Автор: Kray74 26.04.16, 12:15
Цитата ЫукпШ @
Это разработчикам Буста придётся решать вопрос иначе, те фактически
на сокетах беркли. А раз такое возможно, зачем нужен порт завершения ?

В asio несколько внутренних реализаций. Под windows используется IOCP, под linux - epoll.

Автор: shm 26.04.16, 12:38
Цитата ЫукпШ @
Делаем приблизительно так:

Велосипед с квадратными колесами. По сути Kray74 правильно ответил.

Автор: Oleg2004 26.04.16, 15:19
Цитата progman @
В среднем 7-8 тысяч входящих в минуту.

Ни фига себе...это что же за приложение такое популярное? :lol:
Цитата progman @
В среднем 7-8 тысяч входящих в минуту.

Это то что разруливается WSARecv().
А сколько еще исходящих WSASend().
Ясно что система работает практически на грани возможностей IOCP.
На мой взгляд (со стороны :) ) нужна система с балансировкой серверов.
Хотя у вас типа уже и нет возможностей (стойка маленькая :D )

Автор: ЫукпШ 26.04.16, 17:06
Цитата shm @
Цитата ЫукпШ @
Делаем приблизительно так:

Велосипед с квадратными колесами.

Очень может быть.
Но существует случай, когда скромный велосипед лучше
модного "мерседеса".
Это когда мерседес постоянно валяется в кювете.
---
Кроме того, можно заметить, что порт завершения для этой задачи не так уж и нужен.
Поскольку он позволяет следить за разнородными объектами, а в данном
случае требуется следить только за сокетами.
Совершенно не понятно, как разорвать ассоциацию порта и сокета, а это
существенный вопрос.
(Возможно, уничтожением порта завершения и создания нового ? Не уверен.)
Зато при использовании сокетов беркли "разрыв ассоциации"
не проблема. В результате легко и просто организовать "конвейерную" обработку
клиентов. Без любых проблем в многопоточной манипуляции сокетами.
При этом сам "сервер" даже может (а, наверное, и должен) не знать,
что он "сетевой". Он просто манипулирует объектами-клиентами.

Автор: progman 27.04.16, 02:34
Цитата Oleg2004 @
Ясно что система работает практически на грани возможностей IOCP.

В искуственных тестах нагрузки она у меня и 50 тысяч в минуту держала на одном сервере.
Давайте считать: 10 мс время обработки одного пакета - значит один поток обработает за секунду 100 пакетов или 6000 за минуту.
У меня 16 потоков. В минуту будет 96000
даже если ошабаюсь на 50 процентов я то 40 тысяч в минуту держать должен

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

Автор: Pacific 27.04.16, 05:20
Цитата Oleg2004 @
Ясно что система работает практически на грани возможностей IOCP.

Когда я тестировал грани возможностей IOCP, у меня 6-ядерный сервер держал 30000 запросов в секунду. И выдержал бы больше, если бы это был простой echo-сервер.

Автор: Oleg2004 27.04.16, 16:09
Ну что ж, progman,Pacific - вы меня убедили и я даже за мелкомягкое изобретение IOCP имею немного гордости... :D молодцы, все таки что то стоящее сочинили.
Значит и надо его юзать по полной, а проблемы решать по мере поступления.
Ну а теперь по расчетам.
Цитата progman @
Давайте считать: 10 мс время обработки одного пакета - значит один поток обработает за секунду 100 пакетов или 6000 за минуту.

Этот расчет слишком на мой взгляд слишком арифметически прямолинеен.
В любой сложной системе крутятся сотни процессов и может быть тысячи потоков. Время переключения контекста потока с одного на другой в разных процессорных системах может колебаться от 20 до 300 циклов - смотрел только что в гуглях. Кроме того, надо учитывать и приоритетность и прочая. Так что процентов на 10-15 я бы ваши цифры уменьшил бы... :)
Цитата Pacific @
Когда я тестировал грани возможностей IOCP, у меня 6-ядерный сервер держал 30000 запросов

А сколько было рабочих потоков?
Ну и кстати такой вопрос - а что такое "запрос"???
Это понятие ой как растяжимое. Скажем HTTP-запрос на сервер GET вообще может содержать не более чем 256 символов - а ответ на него может длиться секундами - пересылка сотен картинок и страниц текста. Так что обработка запроса должна рассматриваться только после ответа.
Если просто принимать запросы - и не отвечать на них - тогда другое дело. Т.е. цифры зависят от характера транзакций.
Цитата Pacific @
И выдержал бы больше, если бы это был простой echo-сервер.

У эхо-сервера нет процесса обработки запроса.
Если туда Хелло и обратно Хелло - может быть.
А к примеру, в качестве запроса на эхо я шлю файлик так под мегабайт. И буду ждать ответа сервера. Тоже выдержит?
Не все так просто...
Но все равно, опыт ваш и прогмана впечатляет.
Только вот прогман сдал IOCP и променял его на буст-обертку... :'(
Кстати, где-то в сети встречал сравнение по скорости исполнения одного и того же
достаточно емкого запроса. Буст явно проигрывал...потому как библиотека, а не родное АПИ.

Автор: Pacific 28.04.16, 06:28
Цитата Oleg2004 @
Ну и кстати такой вопрос - а что такое "запрос"???

Там был свой протокол на базе TCP+SSL. От клиента приходит не больше 1 Кб, от сервера к клиенту - от 1 до 64 Кб (в среднем было 3-4 Кб). Вот это все (установка TCP соединения, SSL хэндшейк, обработка запроса и закрытие соединения) обрабатывалось по 30000 в секунду. Все это конечно тестировалось в гигабитной локалке, в реальной работе таких нагрузок не было.

Автор: Oleg2004 28.04.16, 06:50
Цитата Pacific @
Все это конечно тестировалось в гигабитной локалке, в реальной работе таких нагрузок не было.

Это реально впечатляет. Вот что IOCP творить может :D

Автор: progman 28.04.16, 09:14
Цитата Oleg2004 @
Этот расчет слишком на мой взгляд слишком арифметически прямолинеен.
В любой сложной системе крутятся сотни процессов и может быть тысячи потоков. Время переключения контекста потока с одного на другой в разных процессорных системах может колебаться от 20 до 300 циклов - смотрел только что в гуглях. Кроме того, надо учитывать и приоритетность и прочая. Так что процентов на 10-15 я бы ваши цифры уменьшил бы...

В данный момент как я уже писал у меня 16 потоков и примерно 7000 в минуту запросов обрабатывается.
Счетчик показывает что среднее время на обработку одного запроса 10мс. Причем 99% этого времени занимает запись в БД.
Загрузка CPU меньше одного процента.
Почему я не имею право экстраполировать? ))))

Автор: Oleg2004 28.04.16, 09:55
Цитата progman @
Почему я не имею право экстраполировать? ))))

Имеете...у вас же все карты на руках... :D
Я же выступаю с позиции удаленного скептика...а вы - локального прагматика. :)

Powered by Invision Power Board (https://www.invisionboard.com)
© Invision Power Services (https://www.invisionpower.com)