служба Windows c idHTTPServer, 2 вопроса

Вопросы программирования и использования среды Lazarus.

Модератор: Модераторы

jsa
постоялец
Сообщения: 295
Зарегистрирован: 28.11.2017 12:46:04

служба Windows c idHTTPServer, 2 вопроса

Сообщение jsa »

Здравствуйте.
Имеется на Lazarus написанная служба windows.
Используется многопоточный idHTTPServer работает в нескольких местах.
Везде стабильно. Но в одном месте приходят тяжелые запросы сразу пачками по 6-8 штук каждые 10 сек.
Через 2-3 часа работы служба виснет.

пока сделали bat-ник который каждые 10 сек "пингует" ее curl-ом с запросом -X HEAD и если нет ответа определяет PID процесса, прибивает службу и перезапускает.
Но это костыль, и нужно разобраться.

Навтыкал во многих местах try except и сохраняю исключение через логгер класса TCustomDaemon

Код: Выделить всё

RestService.LogMessage(FormatDateTime('YYYY-MM-DD_hh:nn:ss.zzz',NOW)+ ' Exception {тут вставляю имя функции} --- '+E.ToString );
Отловил странную ошибку.
EAccessViolation: Access violation
в процедуре создания объектов TMSSQLConnection, TSQLTransaction, TSQLQuery они создаются в каждом потоке и удаляются перед закрытием.

Обновил библиотеку dblib.dll с версии 1.0 на 1.3
А создание компонент оформил циклом из нескольких попыток, с паузой между попытками.
Повторно эта ошибка пока не появлялась.
Но хочется понять смысл, почему ограничивается доступ. Что вероятнее? проблема с многопоточностью библиотеки или MS SQL ограничивает частоту и кол-во подключений? (не встречал ни разу такую настройку).

Так же происходят зависания, которые не попадают в виндовый журнал. Сижу расставляю дальше try excep . А может есть какая то настройка у TDaemon? чтобы все критические события в работе автоматом записывались в журнал, и не было нужды расставлять везде try except?
Аватара пользователя
WAYFARER
энтузиаст
Сообщения: 565
Зарегистрирован: 09.10.2009 00:00:04
Откуда: г. Курган

Сообщение WAYFARER »

jsa писал(а):Через 2-3 часа работы служба виснет.
Известно по какой причине виснет?
jsa
постоялец
Сообщения: 295
Зарегистрирован: 28.11.2017 12:46:04

Сообщение jsa »

WAYFARER писал(а):
jsa писал(а):Через 2-3 часа работы служба виснет.
Известно по какой причине виснет?
Так я причину и пытаюсь определить втыкая обработки исключений по всему тексту.
Аватара пользователя
WAYFARER
энтузиаст
Сообщения: 565
Зарегистрирован: 09.10.2009 00:00:04
Откуда: г. Курган

Сообщение WAYFARER »

jsa писал(а):Так я причину и пытаюсь определить втыкая обработки исключений по всему тексту.
Не верно понял, извиняюсь.
Вообще с многопоточностью это больная тема, библиотека и SQL Server скорее всего ни при чем, Чаще это попытки обратиться к уже несуществующим объектам или попытке одновременного обращения разных потоков к одной и той же области памяти.
jsa писал(а):Так же происходят зависания, которые не попадают в виндовый журнал.
В журнал будут попадать ошибки только пока служба отвечает либо упала совсем. А у вас она не упала, она просто висит. И вообще есть вероятность что зависания не связаны с возникающими ошибками, может тупо данными подавиться, ресурсов не хватить. Я бы рекомендовал добавить логирование в программу и записывать в лог все, что программа делает в реальном времени, тогда можно будет примерно увидеть что происходило перед зависанием, в том числе и исключения туда писать.
jsa писал(а):пока сделали bat-ник который каждые 10 сек "пингует" ее curl-ом с запросом -X HEAD и если нет ответа определяет PID процесса, прибивает службу и перезапускает.
Но это костыль, и нужно разобраться.
У меня почти такие же костыли. У меня используется демон-папа, который отслеживает состояние в реальном времени и перезапускает по необходимости. Но в случает когда служба работает с данными это очень плохое решение...
stikriz11
постоялец
Сообщения: 136
Зарегистрирован: 04.09.2023 14:54:19

Сообщение stikriz11 »

jsa писал(а):Так я причину и пытаюсь определить втыкая обработки исключений по всему тексту.
jsa
постоялец
 
Сообщения: 254
Зарегистрирован: 28.11.2017 12:46:04
А Вы сделайте запуск как приложения. Проще будет отлаживать.

if (ParamCount > 0) and (UpCase(ParamStr(1)) = '-A') then
begin
Forms.Application.Title:='Запустились аплиухой';
Forms.RequireDerivedFormResource:=true;
Forms.Application.Scaled:=True;
Forms.Application.Initialize;
Forms.Application.CreateForm(TfПростоФорма, которая при открытии создаст и запустит демон, fформааплиухи);
Forms.Application.Run;
end
else
begin
DaemonApp.Application.Initialize;
Application.Run;
end;
Это в модуле проекта
jsa
постоялец
Сообщения: 295
Зарегистрирован: 28.11.2017 12:46:04

Сообщение jsa »

stikriz11 писал(а): А Вы сделайте запуск как приложения. Проще будет отлаживать.
Идею не понял.
А именно как это работает и что это облегчает.
stikriz11
постоялец
Сообщения: 136
Зарегистрирован: 04.09.2023 14:54:19

Сообщение stikriz11 »

jsa писал(а):Идею не понял.
Любой демон можно запустить как обычное приложение. TDaemon - это потомок от TDataModule.
Можно параметром передать как именно мы запускаем. Я использую параметры запуска - Параметр командной строки, допустим, -А
При запуске в модуле проекта мы проверяем, есть ли такой параметр и если да, то запускаем как приложение.
Есть два модуля: DaemonApp - там демон и все, чтобы запускать демон, и Forms - там все, чтобы запускать приложение с формами.
Если параметр есть, то мы не обращаемся к модулю DaemonApp , а обращаемся к Forms. В нем мы инициализируем приложение и создаем форму, которая будет просто видна. При создании этой формы, мы создаем демон - это просто потомок от датамодуля. И вызываем ему Start. Все будет работать так же точно, как и демон. Но, Вы сможете обычным способом отлаживать приложение и ловить ошибки. Идея в том, чтобы отлаживать как приложение, а потом уже запускать как демон, когда все отдажено
public
function Start: Boolean; override;
function Stop: Boolean; override;
jsa
постоялец
Сообщения: 295
Зарегистрирован: 28.11.2017 12:46:04

Сообщение jsa »

WAYFARER писал(а): Известно по какой причине виснет?
Отловил два исключения связанные с MS SQL сервером. И написал их обработку.

Еще одно исключение возникает при сборе параметров входящего url запроса в динамическую таблицу

Код: Выделить всё

глобально

type    Tprm1 = record name, value: string; end;//тип для создания массива параметров входящего запроса      

в многопоточной процедуре 

var prms:array of Tprm1;

begin

try
// кол-во параметров берется тут 
prms_cnt:=ARequestInfo.Params.Count; 
rJdataStr := ARequestInfo.UnparsedParams; 

// массив динамический, устанавливается размер
SetLength(prms,prms_cnt);
except 
... //исключение ( EAccessViolation: Access violation ) возникает именно в этом блоке  
end;

// далее массив наполняется параметрами

end;
Тут ощущение, что проблема с переменной динамической таблицы в потоке.


Возможно есть еще одно исключение которое еще не обернул в try...except

Добавлено спустя 4 минуты 18 секунд:
stikriz11 писал(а):Но, Вы сможете обычным способом отлаживать приложение и ловить ошибки. Идея в том, чтобы отлаживать как приложение, а потом уже запускать как демон, когда все отдажено
Метод не годится.
Проблемы проявляются на рабочем сервере под реальными нагрузками и только в одном месте.
Заниматься подобной отладкой на сервере нет возможности.
сейчас батник "пингует" службу и тут же перезапускает ее при зависании.
А у меня есть возможность только заменять exe-шник и анализировать логи программы и виндовые логи с новыми точками записи.
xchgeaxeax
постоялец
Сообщения: 207
Зарегистрирован: 11.05.2023 02:51:40

Сообщение xchgeaxeax »

Попробуйте строку параметров копировать, а не присваивать:

Код: Выделить всё

if Assigned(PChar(ARequestInfo.UnparsedParams))
  then rJdataStr := copy(ARequestInfo.UnparsedParams, 1, Length(ARequestInfo.UnparsedParams))
  else rJdataStr := default(srting);
Возможно строка пропадает до её разбора или вовсе отсутствует. Отсюда и AccessViolation. Еще, возможно, из-за мусора в динамическом массиве prms. Установите его в nil перед

Код: Выделить всё

prms := nil;
SetLength(prms, prms_cnt);
Аватара пользователя
WAYFARER
энтузиаст
Сообщения: 565
Зарегистрирован: 09.10.2009 00:00:04
Откуда: г. Курган

Сообщение WAYFARER »

xchgeaxeax писал(а):Еще, возможно, из-за мусора в динамическом массиве prms. Установите его в nil перед
Так а откуда там мусор возьмется, если AV происходит до начала работы с массивом, а переменная prms локальная?
И тут вообще непонятно, AV до выделения памяти для prms или после? Тут вообще по куску кода без контекста мало что понятно. jsa, может где то процедура где то лезет в основной поток без синхронизации?
xchgeaxeax
постоялец
Сообщения: 207
Зарегистрирован: 11.05.2023 02:51:40

Сообщение xchgeaxeax »

WAYFARER писал(а):Так а откуда там мусор возьмется, если AV происходит до начала работы с массивом, а переменная prms локальная?
И тут вообще непонятно, AV до выделения памяти для prms или после? Тут вообще по куску кода без контекста мало что понятно. jsa, может где то процедура где то лезет в основной поток без синхронизации?
Согласен. Вопросов тут много. Но, как показывает опыт, в таких простых местах ошибки AV исправляются присвоением начальных значений для переменных до вызовов выделяющих память. Так же неизвестно как он формирует и использует объект ARequestInfo. Поэтому стоит не создавать ссылку на его строку, а копировать данные в новый буфер для текущего потока. А в целом должен помочь вывод в лог значений переменных в блоке вызвавшем AV (для указателей - значений указателей, а не данных по ним).
stikriz11
постоялец
Сообщения: 136
Зарегистрирован: 04.09.2023 14:54:19

Сообщение stikriz11 »

Хорошо, если ошибка в работе с памятью там, где она вылазиет, а не в произвольно любом другом месте. Heaptrcon не пробовали? Ах, да. У Вас же нет возможности отладки на сервере...
jsa
постоялец
Сообщения: 295
Зарегистрирован: 28.11.2017 12:46:04

Сообщение jsa »

xchgeaxeax писал(а):опробуйте строку параметров копировать, а не присваивать:
Постепенно сужая проблемные места и наблюдая логи определил

1. Проблема не в копировании

Код: Выделить всё

rJdataStr := ARequestInfo.UnparsedParams; 

в самое начало процедуры вынес перенос в свои переменные значения всего используемого из ARequestInfo
Ошибка точно в функции

Код: Выделить всё

rJdataStr := TIdURI.URLDecode( rJdataStr );
я ее тоже обернул в try except, регулярно выдает исключение Invalid pointer operation

2. Ошибку при создании TMSSQLConnection, TSQLTransaction, TSQLQuery исключил, проблема не в создании, а именно в подключении MSSQLConnect.Open;
попытки подключения поместил в цикл с sleep(500) и прерываем после 5 неудачных.
Пока не проявлялось.

3. Остаются загадочными несколько зависаний-перезапусков службы т.к. от них нет никаких записей в виндовый журнал.
Попробовал сделать обработку в event
IdHTTPServer1Exception
После нескольких часов выяснилось, что эта обработка ничего не сохраняет в лог.
Или я не понял как она должна работать, или просто не дождался. Но пока убрал, потому что кол-во перезапусков службы подозрительно участилось.
xchgeaxeax
постоялец
Сообщения: 207
Зарегистрирован: 11.05.2023 02:51:40

Сообщение xchgeaxeax »

jsa писал(а):я ее тоже обернул в try except, регулярно выдает исключение Invalid pointer operation
Попробуйте выводить в лог значения rJdataStr, или ARequestInfo.UnparsedParams. Скорее всего там пусто.
jsa
постоялец
Сообщения: 295
Зарегистрирован: 28.11.2017 12:46:04

Сообщение jsa »

xchgeaxeax писал(а):Попробуйте выводить в лог значения rJdataStr, или ARequestInfo.UnparsedParams. Скорее всего там пусто.
Вывод в лог rJdataStr прямо перед декодировкой поставил.
но
У меня две противоречивые на начальном этапе цели 1. Снизить кол-во перезапусков 2. разобраться с причинами.
т.к. 90% случаев это параметры без кириллицы и символов которые нужно кодировать, пришлось сделать проверку на наличие '%' и если его в строке нет то просто не использовать TIdURI.URLDecode
Поэтому колво зафиксированных ошибок резко упадет.
Ответить