name: error-handling description: Обработка ошибок, транзакции и блокировки. Этот навык учит агента правильно обрабатывать ошибки, управлять транзакциями и блокировками в 1С.
Обработка ошибок, транзакции и блокировки
Назначение
Этот навык учит агента правильно обрабатывать ошибки, управлять транзакциями и блокировками в 1С. Ошибки в этих областях — самые опасные: они приводят к потере данных, зависшим блокировкам (deadlock), невозможности работы пользователей и трудновоспроизводимым багам. В отличие от ошибок производительности (тормозит, но работает), ошибки транзакций и блокировок могут полностью остановить работу предприятия.
Ключевой принцип: В 1С нет автоматического управления транзакциями (как в некоторых фреймворках). Разработчик вручную управляет началом, фиксацией и откатом транзакций. Каждая незакрытая транзакция — потенциальная катастрофа.
Источники:
- Стандарты ИТС: «Обработка исключений»
- Стандарты ИТС: «Транзакции: правила использования»
- Стандарты ИТС: «Управляемые блокировки»
- Документация платформы: «Механизм управляемых блокировок»
Сводка правил
| # | Правило | Обоснование |
|---|---|---|
| 1 | Всегда логировать в Исключение | Диагностика, аудит |
| 2 | Канонический паттерн транзакций | Предотвращение зависших транзакций |
| 3 | ТранзакцияАктивна() — защитно, не заместительно | Корректное вложение транзакций |
| 4 | Минимизация времени транзакции | Снижение конкуренции |
| 5 | БлокировкаДанных перед чтением | Предотвращение race conditions |
| 6 | ЗаблокироватьДанныеДляРедактирования | Предотвращение lost update |
| 7 | ЗаписьЖурналаРегистрации по стандарту | Структурированная диагностика |
| 8 | Понятные сообщения пользователю | UX при ошибках |
| 9 | ВызватьИсключение без/с параметром | Сохранение стека vs понятность |
| 10 | Поэлементная обработка массовых операций | Одна ошибка не блокирует все |
| 11 | Единый порядок блокировки | Предотвращение deadlock |
| 12 | Попытка для внешних вызовов | Внешние системы ненадёжны |
Правило 1: Попытка/Исключение — всегда логируйте, никогда не глотайте
Почему: Проглоченное исключение — самый опасный антипаттерн. Система выглядит работающей, но:
- Данные не записаны / не обработаны
- Пользователь не знает об ошибке
- В журнале регистрации нет следов
- Отладка невозможна — воспроизвести проблему невозможно без логов
Стандарт ИТС: «В обработчике исключения обязательно фиксировать информацию об ошибке в журнале регистрации».
Канонический паттерн обработки исключений
Попытка
// Опасная операция
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
// 1. Получаем полную информацию об ошибке
ИнформацияОбОшибке = ИнформацияОбОшибке();
// 2. Логируем в журнал регистрации
ЗаписьЖурналаРегистрации(
НСтр("ru = 'Проведение документа'"), // Имя события
УровеньЖурналаРегистрации.Ошибка, // Уровень
ДокументОбъект.Метаданные(), // Метаданные объекта
ДокументОбъект.Ссылка, // Ссылка на объект
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке)); // Полный стек ошибки
// 3. Решаем: пробросить или обработать
ВызватьИсключение; // Пробрасываем — пусть вызывающий код обработает
КонецПопытки;
Два представления ошибки
| Функция | Когда использовать | Что содержит |
|---|---|---|
КраткоеПредставлениеОшибки() |
Для отображения пользователю | Понятный текст без технических деталей |
ПодробноеПредставлениеОшибки() |
Для журнала регистрации | Полный стек вызовов, номера строк, вложенные ошибки |
Пример: разные уровни обработки
// Нижний уровень — логирование + проброс
Функция ЗаписатьДокумент(ДокументОбъект)
Попытка
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Возврат Истина;
Исключение
ЗаписьЖурналаРегистрации(
НСтр("ru = 'Проведение документа'"),
УровеньЖурналаРегистрации.Ошибка,
ДокументОбъект.Метаданные(),
ДокументОбъект.Ссылка,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
КонецФункции
// Верхний уровень (обработчик формы) — показ пользователю
&НаКлиенте
Процедура ЗаписатьДокумент(Команда)
Попытка
ЗаписатьДокументНаСервере();
Исключение
// Пользователю — краткое и понятное сообщение
ПоказатьПредупреждение(,
НСтр("ru = 'Не удалось записать документ. Обратитесь к администратору.'"));
КонецПопытки;
КонецПроцедуры
Правило 2: Канонический паттерн транзакций
Почему: Транзакция в 1С должна быть:
- Атомарной — либо все операции выполнены, либо ни одна
-
Обязательно закрыта —
ЗафиксироватьТранзакцию()илиОтменитьТранзакцию() - Минимальной по длительности — чем дольше транзакция, тем дольше блокируются данные
Незакрытая транзакция блокирует записи в СУБД. Другие пользователи, обращающиеся к тем же данным, будут ждать (таймаут обычно 20 сек) и получат ошибку «Превышено время ожидания блокировки».
Стандарт ИТС: «Транзакции: правила использования» — НачатьТранзакцию() ВСЕГДА должна быть непосредственно перед Попытка.
Канонический паттерн (ОБЯЗАТЕЛЬНЫЙ)
НачатьТранзакцию();
Попытка
// === Блок операций внутри транзакции ===
// 1. Блокировка данных (если нужно — см. правило 5)
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Документ.РеализацияТоваровУслуг");
ЭлементБлокировки.УстановитьЗначение("Ссылка", ДокументСсылка);
Блокировка.Заблокировать();
// 2. Чтение и модификация данных
ДокументОбъект = ДокументСсылка.ПолучитьОбъект();
ДокументОбъект.Статус = Перечисления.СтатусыДокументов.Согласован;
// 3. Запись
ДокументОбъект.Записать();
// === Фиксация — ПОСЛЕДНЯЯ операция перед Исключение ===
ЗафиксироватьТранзакцию();
Исключение
// === Откат — ПЕРВАЯ операция в блоке Исключение ===
ОтменитьТранзакцию();
// Логирование ПОСЛЕ отката (запись в ЖР тоже может быть в транзакции!)
ЗаписьЖурналаРегистрации(
НСтр("ru = 'Согласование документа'"),
УровеньЖурналаРегистрации.Ошибка,
Метаданные.Документы.РеализацияТоваровУслуг,
ДокументСсылка,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
Критические правила порядка
| Требование | Почему |
|---|---|
НачатьТранзакцию() непосредственно перед Попытка |
Если между ними ошибка — транзакция не закроется |
ЗафиксироватьТранзакцию() — последний оператор перед Исключение |
Любая операция после фиксации, но до конца Попытка, при ошибке не откатится |
ОтменитьТранзакцию() — первый оператор в Исключение |
Логирование тоже может вызвать ошибку; если откат ещё не сделан — каскадная проблема |
ЗаписьЖурналаРегистрации() — ПОСЛЕ ОтменитьТранзакцию() |
Запись в ЖР внутри отменённой транзакции будет потеряна |
Неправильные варианты
// ❌ ПЛОХО: НачатьТранзакцию до Попытка с промежуточным кодом
НачатьТранзакцию();
ПодготовитьДанные(); // Если здесь ошибка — транзакция зависнет!
Попытка
// ...
КонецПопытки;
// ❌ ПЛОХО: ЗаписьЖурнала ДО ОтменитьТранзакцию
Исключение
ЗаписьЖурналаРегистрации(...); // Может быть потеряна при откате!
ОтменитьТранзакцию();
КонецПопытки;
// ❌ ПЛОХО: код после ЗафиксироватьТранзакцию, но до конца Попытка
ЗафиксироватьТранзакцию();
ОтправитьОповещение(); // Если ошибка здесь — транзакция уже зафиксирована, но блок Исключение выполнится!
Исключение
ОтменитьТранзакцию(); // Ошибка! Транзакция уже зафиксирована!
КонецПопытки;
Правило 3: Вложенные транзакции — проверяйте ТранзакцияАктивна()
Почему: В 1С транзакции счётчиковые: вложенный НачатьТранзакцию() не создаёт новую транзакцию, а увеличивает счётчик. ОтменитьТранзакцию() помечает транзакцию как «отменённая», и любой последующий ЗафиксироватьТранзакцию() (даже на внешнем уровне) вызовет исключение.
Это значит: если вложенная процедура откатила транзакцию, а внешняя процедура не знает об этом и пытается зафиксировать — ошибка.
Правильно — проверка перед операциями с транзакцией
// Процедура, которая может вызываться как самостоятельно, так и внутри внешней транзакции
Процедура ЗаписатьДанные(ДанныеДляЗаписи)
НачатьТранзакцию();
Попытка
// ... операции записи ...
ДокументОбъект.Записать();
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации(
НСтр("ru = 'Запись данных'"),
УровеньЖурналаРегистрации.Ошибка,,,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение; // ОБЯЗАТЕЛЬНО пробрасываем — внешний код должен знать
КонецПопытки;
КонецПроцедуры
// Внешний код, вызывающий процедуру в своей транзакции
Процедура ОбработатьПакетДокументов(МассивДокументов)
НачатьТранзакцию();
Попытка
Для Каждого ДанныеДокумента Из МассивДокументов Цикл
ЗаписатьДанные(ДанныеДокумента); // Вложенная транзакция
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию(); // Откатит ВСЕ записи, включая вложенные
ЗаписьЖурналаРегистрации(...);
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
Проверка ТранзакцияАктивна() для защитного программирования
// Защитная проверка — полезна в коде, который не знает, был ли откат
Если ТранзакцияАктивна() Тогда
ОтменитьТранзакцию();
КонецЕсли;
Правило: НЕ используйте ТранзакцияАктивна() как замену правильному паттерну
// ❌ ПЛОХО: ТранзакцияАктивна() как «защита» от дублирования отката
Попытка
НачатьТранзакцию();
// ...
ЗафиксироватьТранзакцию();
Исключение
Если ТранзакцияАктивна() Тогда // Маскирует ошибку в структуре кода!
ОтменитьТранзакцию();
КонецЕсли;
КонецПопытки;
// ✅ ПРАВИЛЬНО: корректная структура делает проверку ненужной
НачатьТранзакцию();
Попытка
// ...
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию(); // Всегда вызывается ровно один раз
ВызватьИсключение;
КонецПопытки;
Правило 4: Минимизация времени транзакции
Почему: Пока транзакция открыта, изменённые данные заблокированы в СУБД. Другие сеансы, обращающиеся к тем же данным, ждут завершения транзакции. Таймаут ожидания — обычно 20 секунд, после чего — ошибка.
Длинная транзакция = каскадные блокировки = пользователи не могут работать.
Правильно — минимальный блок транзакции
// Подготовка данных — ВНЕ транзакции
МассивДанных = ПодготовитьДанные(); // Запросы, вычисления — долго, но безопасно
ПроверитьКорректность(МассивДанных); // Валидация — тоже вне транзакции
// Транзакция — только запись
НачатьТранзакцию();
Попытка
// Только быстрые операции записи
Для Каждого ДанныеСтроки Из МассивДанных Цикл
ЗаписатьСтроку(ДанныеСтроки);
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации(...);
ВызватьИсключение;
КонецПопытки;
Неправильно — долгие операции внутри транзакции
// ❌ ПЛОХО: запрос + обработка + внешний вызов внутри транзакции
НачатьТранзакцию();
Попытка
// Долгий запрос — блокирует данные на время выполнения
Результат = ВыполнитьСложныйЗапрос(); // 5 секунд
// Обработка — ещё время
Для Каждого Строка Из Результат Цикл
ОбработатьСтроку(Строка); // Каждая строка — запись в БД
КонецЦикла;
// Внешний вызов — КАТАСТРОФА в транзакции!
ОтправитьHTTPЗапрос(Данные); // Таймаут HTTP = 30 сек → блокировка 30 сек!
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
КонецПопытки;
Правило 5: Управляемые блокировки — БлокировкаДанных
Почему: Автоматические блокировки 1С (режим «Автоматический») блокируют целые таблицы, что вызывает конфликты при параллельной работе пользователей. Управляемые блокировки позволяют блокировать только нужные записи, радикально снижая конкуренцию.
Стандарт ИТС: «Управляемые блокировки» — использовать БлокировкаДанных для гранулярной блокировки перед чтением-изменением.
Паттерн: прочитать-заблокировать-изменить-записать
НачатьТранзакцию();
Попытка
// 1. СНАЧАЛА блокируем — чтобы между чтением и записью никто не изменил данные
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ТоварыНаСкладах");
ЭлементБлокировки.УстановитьЗначение("Номенклатура", НоменклатураСсылка);
ЭлементБлокировки.УстановитьЗначение("Склад", СкладСсылка);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();
// 2. Теперь читаем — гарантированно актуальные данные
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.КоличествоОстаток КАК Остаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(,
| Номенклатура = &Номенклатура И Склад = &Склад) КАК Остатки";
Запрос.УстановитьПараметр("Номенклатура", НоменклатураСсылка);
Запрос.УстановитьПараметр("Склад", СкладСсылка);
Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
ВызватьИсключение НСтр("ru = 'Нет остатков на складе.'");
КонецЕсли;
Выборка = Результат.Выбрать();
Выборка.Следующий();
// 3. Проверяем и модифицируем
Если Выборка.Остаток < ТребуемоеКоличество Тогда
ВызватьИсключение СтрШаблон(
НСтр("ru = 'Недостаточно остатков. На складе: %1, требуется: %2.'"),
Выборка.Остаток, ТребуемоеКоличество);
КонецЕсли;
// 4. Записываем
// ... запись движений ...
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации(...);
ВызватьИсключение;
КонецПопытки;
Почему блокировка ПЕРЕД чтением
Без блокировки (race condition):
Сеанс A: Читает остаток = 10 | Сеанс B: Читает остаток = 10
Сеанс A: 10 >= 8? Да, списываем 8 | Сеанс B: 10 >= 7? Да, списываем 7
Итого: списано 15 единиц при остатке 10 → отрицательный остаток!
С блокировкой:
Сеанс A: Блокирует → Читает 10 → Списывает 8 → Фиксирует → Разблокирует
Сеанс B: Ждёт блокировку → Читает 2 → 2 < 7 → Ошибка (корректная!)
Правило 6: ЗаблокироватьДанныеДляРедактирования — пессимистичная блокировка объектов
Почему: Когда два пользователя одновременно открывают один документ, оба редактируют и записывают — данные второго перезатрут данные первого (lost update). ЗаблокироватьДанныеДляРедактирования() предотвращает это: второй пользователь получит ошибку «Объект заблокирован пользователем X».
Правильное использование в модуле формы
// Платформа автоматически блокирует объект при открытии формы объекта.
// Ручная блокировка нужна только при программном изменении.
// Программное изменение — с явной блокировкой
Процедура ИзменитьСтатусДокумента(ДокументСсылка, НовыйСтатус)
НачатьТранзакцию();
Попытка
// Блокируем объект от параллельного редактирования
ЗаблокироватьДанныеДляРедактирования(ДокументСсылка);
ДокументОбъект = ДокументСсылка.ПолучитьОбъект();
ДокументОбъект.Статус = НовыйСтатус;
ДокументОбъект.Записать();
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации(
НСтр("ru = 'Изменение статуса документа'"),
УровеньЖурналаРегистрации.Ошибка,,,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
Разница между блокировками
| Тип блокировки | Механизм | Уровень | Когда использовать |
|---|---|---|---|
БлокировкаДанных |
СУБД (управляемая) | Записи регистров/таблиц | Контроль остатков, атомарные операции |
ЗаблокироватьДанныеДляРедактирования |
Сервер 1С (пессимистичная) | Объект целиком | Предотвращение lost update при редактировании |
| Автоматическая блокировка | СУБД (при записи) | Зависит от режима | Всегда работает, но грубая |
Правило 7: ЗаписьЖурналаРегистрации — правильное логирование
Почему: Журнал регистрации (ЖР) — основной инструмент диагностики проблем в продуктиве. Без структурированных логов разбор инцидентов превращается в гадание. Правильное логирование:
- Позволяет найти проблему за минуты, а не за часы
- Даёт контекст: что произошло, с каким объектом, кто инициировал
- Фильтруется по уровню, событию, объекту, пользователю
Полный формат записи
ЗаписьЖурналаРегистрации(
ИмяСобытия, // Строка — иерархическое имя (через точку)
УровеньСобытия, // УровеньЖурналаРегистрации — Ошибка/Предупреждение/Информация/Примечание
МетаданныеОбъекта, // Объект метаданных — для фильтрации по типу
Данные, // Ссылка на объект — для навигации из ЖР
Комментарий); // Строка — подробное описание (до 1024 символов)
Уровни и когда их использовать
| Уровень | Когда | Пример |
|---|---|---|
Ошибка |
Операция не выполнена, данные потеряны или некорректны | Ошибка записи, откат транзакции |
Предупреждение |
Операция выполнена, но с ограничениями | Документ записан без проведения, данные неполные |
Информация |
Значимые события для аудита | Пользователь удалил 100 элементов, обмен данными завершён |
Примечание |
Диагностическая информация | Длительность операции, параметры вызова |
Пример: структурированное логирование
// Имя события — иерархическое, для фильтрации
// Формат: Подсистема.Событие или Объект.Действие
ИмяСобытия = НСтр("ru = 'ОбменДанными.ОтправкаДанных.Ошибка'");
// Комментарий — максимум информации для диагностики
Комментарий = СтрШаблон(
НСтр("ru = 'Ошибка при отправке данных в узел ""%1"".
|Количество объектов: %2.
|Текст ошибки:
|%3'"),
Строка(УзелОбмена),
КоличествоОбъектов,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ЗаписьЖурналаРегистрации(
ИмяСобытия,
УровеньЖурналаРегистрации.Ошибка,
Метаданные.ПланыОбмена.ОбменСКонтрагентами,
УзелОбмена,
Комментарий);
Правило 8: Формирование сообщений об ошибках для пользователя
Почему: Пользователь — не разработчик. Сообщение «{ОбщийМодуль.МодульОбработкиДанных.Модуль(123)}: Ошибка при вызове метода контекста» — бесполезно. Пользователю нужно знать:
- Что произошло (простыми словами)
- Что делать (повторить, обратиться к администратору, проверить данные)
- Куда обратиться за помощью
Правильно — человекопонятные сообщения
Попытка
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
// В журнал — техническую информацию
ЗаписьЖурналаРегистрации(
НСтр("ru = 'Проведение документа'"),
УровеньЖурналаРегистрации.Ошибка,
ДокументОбъект.Метаданные(),
ДокументОбъект.Ссылка,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
// Пользователю — понятное сообщение
ТекстДляПользователя = СтрШаблон(
НСтр("ru = 'Не удалось провести документ ""%1"".
|Попробуйте повторить операцию. Если ошибка повторяется, обратитесь к администратору.
|
|Техническая информация: %2'"),
ДокументОбъект,
КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение ТекстДляПользователя;
КонецПопытки;
Неправильно — технический стек вместо объяснения
// ❌ ПЛОХО: проброс технической ошибки пользователю
Попытка
ДокументОбъект.Записать();
Исключение
ВызватьИсключение; // Пользователь увидит стек вызовов с номерами строк
КонецПопытки;
Правило 9: Правильный проброс исключений — ВызватьИсключение
Почему: В BSL есть два способа пробросить исключение:
-
ВызватьИсключение;(без параметра) — пробрасывает оригинальное исключение с полным стеком вызовов -
ВызватьИсключение ТекстСообщения;(со строкой) — создаёт новое исключение, оригинальный стек теряется
Когда что использовать
| Способ | Когда | Почему |
|---|---|---|
ВызватьИсключение; |
В промежуточном коде (модуль объекта, общий модуль) | Сохраняет оригинальный стек — легче диагностировать |
ВызватьИсключение "Текст"; |
В обработчиках формы, на границе с пользователем | Заменяет технический стек на понятное сообщение |
Пример
// Промежуточный слой — пробрасываем оригинал
Процедура ОбработатьДанные(Данные)
Попытка
ЗаписатьДанные(Данные);
Исключение
ЗаписьЖурналаРегистрации(...);
ВызватьИсключение; // Оригинальный стек сохранён
КонецПопытки;
КонецПроцедуры
// Граница с пользователем — формируем понятное сообщение
&НаСервере
Процедура ОбработатьНаСервере()
Попытка
ОбработатьДанные(ДанныеФормы);
Исключение
ВызватьИсключение СтрШаблон(
НСтр("ru = 'Ошибка обработки данных: %1'"),
КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецПроцедуры
Правило 10: Обработка ошибок при массовых операциях
Почему: При обработке массива документов (пакетное проведение, обмен данными) ошибка в одном документе не должна останавливать обработку остальных. Но каждая ошибка должна быть залогирована.
Паттерн: поэлементная обработка с накоплением ошибок
Процедура ПровестиДокументыПакетно(МассивДокументов)
МассивОшибок = Новый Массив;
Для Каждого ДокументСсылка Из МассивДокументов Цикл
НачатьТранзакцию();
Попытка
ДокументОбъект = ДокументСсылка.ПолучитьОбъект();
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ИнфоОшибки = ИнформацияОбОшибке();
// Логируем каждую ошибку отдельно
ЗаписьЖурналаРегистрации(
НСтр("ru = 'Пакетное проведение'"),
УровеньЖурналаРегистрации.Ошибка,
ДокументСсылка.Метаданные(),
ДокументСсылка,
ПодробноеПредставлениеОшибки(ИнфоОшибки));
// Накапливаем для итогового отчёта
МассивОшибок.Добавить(Новый Структура("Документ, Ошибка",
ДокументСсылка,
КраткоеПредставлениеОшибки(ИнфоОшибки)));
// НЕ прерываем цикл — продолжаем с остальными документами
КонецПопытки;
КонецЦикла;
// Итоговый отчёт
Если МассивОшибок.Количество() > 0 Тогда
ТекстИтога = СтрШаблон(
НСтр("ru = 'Обработано документов: %1. Ошибок: %2.'"),
МассивДокументов.Количество(),
МассивОшибок.Количество());
// Выводим ошибки пользователю
Для Каждого ОписаниеОшибки Из МассивОшибок Цикл
ОбщегоНазначения.СообщитьПользователю(
СтрШаблон(НСтр("ru = 'Документ %1: %2'"),
ОписаниеОшибки.Документ, ОписаниеОшибки.Ошибка));
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Правило 11: Таймауты блокировок и deadlock
Почему: Когда два сеанса блокируют данные в разном порядке, возникает deadlock (взаимная блокировка):
- Сеанс A заблокировал запись 1, ждёт запись 2
- Сеанс B заблокировал запись 2, ждёт запись 1
- Оба ждут бесконечно
СУБД обнаруживает deadlock и откатывает одну из транзакций. Это нормально, но нужно обработать.
Правильно — единый порядок блокировки
// ПРАВИЛО: всегда блокируйте ресурсы в одном порядке (например, по ссылке)
МассивСсылок = ОбщегоНазначенияКлиентСервер.СвернутьМассив(МассивДокументов);
МассивСсылок.СортироватьПоЗначению(); // Фиксированный порядок — предотвращает deadlock
Для Каждого Ссылка Из МассивСсылок Цикл
ЗаблокироватьДанныеДляРедактирования(Ссылка);
КонецЦикла;
Обработка ошибки блокировки
НачатьТранзакцию();
Попытка
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Справочник.Номенклатура");
ЭлементБлокировки.УстановитьЗначение("Ссылка", НоменклатураСсылка);
Попытка
Блокировка.Заблокировать();
Исключение
// Не удалось заблокировать — данные заняты другим пользователем
ОтменитьТранзакцию();
ВызватьИсключение СтрШаблон(
НСтр("ru = 'Не удалось заблокировать ""%1"". Данные редактируются другим пользователем. Повторите попытку позже.'"),
НоменклатураСсылка);
КонецПопытки;
// Данные заблокированы — безопасно работаем
НоменклатураОбъект = НоменклатураСсылка.ПолучитьОбъект();
// ...
НоменклатураОбъект.Записать();
ЗафиксироватьТранзакцию();
Исключение
Если ТранзакцияАктивна() Тогда
ОтменитьТранзакцию();
КонецЕсли;
ЗаписьЖурналаРегистрации(...);
ВызватьИсключение;
КонецПопытки;
Правило 12: Попытка/Исключение для внешних вызовов
Почему: Вызовы внешних систем (HTTP, почта, файловые операции) ненадёжны по определению: сеть может быть недоступна, файл — заблокирован, сервис — перегружен. Такие вызовы всегда оборачиваются в Попытка/Исключение с осмысленной обработкой.
Паттерн: HTTP-вызов с повторными попытками
Функция ОтправитьДанныеВоВнешнююСистему(Данные)
МаксимумПопыток = 3;
Для НомерПопытки = 1 По МаксимумПопыток Цикл
Попытка
HTTPСоединение = Новый HTTPСоединение("api.example.com",,,,, 30); // таймаут 30 сек
Запрос = Новый HTTPЗапрос("/api/data");
Запрос.УстановитьТелоИзСтроки(Данные);
Ответ = HTTPСоединение.ОтправитьДляОбработки(Запрос);
Если Ответ.КодСостояния = 200 Тогда
Возврат Истина;
Иначе
ВызватьИсключение СтрШаблон(
НСтр("ru = 'Сервер вернул код %1: %2'"),
Ответ.КодСостояния,
Ответ.ПолучитьТелоКакСтроку());
КонецЕсли;
Исключение
ЗаписьЖурналаРегистрации(
НСтр("ru = 'Интеграция.ОтправкаДанных'"),
?(НомерПопытки < МаксимумПопыток,
УровеньЖурналаРегистрации.Предупреждение,
УровеньЖурналаРегистрации.Ошибка),,,
СтрШаблон(НСтр("ru = 'Попытка %1 из %2. Ошибка: %3'"),
НомерПопытки, МаксимумПопыток,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке())));
Если НомерПопытки = МаксимумПопыток Тогда
ВызватьИсключение;
КонецЕсли;
// Пауза перед повторной попыткой (exponential backoff)
// В 1С нет встроенного Sleep, но можно использовать обработчик ожидания
КонецПопытки;
КонецЦикла;
Возврат Ложь;
КонецФункции
depends_on: []
chat Comments (0)
Sign in to join the discussion and leave a comment.
Skill Details
Related Skills
Build your own?
Join 12,000+ developers contributing to the Claude ecosystem.
No comments yet. Be the first to share your thoughts!