Как программно завершить зависшее фоновое задание или пользовательский сеанс в 1С 8.3?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей IT и автоматизация бизнеса
← На главную

В процессе работы с системами 1С:Предприятие 8.3 мы часто сталкиваемся с ситуациями, когда фоновые задания или пользовательские сеансы по различным причинам "зависают". Это может быть вызвано некорректным кодом, непредвиденными ошибками, блокировками данных или другими факторами. Зависшие процессы могут привести к нежелательным последствиям: блокировке критически важных данных, чрезмерной нагрузке на сервер, замедлению работы системы в целом. Наша задача — разобраться, как можно программно, из кода 1С, найти такие зависшие процессы и корректно или принудительно их завершить.

Рассмотрим различные подходы и методы, доступные в платформе 1С:Предприятие 8.3, для эффективного управления сеансами и фоновыми заданиями. Мы будем исходить из того, что нам необходимо решить эту проблему именно из управляемого приложения или серверного модуля.

Понимание проблемы: почему зависают сеансы и фоновые задания?

Прежде чем перейти к методам завершения, давайте выясним основные причины, по которым сеансы или фоновые задания могут зависнуть:

  1. Бесконечные циклы или длительные операции: Код может попасть в бесконечный цикл без возможности выхода или выполнять очень длительную операцию, которая не предусмотрена по времени выполнения, что требует регулярного замера производительности и поиска узких мест в системе.
  2. Необработанные исключения: Ошибки, которые не были перехвачены блоком Попытка...Исключение...КонецПопытки, могут привести к остановке выполнения и зависанию сеанса.
  3. Блокировки данных: Конфликты при работе с данными, когда один сеанс удерживает блокировку, а другой пытается получить к ним доступ, могут вызвать взаимное ожидание (deadlock) и зависание.
  4. Проблемы с внешними системами: Если фоновое задание или сеанс взаимодействует с внешними сервисами, базами данных или файлами, и эти внешние ресурсы недоступны или отвечают медленно, процесс может зависнуть в ожидании ответа.
  5. Недостаток ресурсов сервера: Чрезмерная нагрузка на сервер, недостаток оперативной памяти, процессорного времени или свободного места на дисках (для контроля которого существуют специальные Telegram-боты) также может привести к замедлению и зависанию процессов.

Важно понимать, что для решения проблемы зависаний критически важны не только методы принудительного завершения, но и меры по предотвращению таких ситуаций: качественное тестирование кода, правильная обработка ошибок и транзакций, а также мониторинг состояния системы.

Метод 1: Программное завершение сеансов через объекты 1С (мягкое завершение)

Первый и наиболее предпочтительный способ — это попытка "мягкого" завершения сеанса средствами самой платформы. Этот метод подразумевает, что платформа 1С корректно обрабатывает команду на завершение, давая сеансу возможность завершить свои текущие операции и освободить ресурсы.

Получение списка сеансов информационной базы

Для начала нам необходимо получить информацию обо всех активных сеансах (поможет утилита администрирования и просмотра активных сеансов) в нашей информационной базе. Это реализуется с помощью глобальной функции ПолучитьСеансыИнформационнойБазы(). Эта функция возвращает коллекцию объектов СеансИнформационнойБазы.

Каждый объект СеансИнформационнойБазы предоставляет нам важные сведения о конкретном сеансе, которые мы можем использовать для идентификации "проблемного" сеанса:

Рассмотрим пример кода, который позволяет получить список всех сеансов и вывести некоторую информацию о них:


Функция ПолучитьИнформациюОСеансах()
    Результат = Новый СписокЗначений;
    Сеансы = ПолучитьСеансыИнформационнойБазы();
    
    Для Каждого Сеанс Из Сеансы Цикл
        СтрокаИнформации = "Идентификатор: " + Сеанс.Идентификатор
                           + ", Пользователь: " + Сеанс.ИмяПользователя
                           + ", Вид клиента: " + Сеанс.ВидКлиента
                           + ", Компьютер: " + Сеанс.ИмяКомпьютера
                           + ", Начало сеанса: " + Формат(Сеанс.НачалоСеанса, "ДФ=dd.MM.yyyy HH:mm:ss")
                           + ", Активность: " + ?(Сеанс.Активность, "Да", "Нет")
                           + ", Последняя активность: " + Формат(Сеанс.ВремяПоследнейАктивности, "ДФ=dd.MM.yyyy HH:mm:ss");
        Результат.Добавить(Сеанс.Идентификатор, СтрокаИнформации);
    КонецЦикла;
    
    Возврат Результат;
КонецФункции

// Пример использования
ИнфоОСеансах = ПолучитьИнформациюОСеансах();
Для Каждого Элемент Из ИнфоОСеансах Цикл
    Сообщить(Элемент.Представление);
КонецЦикла;

Используя эту информацию, мы можем отфильтровать сеансы по различным критериям, например, найти все фоновые задания, запущенные определенным пользователем, или сеансы, которые не проявляли активности в течение длительного времени.

Завершение конкретного сеанса методом Сеанс.Завершить()

После того как мы идентифицировали целевой сеанс, мы можем попытаться завершить его с помощью метода Сеанс.Завершить(), который доступен для каждого объекта СеансИнформационнойБазы. Этот метод отправляет сигнал платформе 1С с просьбой завершить работу данного сеанса.

Важно понимать, что этот метод является "мягким". Если сеанс находится в состоянии, когда он не может обработать этот сигнал (например, в глубоком зависании, бесконечном цикле без контроля платформы, или в ожидании внешней операции), то метод Сеанс.Завершить() может не дать немедленного эффекта. Сеанс может продолжать висеть, пока не выйдет из своего состояния или пока не будет принудительно завершен на уровне операционной системы.

Рассмотрим пример кода для завершения фонового задания по его имени или другим характеристикам, как обсуждалось на форуме:


Процедура ЗавершитьФоновоеЗадание(ИскомоеИмяЗадания)
    Сеансы = ПолучитьСеансыИнформационнойБазы();
    НайденныеСеансы = Новый Массив();
    
    Для Каждого Сеанс Из Сеансы Цикл
        // Ищем фоновое задание по ВидуКлиента и, например, по имени пользователя,
        // которое обычно соответствует имени регламентного задания или имени пользователя, его запустившего.
        // Или можно искать по ИдентификаторуФоновогоЗадания, если он доступен
        // (например, при получении информации о регламентных заданиях).
        Если Сеанс.ВидКлиента = ВидКлиента.ФоновоеЗадание И Сеанс.ИмяПользователя = ИскомоеИмяЗадания Тогда 
            НайденныеСеансы.Добавить(Сеанс);
        КонецЕсли;
    КонецЦикла;
    
    Если НайденныеСеансы.Количество() = 0 Тогда
        Сообщить("Фоновое задание с именем '" + ИскомоеИмяЗадания + "' не найдено среди активных сеансов.");
        Возврат;
    КонецЕсли;
    
    Для Каждого СеансДляЗавершения Из НайденныеСеансы Цикл
        Попытка
            СеансДляЗавершения.Завершить();
            Сообщить("Попытка завершения сеанса фонового задания " + СеансДляЗавершения.ИмяПользователя 
                     + " (ИД: " + СеансДляЗавершения.Идентификатор + ") инициирована.");
        Исключение
            Сообщить("Ошибка при попытке завершения сеанса " + СеансДляЗавершения.Идентификатор + ": " + ОписаниеОшибки());
        КонецПопытки;
    КонецЦикла;
КонецПроцедуры

// Пример использования для фонового задания с именем "ОбновлениеКурсовВалют"
// (ваше фоновое задание должно быть запущено под этим именем пользователя или иметь соответствующий ИД)
// ЗавершитьФоновоеЗадание("ОбновлениеКурсовВалют"); 
// Или, если вы знаете точный идентификатор сеанса (UUID), полученный ранее:
Процедура ЗавершитьСеансПоИД(ИдентификаторСеанса)
    Сеансы = ПолучитьСеансыИнформационнойБазы();
    Для Каждого Сеанс Из Сеансы Цикл
        Если Сеанс.Идентификатор = ИдентификаторСеанса Тогда
            Попытка
                Сеанс.Завершить();
                Сообщить("Сеанс " + ИдентификаторСеанса + " успешно инициирован к завершению.");
            Исключение
                Сообщить("Ошибка при попытке завершения сеанса " + ИдентификаторСеанса + ": " + ОписаниеОшибки());
            КонецПопытки;
            Возврат;
        КонецЕсли;
    КонецЦикла;
    Сообщить("Сеанс с идентификатором " + ИдентификаторСеанса + " не найден.");
КонецПроцедуры

// Пример использования
// ЗавершитьСеансПоИД("a1b2c3d4-e5f6-7890-1234-567890abcdef"); 

Управление регламентными заданиями

Для фоновых заданий, которые являются регламентными, существует также возможность воздействия через объект РегламентноеЗадание. У него есть метод Завершить(). Этот метод пытается сигнализировать выполняющемуся регламентному заданию о необходимости завершения. Однако, как и в случае с Сеанс.Завершить(), это сработает только в том случае, если код регламентного задания способен обработать этот сигнал и корректно выйти. Если задание находится в "глухом" зависании (например, в бесконечном цикле без проверки статуса), этот метод не поможет.

Чтобы использовать этот подход, нам сначала нужно получить объект РегламентноеЗадание по его имени или идентификатору. Мы можем это сделать, перебрав все зарегистрированные регламентные задания:


Процедура ЗавершитьРегламентноеЗаданиеПоИмени(ИмяРегламентногоЗадания)
    Для Каждого Задание Из РегламентныеЗадания.ПолучитьВсеРегламентныеЗадания() Цикл
        Если Задание.Имя = ИмяРегламентногоЗадания И Задание.Состояние = СостояниеРегламентногоЗадания.Выполняется Тогда
            Попытка
                Задание.Завершить(); // Попытка завершить регламентное задание
                Сообщить("Сигнал на завершение для регламентного задания '" + ИмяРегламентногоЗадания + "' отправлен.");
            Исключение
                Сообщить("Ошибка при попытке завершения регламентного задания '" + ИмяРегламентногоЗадания + "': " + ОписаниеОшибки());
            КонецПопытки;
            Возврат;
        КонецЕсли;
    КонецЦикла;
    Сообщить("Регламентное задание с именем '" + ИмяРегламентногоЗадания + "' не найдено или не находится в состоянии 'Выполняется'.");
КонецПроцедуры

// Пример использования
// ЗавершитьРегламентноеЗаданиеПоИмени("ОбновлениеДанных");

Этот подход полезен, если фоновое задание написано с учетом возможности такого "мягкого" прерывания, например, периодически проверяет флаг прерывания и выходит, если он установлен. Существуют готовые примеры реализации отслеживания и отмены выполнения фоновых заданий, которые можно взять за основу.

Метод 2: Принудительное завершение (административный подход)

Когда "мягкие" методы не работают, или нам требуется гарантированное и немедленное завершение сеанса, мы обращаемся к административным функциям платформы. Функция ПринудительноеЗавершениеПользователей() — это мощный инструмент, который позволяет администраторам системы отключать сеансы независимо от их текущего состояния. Однако, с этой мощью приходит и большая ответственность, так как некорректное использование может привести к потере данных или нестабильности системы.

Функция ПринудительноеЗавершениеПользователей()

Функция ПринудительноеЗавершениеПользователей() является глобальной и доступна в серверном контексте. Она предназначена для принудительного отключения одного или нескольких сеансов по их идентификаторам. Ее синтаксис выглядит следующим образом:

ПринудительноеЗавершениеПользователей(МассивИдентификаторовСеансов, ТекстСообщения, Отказ)

Важные моменты при использовании ПринудительноеЗавершениеПользователей():

  1. Требуемые права: Пользователь, под которым выполняется код с вызовом этой функции, должен обладать административными правами в 1С или иметь специальную роль "Завершение работы пользователей". Без этих прав функция не сработает, и параметр Отказ будет установлен в Истина.
  2. Серверный вызов: Данная функция является серверной и должна вызываться исключительно в серверном контексте (в серверных модулях, модулях менеджера или через вызовы серверных процедур/функций из клиентского кода).
  3. Потенциальная потеря данных: Принудительное завершение может привести к незавершенным транзакциям и потенциальной потере несохраненных данных пользователя. Используйте эту функцию с крайней осторожностью и только тогда, когда другие методы неэффективны.
  4. Влияние на фоновые задания: Для фоновых заданий, которые являются фактически безмолвными сеансами, принудительное завершение также работает, гарантируя остановку их выполнения.

Рассмотрим пример кода, демонстрирующий использование функции ПринудительноеЗавершениеПользователей(). Этот пример предполагает, что мы уже определили идентификатор зависшего сеанса (например, с помощью анализа активности):


Процедура ПринудительноЗавершитьСеансПоИД(ИдентификаторСеансаДляЗавершения)
    МассивСеансовДляЗавершения = Новый Массив();
    МассивСеансовДляЗавершения.Добавить(ИдентификаторСеансаДляЗавершения);
    
    Попытка
        Отказ = Ложь;
        ТекстСообщения = "Ваш сеанс будет принудительно завершен администратором в связи с длительной неактивностью или зависанием. " 
                         + "Пожалуйста, сохраните ваши данные и перезапустите 1С.";
        
        ПринудительноеЗавершениеПользователей(МассивСеансовДляЗавершения, ТекстСообщения, Отказ);
        
        Если Не Отказ Тогда
            Сообщить("Сеанс с ИД " + ИдентификаторСеансаДляЗавершения + " успешно принудительно завершен.");
        Иначе
            Сообщить("Принудительное завершение сеанса с ИД " + ИдентификаторСеансаДляЗавершения 
                     + " отклонено. Проверьте права пользователя, под которым выполняется код.");
        КонецЕсли;
    Исключение
        Сообщить("Ошибка при принудительном завершении сеанса " + ИдентификаторСеансаДляЗавершения + ": " + ОписаниеОшибки());
    КонецПопытки;
КонецПроцедуры

// Пример использования:
// Предположим, мы обнаружили зависший сеанс с ID "ваш-uuid-сеанса"
// ПринудительноЗавершитьСеансПоИД("a1b2c3d4-e5f6-7890-1234-567890abcdef");

Этот метод предоставляет максимальную гарантию завершения сеанса, но должен использоваться обдуманно и, желательно, с предварительным уведомлением пользователя, если это интерактивный сеанс.

Сопутствующие аспекты и лучшие практики

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

Как определить зависший сеанс/задание?

Автоматическое определение зависших сеансов — ключевой элемент надежной системы. Мы можем использовать несколько подходов:

  1. Анализ Сеанс.ВремяПоследнейАктивности: Это наиболее распространенный способ. Мы можем создать регламентное задание, которое периодически (например, раз в 5-10 минут) обходит все сеансы информационной базы. Если для какого-либо сеанса ВремяПоследнейАктивности существенно старше текущего времени (например, на 30 минут или более) и сеанс все еще активен (Сеанс.Активность = Истина), то с большой долей вероятности он завис. Мы можем задать порог неактивности, после которого сеанс считается зависшим и подлежит завершению.
  2. Анализ журнала регистрации: В журнал регистрации 1С записываются события начала и завершения фоновых заданий, а также ошибки. Если фоновое задание начало выполняться, но запись о его завершении или ошибке отсутствует в течение аномально долгого времени, это может указывать на зависание. Для автоматизации этого процесса существуют инструменты анализа и информирования о невыполненных регламентных заданиях.
  3. Тайм-ауты в коде заданий: При разработке фоновых заданий важно предусматривать механизм тайм-аутов для длительных операций, особенно при взаимодействии с внешними системами. Если операция превышает допустимое время, задание должно само себя завершить или выдать ошибку.
  4. "Сторожевой таймер": Можно реализовать внешний "сторожевой таймер" (отдельное регламентное задание), которое будет мониторить работу других регламентных заданий, записывая их состояние в регистр сведений. Если статус задания не обновляется в течение определенного времени, сторожевой таймер может инициировать его принудительное завершение.

Обеспечение устойчивости фоновых заданий

Лучший способ борьбы с зависшими заданиями — это предотвращение их появления. Разработчикам необходимо придерживаться следующих принципов при написании кода фоновых заданий. Для сложных сценариев можно проектировать целые конвейеры обработки задач, чтобы разбить длительные операции на управляемые этапы.

  1. Обработка исключений: Все критические участки кода, особенно те, которые взаимодействуют с данными или внешними системами, должны быть обернуты в блоки Попытка...Исключение...КонецПопытки. Это позволит корректно обрабатывать ошибки, записывать их в журнал и завершать задание вместо зависания.
  2. Логирование прогресса: Фоновые задания должны регулярно записывать свой прогресс в журнал регистрации или специальный регистр сведений. Это позволяет отслеживать их текущее состояние и определять, на каком этапе произошло зависание.
  3. Использование транзакций: При работе с данными важно использовать транзакции. Если задание завершается с ошибкой, все изменения должны быть откачены.
  4. Защита от повторного запуска: Убедитесь, что одно и то же регламентное задание не может быть запущено одновременно несколько раз, если это не предусмотрено логикой. Используйте блокировки или контроль состояния.
  5. Ограничение по времени выполнения: По возможности, предусматривайте в коде задания проверку на максимально допустимое время выполнения. Если время истекло, задание должно само себя завершить.

Права доступа и безопасность

Вопросы прав доступа при завершении сеансов являются критически важными:

Выполнение на сервере

Все рассмотренные функции и методы (ПолучитьСеансыИнформационнойБазы(), Сеанс.Завершить(), ПринудительноеЗавершениеПользователей()) являются серверными. Это означает, что они должны вызываться только в серверном контексте. Если вам нужно инициировать завершение сеанса из клиентского кода (например, из формы обработки администратора), вам необходимо будет создать серверную процедуру или функцию, которая будет выполнять эти действия, и вызывать ее с клиента.


// Пример клиентской процедуры, вызывающей серверную для завершения сеанса
&НаКлиенте
Процедура ЗавершитьСеансПоКнопке(Команда)
    ИдентификаторВыбранногоСеанса = ПолучитьИдентификаторСеансаИзТаблицыНаФорме(); // Предполагается, что на форме есть список сеансов
    Если Не ЗначениеЗаполнено(ИдентификаторВыбранногоСеанса) Тогда
        ПоказатьПредупреждение(, "Выберите сеанс для завершения.");
        Возврат;
    КонецЕсли;
    
    Вопрос = Новый ДиалогВопрос(ВопросДиалога.ДаНет, "Вы уверены, что хотите принудительно завершить сеанс " 
                                + ИдентификаторВыбранногоСеанса + "?", 0, "Завершение сеанса");
    
    Если Вопрос.ПолучитьРезультат() = КодВозвратаДиалога.Да Тогда
        // Вызов серверной процедуры
        ПринудительноЗавершитьСеансНаСервере(ИдентификаторВыбранногоСеанса);
    КонецЕсли;
КонецПроцедуры

// Пример серверной процедуры, вызываемой с клиента
&НаСервере
Процедура ПринудительноЗавершитьСеансНаСервере(ИдентификаторСеансаДляЗавершения)
    ПринудительноЗавершитьСеансПоИД(ИдентификаторСеансаДляЗавершения); // Вызываем нашу ранее описанную серверную функцию
КонецПроцедуры

Такой подход обеспечивает правильный контекст выполнения и разделение обязанностей между клиентской и серверной частями приложения.

Заключение

Мы рассмотрели два основных подхода к программному завершению зависших фоновых заданий и пользовательских сеансов в 1С:Предприятие 8.3: "мягкое" завершение с помощью Сеанс.Завершить() и РегламентноеЗадание.Завершить(), а также принудительное административное завершение с использованием ПринудительноеЗавершениеПользователей(). Мы выяснили, что для эффективного управления нам необходимо уметь получать информацию о сеансах, анализировать их активность и выбирать подходящий метод завершения в зависимости от ситуации.

Наш выбор метода должен быть обусловлен степенью "зависания" сеанса и требуемой гарантией его остановки. Если "мягкий" метод не дает результата, мы прибегаем к принудительному, помня о необходимости соответствующих прав и возможных рисках. Кроме того, мы подчеркнули важность превентивных мер: качественного кода фоновых заданий, обработки исключений, логирования и мониторинга для минимизации случаев зависания в будущем. Применяя эти знания на практике, мы сможем создать более стабильные и управляемые системы на платформе 1С:Предприятие.

← На главную