Рассмотрите такой код:
procedure ObliterateDocument;А чуть позже у вас начинает ругаться Assert в document.Destroy, говоря, что в документе всё ещё есть активные плагины. Но у нас же есть вызов document.DestroyPlugins, и он находится в блоке finally, а весь смысл блока finally в том, что вы никак не можете избежать его выполнения.
begin
try
try
document.DestroyAll;
finally
document.Close;
document.DestroyExtensions;
document.DestroyPlugins;
end;
finally
document.Destroy;
end;
end;
Так почему же document.DestroyPlugins не выполняется?
Потому что ваш обработчик исключений тоже может возбудить исключение.
Обработчик исключения не активен во время обработки своего собственного блока обработки исключения (как finally, так и except). В результате, если document.Close возбудит исключение, то поиск обработчика для выполнения начнётся с блока вне текущего блока finally.
(То, что обработчик исключения не активен во время прогона своего собственного блока обработки, должно быть очевидно. Потому что в противном случае программа вошла бы в бесконечный цикл, возникни в блоке обработки исключение. Кроме того, вы не смогли бы повторно возбудить исключение, т.к. raise был бы пойман вами же!)
В нашем случае, исключение ловится внешним кодом, что приводит к пропуску остатка finally-блока. Прочие внешние finally блоки выполняются - потому что они содержат в себе finally-блок, который умер.
(Эта ошибка также существует в предлагаемой анонимом альтернативе к коду на кодах ошибок).
Обработчик исключениЕ не активен
ОтветитьУдалитьКак раз столкнулся с этой проблемой. Какие есть варианты решения? Есть идея заключить блок finally, в котором могут возникнуть исключения, в дополнительный обработчик try..except:
ОтветитьУдалитьtry
...
finally
try
...
except
HandleFinallyException;
end
end;
Поискал в исходниках VCL, нигде подобной конструкции не видел. Разработчики видимо свято верят, что код в их блоках finally никогда не возбудит исключения?
Идеологически правильное решение: любой деструктор и деструктор-like метод обязаны обрабатывать все свои исключения. Иными словами: очистка обязана быть успешной. Почему? Потому что вы не можете предложить ни одного способа восстановления после исключения в деструкторе или любом методе очистки.
ОтветитьУдалитьНаводящие вопросы: является ли объект, в котором произошло исключение в деструкторе, удалённым? Нет? Тогда, является ли он валидным? А как тогда удалять его повторно? И т.п.
Если же деструктор выпускает наружу исключение, то это исключение должно рассматриваться как фатальное (типа AV в менеджере памяти). Программа должна быть перезапущена немедленно.
Спасибо за ответ. Итак, деструкторы (и прочие методы финализации) не должны выпускать исключения наружу. Осталось только убедить в этом безответственных товарищей, которые пишут плагины к моему приложению :) Потому что обидно, когда МОЯ программа рушится из-за того, что КТО-ТО некорректно написал деструктор...
ОтветитьУдалитьP.S. Давно и с удовольствием читаю Ваши блоги и статьи на КД; отдельное спасибо за статью про обработку ошибок :)
Ну, я имел ввиду только ваш код, конечно же.
ОтветитьУдалитьКогда же речь заходит о взаимодействии со сторонним кодом, то часто приходится вставлять код, исправляющий чужие косяки и то, "что никогда не может произойти". А "убедить этих" - забудьте об этом. Это данность, которую нужно принять.
Если протокол плагинов под вашим контролем - попробуйте добавить в инфу о плагине e-mail автора и автоматом высылать ему отчёт о проблемах с его плагином. Вот поймали исключение в его функции очистки - отправили отчёт. Ну это просто как пример.
Примерно так и делаю. Только не по e-mail, а просто пишу информацию об исключении в лог. Вобщем, Вы полностью подтвердили мои соображения, убедился, что я иду правильным путем :)
ОтветитьУдалить