Обработка обрыва соединения в IBX 2002 by Ivan Ravin (ivan_ra@chat.ru) Основная проблема, возникающая при обрыве соединения в IBX - неосвобождение хендла транзакции. То есть, если в момент обрыва связи у вас была открыта база TIBDatabase, но не было активных транзакций, то вы просто получите соответствующий Exception, который можно обработать, и продолжать выполнение. Если же была хоть одна активная транзакция, то при обрыве соединения неизбежно возникнет AV для IBX 4.42 (аплоть до 4.62 ?). В последней версии библиотеки была предпринята попытка решить проблему, но стало только хуже - при обрыве связи происходит крах программы в результате переполнения стека. Ниже предлагается решение, решающее почти все возникающие проблемы: 1. Исправляем IBDatabase.pas (отмечено комментарием ivan_ra), это общее исправление для всех библиотек - сначала добавляем в uses IBErrorCodes ("младшие" версии библиотеки): ... implementation uses IBIntf, IBSQLMonitor, IBCustomDataSet, IBDatabaseInfo, IBSQL, IBUtils, typInfo, IBErrorCodes; // ivan_ra ... a) помещаем в try..except..end вызов внутри EndTransaction: procedure TIBTransaction.EndTransaction(Action: TTransactionAction; Force: Boolean); var status: ISC_STATUS; i: Integer; begin CheckInTransaction; case Action of TARollback, TACommit: begin if (HandleIsShared) and (Action <> FDefaultAction) and (not Force) then IBError(ibxeCantEndSharedTransaction, [nil]); for i := 0 to FSQLObjects.Count - 1 do try // ivan_ra begin if FSQLObjects[i] <> nil then SQLObjects[i].DoBeforeTransactionEnd; except if not Force then raise; end; // ivan_ra end ... б) в младшие версии добавляем код из 4.63: function TIBTransaction.Call(ErrCode: ISC_STATUS; RaiseError: Boolean): ISC_STATUS; var i: Integer; begin result := ErrCode; for i := 0 to FDatabases.Count - 1 do if FDatabases[i] <> nil then Databases[i].FCanTimeout := False; FCanTimeout := False; {Handle when the Error is due to a Database disconnect. Pass it on to FDatabase so it can handle this} if CheckStatusVector([isc_lost_db_connection]) then // ivan_ra FDefaultDatabase.Call(ErrCode, RaiseError) // ivan_ra else if RaiseError and (result > 0) then IBDataBaseError; end; Немного больше придется повозиться с TIBDataBase (в 4.63 принимаем меры от зацикливания, а в предыдущие версии также добавляем код, появившийся в 4.63) а) Добавляем флаг, предотвращающий зацикливание (во всех версиях библиотеки): TIBDataBase = class(TCustomConnection) private ... FAlreadyForce:boolean; // ivan_ra ... б) не забываем о его инициализации: constructor TIBDatabase.Create(AOwner: TComponent); begin ... FAlreadyForce:=false; // ivan_ra end; в) правим место, где происходило зацикливание: procedure TIBDatabase.InternalClose(Force: Boolean); var i: Integer; FConnectionLost:boolean; // ivan_ra begin CheckActive; if FAlreadyForce then Exit; // ivan_ra FAlreadyForce:=Force; // ivan_ra FLostConnection:=CheckStatusVector([isc_lost_db_connection]); // ivan_ra ... FAlreadyForce:=false; // ivan_ra if FLostConnection then // все-таки надо что-то сообщить пользователю raise Exception.Create('Connection lost!'); // ivan_ra end; г) в младшие версии библиотеки добавляем код, появившийся в 4.63 function TIBDatabase.Call(ErrCode: ISC_STATUS; RaiseError: Boolean): ISC_STATUS; begin result := ErrCode; FCanTimeout := False; {Handle when the Error is due to a Database disconnect. Call the OnConnectionLost if it exists.} if CheckStatusVector([isc_lost_db_connection]) then // ivan_ra ForceClose; // ivan_ra if (RaiseError and (ErrCode > 0)) then IBDataBaseError; end; 2. Исправляем IBSQL.pas в младшие версии добавляем код из 4.63: procedure TIBSQL.Close; var isc_res: ISC_STATUS; begin try if (FHandle <> nil) and (SQLType = SQLSelect) and FOpen then begin isc_res := Call( isc_dsql_free_statement(StatusVector, @FHandle, DSQL_close), False); if (StatusVector^ = 1) and (isc_res > 0) and not CheckStatusVector( [isc_bad_stmt_handle, isc_dsql_cursor_close_err]) then // ivan_ra begin if isc_res = isc_lost_db_connection then FBase.Database.Call(isc_res, true) else // ivan_ra end IBDatabaseError; end; finally FEOF := False; FBOF := False; FOpen := False; FRecordCount := 0; end; end; -------------------------------------------------------------------------- Теперь, при обрыве связи TDatabase будет автоматически отключаться, хендлы всех транзакций будут очищены, датасеты нормально закрыты. Можно пытаться восстановить соединение, либо спокойно закрывать программу. К сожалению, в конце процедуры TIBDatabase.InternalClose уже не будет работать IBDatabaseError. Я поместил туда простой эксепшн: if FLostConnection then // все-таки надо что-то сообщить raise Exception.Create('Connection lost!'); // ivan_ra Правильней будет форматировать его как это делается в IBDatabaseError, но в принципе, юзеру и так должно быть понятно: программу надо завершать или подключаться по новой Отдельная история - когда идет работа с dll и происходит передача хендла базы и транзакции между библиотеками и хостом. В этом случае при потере соединения хендлы будут освобождены только в библиотеке/хосте где это обнаружено. Позаботьтесь о передаче события о потере соединения во все подключенные модули!