Обработка ошибок

Какие ошибки перехватывает ядро и как плагинам обрабатывать ошибки в экспортируемых методах.

Plugify по-разному обрабатывает ошибки в двух местах: пока загружает и ведёт расширения и пока плагины вызывают друг друга.

Ошибки, которые обрабатывает ядро

Ядро проверяет всё, что делает с расширением: чтение манифеста, разрешение зависимостей, загрузку, экспорт, запуск, обновление и остановку. Языковой модуль сообщает о сбое, возвращая ошибку из соответствующего метода ILanguageModule; исключение, выброшенное из этих методов, перехватывается и обрабатывается так же. Расширение получает состояние Failed (или Corrupted, Unresolved, Disabled) с причиной, зависящие от него расширения получают Skipped, а остальные продолжают работать. После загрузки ядро пишет в лог отчёт с причиной по каждому незапущенному расширению.

Вызовы между плагинами

Когда плагин вызывает метод другого плагина, вызов идёт прямо в нативную функцию этого метода. Ядро между ними не стоит, благодаря этому вызовы быстрые, но это значит и то, что ядро их не проверяет. Что происходит при ошибке, зависит от языка вызванного метода:

  • Нативный код (C++, Go, Rust): падение в методе — это падение процесса. Исключение C++ не должно выходить из экспортируемой функции.
  • Языки со средой выполнения (C#, Python, JavaScript, Lua): языковой модуль перехватывает ошибку кода плагина, пишет её в лог и возвращает вызывающему значение по умолчанию (ноль, пустую строку и т. п.). Вызывающий не узнаёт, что вызов не удался.

Рекомендации для экспортируемых методов

Проверяйте аргументы

Проверяйте диапазоны, размеры и хэндлы в начале каждого экспортируемого метода. Другой плагин может передать всё, что допускает сигнатура.

Сообщайте о сбое через возвращаемое значение

Раз ошибка не доходит до вызывающего, сделайте её частью сигнатуры: возвращайте bool или код ошибки, а результаты передавайте через ref-параметры.

{
  "name": "TryParse",
  "funcName": "TryParse",
  "paramTypes": [
    { "name": "text", "type": "string" },
    { "name": "value", "type": "int32", "ref": true }
  ],
  "retType": { "type": "bool" }
}
extern "C" bool TryParse(const plg::string& text, int32_t& value) {
    auto [ptr, ec] = std::from_chars(text.data(), text.data() + text.size(), value);
    return ec == std::errc{};
}

Не выпускайте ошибки наружу

В C++ перехватывайте исключения внутри экспортируемой функции. В языках с исключениями перехватывайте то, что можете обработать, и возвращайте результат, который вызывающий может проверить.

Описывайте условия ошибок

Пишите в description метода и его параметров, что он возвращает при сбое. Генераторы биндингов переносят эти описания в код, который видят другие плагины.