Служба, rest сервер для работы с MS SQL, потоки, ошибки.

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

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

Служба, rest сервер для работы с MS SQL, потоки, ошибки.

Сообщение jsa » 12.11.2019 08:32:12

Здравствуйте.
Нуждаюсь в помощи, совете, пояснениях.

Написал примитивный rest сервер для работы Android приложения с базой на MS SQL
(предыдущая тема http://www.freepascal.ru/forum/viewtopic.php?f=5&t=42810 )

служба rest сервера работает.
Но при небольшом увеличении нагрузки, служба виснет. Один раз повисла так, что процесс не удалялся никаким средствами, а для taskkill не определялся PID процесса, пришлось перезапускать винду.
Никаких потоков специально не оформлял, вроде как TIdHTTPServer и так многопоточная компонента.
Предполагаю, что дело именно в этом.
Помогите пожалуйста, как отслеживать отслеживать ошибку/ понять причину запвисания?
Как правильно оформлять работу с файлами и с sql сервером в службе?

---
Работа строго в локалке.
сервер получает 3 get запроса и запускает скрипт с параметрами в котором используются хранимые процедура и табличная функция.
Процедура определяет девайс по параметру "мак-адрес" и записывает в таблицу лога параметры запроса.
Процедура возвращает флаг 0/1
в зависимости от которого (=0) скрипт возвращает сообщение об ошибке, или вызывает функцию, и возвращает ее результат
Код: Выделить всё
declare @hid int, @devsn varchar(20), @jsontxt varchar(max), @ident int
set @hid = isnull( :prmhid , -1)
set @devsn = isnull( :prmDeviceSN, '''' )
set @ident = 0
set @jsontxt= ''сообщение об ошибке''
exec dbo.sp_rest_hInfo_LOG @hid, @devsn, @identified=@ident output
if @ident=1 select top 1 @jsontxt=jsontxt from dbo.sp_rest_hInfo(@hid)
select ( @jsontxt ) jsontxt   

Все три скрипта (для 3х get запросов) похожи на этот, различие в именах функций и количестве дополнительных параметров.

Все процедуры и функции службы я расместил по init-ам
в unit daemon_unit_main
procedure DataModuleCreate(Sender: TObject);
procedure DataModuleShutDown(Sender: TCustomDaemon);
procedure DataModuleStart(Sender: TCustomDaemon; var OK: Boolean);
procedure DataModuleStop(Sender: TCustomDaemon; var OK: Boolean);
procedure ServerRestCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);

Вот текст основного unit
Код: Выделить всё
unit daemon_unit_main;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, IdHTTPServer, IdSocketHandle, Interfaces, ExtCtrls, DaemonApp, LazUTF8, IdCustomHTTPServer, IdContext, IdGlobal, IdCoder, IdCoderMIME;

type

  { TDM }

  TDM = class(TDaemon)
    IdHTTPServerRest: TIdHTTPServer;
    procedure DataModuleCreate(Sender: TObject);
    procedure DataModuleShutDown(Sender: TCustomDaemon);
    procedure DataModuleStart(Sender: TCustomDaemon; var OK: Boolean);
    procedure DataModuleStop(Sender: TCustomDaemon; var OK: Boolean);
    procedure ServerRestCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
  private

  public

  end;

var DM: TDM;
    prm0:string;
    IdHTTPServerRest:TIdHTTPServer;
    Binding:TIdSocketHandle;
    rest_port, IntChKey:integer;
    ver, err_msg:string;

implementation
uses myProc, readini, unit_sql, writelog, lpudata;

procedure RegisterDaemon;
begin
  RegisterDaemonClass(TDM)
end;

{$R *.lfm}

{ TDM }

{ запоминание пути запуска при старте }
procedure TDM.DataModuleCreate(Sender: TObject);
begin
     prm0:=ParamStr(0);
end;

{ Запуск службы, чтение параметров, подключение к базе }
procedure TDM.DataModuleStart(Sender: TCustomDaemon; var OK: Boolean);
var automediINI, automediVER:string;
begin
     ver:=myProc.ProgramVersion();
     optionsINI:=readini.GetIni(prm0+'options.ini');
     readini.GetParametersFromIni(optionsINI);
     writelog.CreateLogFile();

     unit_sql.MSSQLServCreate();

     IdHTTPServerRest:=TIdHTTPServer.Create;
     IdHTTPServerRest.OnCommandGet:=@ServerRestCommandGet;
     Binding := IdHTTPServerRest.Bindings.Add;
     Binding.Port := rest_port;
     IdHTTPServerRest.Active := True;
end;

{ отключение от sql сервера при остановке службы}
procedure TDM.DataModuleStop(Sender: TCustomDaemon; var OK: Boolean);
begin
     //DM.IdHTTPServerRest.Active:=false;
     //unit_sql.MSSQLServActClose();
     writelog.CloseLogFile();
end;

procedure TDM.DataModuleShutDown(Sender: TCustomDaemon);
begin
     writelog.CloseLogFile();
end;

//
//          web часть
//

procedure TDM.ServerRestCommandGet(AContext: TIdContext;  ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var rCommand, rDocument, rRemoteIP, rParams: string;
    hid, actID:integer; deviceSN, respTxt:string;
begin
     AResponseInfo.AuthRealm:='';
     AResponseInfo.ContentLanguage:='ru';
     AResponseInfo.CharSet:='UTF-8';
     AResponseInfo.ContentType:='application/json';

if unit_sql.MSSQLServActConnect() then
   begin
     rCommand  := ARequestInfo.Command;
     rDocument := StringReplace( AnsiLowerCase(ARequestInfo.Document) , '/', '', [rfReplaceAll, rfIgnoreCase]);
     rRemoteIP := ARequestInfo.RemoteIP;
     rParams := ARequestInfo.Params.Text;
     hid:=-1;
     actID:=-1;

     if lpuData.CheckKey() then --сверка ключа
        begin
             if rDocument = 'hinfo' then
                begin
                hid:= StrToInt( ARequestInfo.Params.Values['hid'] );
                deviceSN:=ARequestInfo.Params.Values['deviceSN'];
                writelog.WrLog( 'get_рInfo // hID = '+inttostr(hid)+' // deviceSN = '+deviceSN);
                respTxt:=unit_sql.GetPatInfo(hid,deviceSN); --процедура выполнения sql скрипта
                end;
             if rDocument = 'act' then
                begin
                hid:= StrToInt( ARequestInfo.Params.Values['hid'] );
                deviceSN:=ARequestInfo.Params.Values['deviceSN'];
                writelog.WrLog( 'get_Act // hID = '+inttostr(hid)+' // deviceSN = '+deviceSN);
                respTxt:=unit_sql.GetActы(hid, deviceSN);
                end;
             if rDocument = 'writeact' then
                begin
                hid:= StrToInt( ARequestInfo.Params.Values['hid'] );
                deviceSN:=ARequestInfo.Params.Values['deviceSN'];
                actID:=StrToInt( ARequestInfo.Params.Values['ActID'] );
                writelog.WrLog( 'get_WriteAct // hID = '+inttostr(hid)+' // deviceSN = '+deviceSN+' // ActID = '+inttostr(actID) );
                respTxt:=unit_sql.GetInsertAct(hid, deviceSN, actID);
                end;
             unit_sql.MSSQLServActClose();
             AResponseInfo.ResponseNo:=200;
        end
        else
        begin
              respTxt:='Обратитесь к системному администратору.';
              AResponseInfo.ResponseNo:=451;
        end;
   end
   else
       begin
             respTxt:='База не доступна, попробуйте позже.';
             AResponseInfo.ResponseNo:=404;
       end;

AResponseInfo.ContentText:= UTF8ToWinCP(respTxt);
AResponseInfo.ContentLength := length(respTxt);
end;


initialization
  RegisterDaemon;
end.


в unit writelog - функция создания текстового файла лога, и функции записи в него. Файл не держится открытым постоянно, а открывается для очередной записи процедурой Append
в unit readini - чтение параметров сервера из ini файла. Чтение осуществляется 1 раз в обработчике DataModuleStart
в unit unit_sql - находятся процедуры создания объектов TMSSQLConnection, TSQLQuery, TSQLTransaction , функция коннекта, функции выполнения 3 скриптов
в unit lpudata - функция проверки лиц.ключа прграммы, ключ хранится в базе и вынимается запросом через отдельный коннект специальных экземпляров объектов TMSSQLConnection, TSQLQuery
jsa
постоялец
 
Сообщения: 261
Зарегистрирован: 28.11.2017 13:46:04

Re: Служба, rest сервер для работы с MS SQL, потоки, ошибки.

Сообщение Ichthyander » 12.11.2019 11:57:41

Глючный этот Indy. ТОже вис, правда это был клиент, а не сервер, причем таймауты не помогали. Может стабильней будет работать через fcl-web, а не Indy?
Аватара пользователя
Ichthyander
энтузиаст
 
Сообщения: 668
Зарегистрирован: 04.04.2007 08:32:43
Откуда: Астрахань

Re: Служба, rest сервер для работы с MS SQL, потоки, ошибки.

Сообщение jsa » 12.11.2019 13:21:55

Глючный этот Indy.

Думаю тут дело все таки в моих ошибках.
переписал так что
1. Все функции работы с sql сервером остались в отдельном unit
2. Но вызов создания и удаления компонент Connection, Query, Transaction поместил в процедуру обработки IdHTTPServerRest.OnCommandGet
Стало в разы стабильнее, но зависания все таки были.

Может стабильней будет работать через fcl-web, а не Indy?

Знать бы наверняка.
У меня есть пример сервера (но он не как служба сделан) где на Indy все работает. Там правда вообще всё в одной процедуре OnCommandGet находится (жесть какая-то)
jsa
постоялец
 
Сообщения: 261
Зарегистрирован: 28.11.2017 13:46:04

Re: Служба, rest сервер для работы с MS SQL, потоки, ошибки.

Сообщение Sharfik » 15.11.2019 03:11:19

Логи!
Сам сейчас мучаюсь с запиливанием сервера для работы с БД. Вообще не моя тема, но надо. Первое что сделал это сохранение логов на каждом чихе, чтобы если все получится не ломать голову как мне удалось подвесить сервак админов. В твоем случае тоже надо лепить логи, а потом их смотреть - при какой команде завис.
Аватара пользователя
Sharfik
энтузиаст
 
Сообщения: 760
Зарегистрирован: 20.07.2013 01:04:30

Re: Служба, rest сервер для работы с MS SQL, потоки, ошибки.

Сообщение olegy123 » 15.11.2019 06:23:09

jsa писал(а):У меня есть пример сервера (но он не как служба сделан) где на Indy все работает. Там правда вообще всё в одной процедуре OnCommandGet находится (жесть какая-то)
9-ая, от 10ой отличается многопоточностью.
в 9ой, вроде события за синхронизированные, то есть выполняются однопоточно.

лайхак:
в TIdThread есть Data (TIdThread.Data)
к нему можно зацепить любой TObject, и он будет путешествовать по всем событиям..
Очень удобно когда вам надо связать подключение с некой сущностью..
http://ww2.indyproject.org/docsite/html ... index.html

Добавлено спустя 1 минуту 43 секунды:
разумеется он будет многопоточным, так как выполняется в теле TIdThread
olegy123
долгожитель
 
Сообщения: 1643
Зарегистрирован: 25.02.2016 12:10:20

Re: Служба, rest сервер для работы с MS SQL, потоки, ошибки.

Сообщение jsa » 15.11.2019 07:04:55

Sharfik писал(а):Логи!...В твоем случае тоже надо лепить логи, а потом их смотреть - при какой команде завис.

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

Мне подсказали про критические сессии TCriticalSection
Завернул в критическую сессию процедуру записи в лог и тогда увидел, что клиентская программа посылает один и тот же запрос с разницей в 15-25 микросекунд
т.е. ошибка в клиентской программе оказалась кстати, т.к. стабильно позволяет тестировать rest сервер на многопоточность.
Метки которые расставлены по обработке get запроса, чередуются от этих двух одинаковых запросов.
При этом стабильна одна и та же картина - компоненты TMSSQLConnection, TSQLTransaction и TSQLQuery создаются. Соединение устанавливается. Текст в запрос помещается. А вот на SQLQuery1.Open виснет.
Точнее в одном потоке запрос выполняется, во втором нет.
При этом сервер выдает клиенту ответ, из одного потока, второй видимо продолжает висеть, т.е. соединение на SQL Server остается, а должно закрыться.
Следующий двойной запрос тоже может отработаться - один поток вернул ответ клиенту, второй висит кол-во соединений на на SQL Server становится +1
и так несколько раз.
Потом при очередном двойном запрос SQLQuery1.Open не выполняется и служба виснет.

Благодаря использованию критических сессий зависшую службу можно закрыть обычным taskkill и не требуется перезагрузка винды.

Пока разбираюсь с многопоточностью, сделал IdHTTPServerRest.MaxConnections:=1
Для данной конкретной задачи работы в один поток хватит на несколько лет.
Но хочу конечно разобраться. тем более есть такой стабильный генератор ошибки.

Добавлено спустя 48 минут 16 секунд:
olegy123 писал(а):к нему можно зацепить любой TObject, и он будет путешествовать по всем событиям..

Вы имеете ввиду, что в Data можно включить один раз созданные компоненты для работы с SQL сервером, и тогда они будут нормально отрабатывать в нескольких потоках?
jsa
постоялец
 
Сообщения: 261
Зарегистрирован: 28.11.2017 13:46:04

Re: Служба, rest сервер для работы с MS SQL, потоки, ошибки.

Сообщение olegy123 » 15.11.2019 14:10:01

Будет вызывается событие, например подключения, в Data можно указать некий объект,
при чтении данных с сокета, также работать с Data-объектом, при этом это будет в TIdThread потоке..
ну и при закрытии сокета нужно соответственно удалить Data-объект
все выполняется в TIdThread потоке но это в Indy10
olegy123
долгожитель
 
Сообщения: 1643
Зарегистрирован: 25.02.2016 12:10:20

Re: Служба, rest сервер для работы с MS SQL, потоки, ошибки.

Сообщение Sharfik » 16.11.2019 08:09:06

Пример работы с потоками тут есть
https://sourceforge.net/p/indy10clieser ... nt&Server/
Если сам не нашел еще, то читай про TIdThreadSafe, TIdNotify, TIdSync. Тут например http://www.delphisources.ru/pages/faq/i ... -Depth.pdf
Аватара пользователя
Sharfik
энтузиаст
 
Сообщения: 760
Зарегистрирован: 20.07.2013 01:04:30

Re: Служба, rest сервер для работы с MS SQL, потоки, ошибки.

Сообщение jsa » 19.11.2019 09:23:18

Всем спасибо, буду смотреть.
jsa
постоялец
 
Сообщения: 261
Зарегистрирован: 28.11.2017 13:46:04


Вернуться в Lazarus

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 17

Рейтинг@Mail.ru
cron