ADODataSet.Open обходит try catch с исключением `ArgumentOutOfRange`, зависает приложение - Delphi 10.2 - PullRequest
4 голосов
/ 08 мая 2020

Я поддерживаю приложение, которое работает как служба в серверной среде. Он многопоточный, где каждый поток работает в соответствии с очередью задач. Эта очередь задач представляет собой просто список строк с «типами заданий» в качестве их значений. Таким образом, хотя может выполняться несколько потоков, каждый поток будет отдельным заданием, и каждый поток внутренне выполняет только одну задачу за раз. TADODataSet. Иногда, не всегда и без видимого шаблона, Data.Win.ADODB выдает EArgumentOutOfRangeException, минуя мою собственную попытку поймать любые исключения. Это исключение вешает весь поток и предотвращает его выполнение в будущем до тех пор, пока я полностью не перезапущу службу. время и изо всех сил пытались найти ответы. У меня вопрос: почему это происходит и как это остановить, поймать или исправить?

Вот фрагмент моего некорректного кода. Это метод, из которого исходит трассировка стека. Он вызывается из другого метода в том же модуле, где я открываю другой набор данных l oop через его записи, и для каждой записи вызываю эту функцию, чтобы получить некоторую информацию на основе переданного значения.

function TFQFoo.DoSomething(IncNo : Int64): string;
var
  ItemList : string;
  MySQL: string;
  ComponentString: string;
begin
  result:='';
  if IncNo<=0 then
    Exit;
  ItemList := '';
  MyQuery.Close;
  MySQL := 'select ID from tbl ' +
    ' where val = ' + IntToStr(IncNo) +
    ' order by col1 DESC, col2, col3';

  try
    try
      MyQuery.CommandText := (MySQL);
      MyQuery.Open;

      while not (MyQuery.EOF) do
      begin
        if (ItemList <> '') then
          ItemList := ItemList + ',';
        ItemList := ItemList +
          MyQuery.FieldbyName('ID').asstring;
        MyQuery.Next;
      end;
    except
      // exception handling code omitted for brevity -- none of it
      // is ever reached, anyway (see below)
    end;
  finally
    MyQuery.Close;
  end;
  Result := ItemList;
end;

Стек вызовов из исключения указывает, что это происходит в Open. Нет блока try..catch, который захватит исключение и зарегистрирует его для меня - я должен использовать EurekaLog, чтобы увидеть какие-либо подробности. Методы трассировки стека (слишком большие, чтобы публиковать здесь) выглядят так:

  1. TFQFoo.DoSomething
  2. TDataSet.Open
  3. ... внутренние вещи
  4. TADOConnection.ExecuteComplete
  5. CheckForAsyncExecute
  6. ...
  7. TCustomConnection.GetDataSet
  8. TListHelper.GetItemRange

Thinking мой компонент TADODataSet каким-то образом повредился / его свойства изменились во время выполнения, я добавил некоторые записи для сбора этих данных для меня, чтобы я мог видеть, не происходит ли там что-то странное. Я ничего не видел, но вот это на случай, если это уместно.

object MyQuery: TADODataSet
  AutoCalcFields = False
  CacheSize = 15
  Connection = FGlobals.RIMSDB
  CursorType = ctStatic
  LockType = ltReadOnly
  CommandText = 
    'select ID from tbl  where val = 202005070074 order by col1 ' +
    'DESC, col2, col3'
  ParamCheck = False
  Parameters = <>
  Left = 32
  Top = 216
end

Для любопытных, это метод, который фактически генерирует исключение из Data.Win.ADODB. Обратите внимание на except, которое, я думаю, перескакивает через мой собственный блок try..catch и отправляет исключение прямо в EurekaLog.

procedure CheckForAsyncExecute;
var
  I: Integer;
begin
  try
    if not Assigned(pError) and Assigned(pRecordset) and
       ((pRecordset.State and adStateOpen) <> 0) then
      for I := 0 to DataSetCount - 1 do
        if (DataSets[I].Recordset = pRecordset) and (eoAsyncExecute in DataSets[I].ExecuteOptions) then
        begin
          DataSets[I].OpenCursorComplete;
          Break;
        end;
  except
    ApplicationHandleException(Self);
  end;
end;

То, что я пробовал:

  • Многие, многие итераций настройки свойств компонента в самом ADODataSet
  • Использование CommandText и параметров изнутри конструктора и назначение параметра перед выполнением во время выполнения
  • Добавление / удаление подсказки (NOLOCK) для сам запрос
  • Передал проблему старшим членам моей команды для ввода
  • Погуглил (часами)
  • Чтение Delphi и документации ADO (для этого не очень полезно )
  • Попытка воспроизведения - мне никогда не удавалось добиться этого ни на одной тестовой системе, которую я использую. Это заставляет меня думать, что это может быть связано с окружающей средой, но я совершенно не понимаю как

Мой вопрос, переформулированный:

Как мне остановить это от происходящего? Что я делаю не так? Я не хочу просто ловить EArgumentOutOfRangeException; Я хочу узнать, почему это происходит в первую очередь, и предотвратить это в будущем.

Я знаю, что иногда выполнение запроса не возвращает результатов, но типичное сообщение CommandText does not return a result set никогда не отображается и возникла из-за того, что код нижнего уровня обходит мой собственный оператор catch. Помимо этого, я не знаю, что еще искать.

До сих пор я обнаружил только одно повторение чего-то похожего, но это относится только к тому исключению, которое не было обнаружено: http://www.delphigroups.info/2/d9/410191.html

1 Ответ

2 голосов
/ 09 мая 2020

Вызов ApplicationHandleException(Self) в CheckForAsyncExecute() поглощает исключения, поэтому ваш блок except не запускается:

// in Data.Win.ADODB.pas:

procedure CheckForAsyncExecute;
var
  I: Integer;
begin
  try
    ...
  except
    ApplicationHandleException(Self); // <-- a caught exception is NOT re-raised here!
  end;
end;

Внутри модуля Data.Win.ADODB обнаруженные исключения будут вызвать собственную функцию устройства ApplicationHandleException(), которая затем вызывает System.Classes.ApplicationHandleException, если она назначена, иначе она просто завершает работу:

// in Data.Win.ADODB.pas:

procedure ApplicationHandleException(Sender: TObject);
begin
  if Assigned(System.Classes.ApplicationHandleException) then
    System.Classes.ApplicationHandleException(Sender);
end;

System.Classes.ApplicationHandleException инициализируется как nil.

В обоих для приложения VCL 1 и FMX конструктор TApplication назначает метод TApplication.HandleException() для System.Classes.ApplicationHandleException, где HandleException() игнорирует EAbort исключения и вызывает обработчик событий TApplication.OnException (если назначен ), метод TApplication.ShowException() или функция System.SyUtils.ShowException(), в зависимости от типа обрабатываемого исключения.

1: в приложении VCL TService TServiceApplication использует Vcl.Forms.TApplication внутренне. TApplication.ShowException() отображает детали исключения для пользователя во всплывающем окне MessageBox и затем закрывается, а System.SysUtils.ShowException() отображает детали исключения для пользователя в консоли или MessageBox, а затем выходит.

Итак, ADO CheckForAsyncExecute() не вызывает повторно перехваченное исключение в код пользователя. . И само собой разумеется, что отображение всплывающего окна MessageBox в службе - не лучшая идея, поскольку пользователь, скорее всего, не увидит его, чтобы закрыть его.

Конечно, лучшим вариантом было бы избегать EArgumentOutOfRangeException исключение из срабатывания в первую очередь. Но есть и другие условия, которые также могут вызывать исключения.

Итак, ваш единственный вариант самостоятельно обрабатывать проглоченные исключения ADO и избегать всплывающих окон MessageBox - это назначить обработчик событий TApplication.OnException (либо напрямую, либо через компонент TApplicationEvents).

...