ДЛЯ ИЗМЕНЕНИЯ: удержать свою блокировку и проверить два сеанса

Учебные задачи

Новый собственный стенд, три настоящих записи и два именованных сеанса. Хранитель читает 001 ДЛЯ ИЗМЕНЕНИЯ в своей транзакции, проверяющий сравнивает ту же запись и контрольную003. После отмены хранителя оба запроса должны снова прочитать свои полные строки; ошибки сами по себе не выдаются за доказательство блокировки.

Где и как запускать

Полный серверный общий модуль УчебныйЯзык только собственного нового стенда УчебныеАвтоблокировки85. Полные точки создания/preflight/удержания/конкуренции/очистки и полный отдельный модуль формы. Запуск в 1С не проверялся.

Подготовка

  1. Создайте НОВУЮ отдельную пустую учебную базу, не копию рабочей, на точной 8.5.1.1343. Имя конфигурации УчебныеАвтоблокировки85; совместимость Не использовать; основной режим запуска Управляемое приложение, толстый клиент. Нет расширений базы/сеанса, других прикладных метаданных, общих реквизитов, подписок/заданий, внешних источников. По очереди можно повторить опыт на отдельном файловом стенде и отдельном клиент-серверном; их данные/квитанции не смешивать.

  2. Свойства конфигурации → Режим управления блокировкой данных=Автоматический. Справочники → УчебныеЗаблокированные85: Режим управления блокировкой данных=Автоматический; Иерархический=Нет; владельцы пусты; Код только Строка длина3, Автонумерация=Нет; Наименование длина40; собственных реквизитов/табличных частей/предопределённых нет. Стандартные поля остаются. Guard читает ОБА metadata свойства; сравнивает с Метаданные.СвойстваОбъектов.РежимУправленияБлокировкойДанныхПоУмолчанию.Автоматический. Это не системный enum параметра НачатьТранзакцию, и readonly-свойствам ничего не присваивается.

  3. Общие → Общие модули → УчебныйЯзык: Сервер=Истина, Глобальный=Ложь, ВызовСервера=Ложь, Привилегированный=Ложь; клиентские/внешнее соединение отключены; повторное использование выключено. Вставьте ВЕСЬ основной модуль с helpers на уровне модуля. Все другие объектные/менеджерские модули, формы справочника, модули приложения/сеанса и обработчики вручную полностью пусты: guard не доказывает это через native МодульОбъекта/МодульМенеджера.

  4. Создайте ровно одну роль УчебныеБлокировки85. На конфигурацию дайте ТолстыйКлиент, РежимОсновногоОкнаОбычный, ИнтерактивноеОткрытиеВнешнихОбработок и только для нового своего стенда Администрирование/АдминистрированиеДанных; на свой каталог Чтение, Просмотр, Добавление, Изменение, Удаление без RLS. В Конфигураторе → Администрирование → Пользователи создайте УчебныйХранитель85 и УчебныйПроверяющий85 только с этой ролью. Свои пароли задайте вручную, рабочие учётные записи не используйте. Привилегированный режим в коде не включается, Текущий.Роли не читается.

  5. В списке запуска зарегистрируйте свой файловый стенд минимальной строкой, начинающейся File=, или свой серверный строкой, начинающейся Srvr=. Возвращаемая СтрокаСоединенияИнформационнойБазы не обязана быть нормализована: helper проверяет только строгий начальный префикс этого нового стенда и отказывается от нестандартного представления, а не ищет File= внутри чужих значений. Полная строка соединения/пароли не выводятся. Не подменяйте реальный вариант ручным Булево.

  6. До опыта прочитайте фактическую настройку времени ожидания своей учебной базы. Для короткого опыта нужны целые1..30секунд; guard читает ПолучитьВремяОжиданияБлокировкиДанных и отказывает при неподходящем значении. Код НИКОГДА не вызывает УстановитьВремяОжиданияБлокировкиДанных: это административная настройка всей базы с временным монопольным доступом, не локальный таймаут сеанса. Оmitted-argument20секунд setter не доказывает текущую настройку новой базы. Не меняйте рабочую базу; если свой стенд не подходит, сначала разберите отдельную настройку вне всех опытов.

  7. Создайте свою новую внешнюю обработку УчебнаяАвтоблокировка85, управляемую Форма. Реквизиты ИД001, ИД002, ИД003 — только Строка длина36, три поля ввода. Команды/кнопки СоздатьДанные, ПроверитьГотовность, Выполнить, ПроверитьПервую, ПроверитьТретью, УдалитьСвоиДанные; их действия — одноимённые процедуры полного отдельного модуля формы. Ссылки восстанавливаются только из реальных ИД, возвращённых собственным созданием; нет готовых демонстрационных GUID.

  8. После ВСЕХ метаданных/флагов/ролей/пользователей и обоих полных модулей сохраните свою конфигурацию. Предыдущие учебные сеансы завершены, активной транзакции/опыта нет. Выполните Конфигурация → Обновить конфигурацию базы данных (F7), подтвердите только свою новую конфигурацию и дождитесь успешного обновления. Затем только свежие явно именованные сеансы. Никаких F7/изменений конфигурации при остановленном серверном коде внутри активной транзакции.

  9. Настройте серверную отладку только своего стенда и возможность приостанавливать выбранный сеанс хранителя, не все сеансы/сервер целиком. Это будущая ручная работа в Конфигураторе/отладчике. Здесь Конфигуратор, BSL, пользовательские базы, блокировки, фиксирование/откат и удаления не запускались.

Процедура ВыполнитьПример(ИД001, ИД002, ИД003) Экспорт
    ПроверитьСвойСеанс("УчебныйХранитель85");
    Вариант = ВариантСвоейБазы();
    Ожидание = ПроверитьСвоёКонечноеОжидание();
    Сообщить(Вариант + "; фактическое ожидание своей базы в секундах: " + Строка(Ожидание));
    Ссылки = СобственныеСсылки(ИД001, ИД002, ИД003);
    ВсеОжидания = СвоиОжидания(Ссылки);
    Запрос = Новый Запрос;
    Запрос.Текст = "ВЫБРАТЬ Кат.Ссылка КАК Ссылка, Кат.Код КАК Код,
    |Кат.Наименование КАК Наименование, Кат.ПометкаУдаления КАК ПометкаУдаления
    |ИЗ Справочник.УчебныеЗаблокированные85 КАК Кат
    |ГДЕ Кат.Ссылка = &СвояСсылка
    |ДЛЯ ИЗМЕНЕНИЯ";
    Запрос.УстановитьПараметр("СвояСсылка", Ссылки[0]);
    НачатьТранзакцию();
    Попытка
        ПроверитьСвоиПолныеСтроки(Запрос.Выполнить().Выгрузить(), СвояОднаСтрока(ВсеОжидания, 0));
        Сообщить("ТОЧКА УДЕРЖАНИЯ: собственная запись001 прочитана ДЛЯ ИЗМЕНЕНИЯ внутри своей транзакции");
    Исключение
        Если ТранзакцияАктивна() Тогда
            ОтменитьТранзакцию();
        КонецЕсли;
        ВызватьИсключение;
    КонецПопытки;
    ОтменитьТранзакцию();
    Если ТранзакцияАктивна() Тогда
        ВызватьИсключение "Своя транзакция хранителя не завершилась";
    КонецЕсли;
    Проверка = Новый Запрос;
    Проверка.Текст = "ВЫБРАТЬ Кат.Ссылка КАК Ссылка, Кат.Код КАК Код,
    |Кат.Наименование КАК Наименование, Кат.ПометкаУдаления КАК ПометкаУдаления
    |ИЗ Справочник.УчебныеЗаблокированные85 КАК Кат
    |УПОРЯДОЧИТЬ ПО Кат.Код ВОЗР";
    ПроверитьСвоиПолныеСтроки(Проверка.Выполнить().Выгрузить(), ВсеОжидания);
КонецПроцедуры

Процедура ПроверитьГотовность(ИД001, ИД002, ИД003) Экспорт
    ПроверитьСвойСеанс("");
    Вариант = ВариантСвоейБазы();
    Ожидание = ПроверитьСвоёКонечноеОжидание();
    Ссылки = СобственныеСсылки(ИД001, ИД002, ИД003);
    Проверка = Новый Запрос;
    Проверка.Текст = "ВЫБРАТЬ Кат.Ссылка КАК Ссылка, Кат.Код КАК Код,
    |Кат.Наименование КАК Наименование, Кат.ПометкаУдаления КАК ПометкаУдаления
    |ИЗ Справочник.УчебныеЗаблокированные85 КАК Кат
    |УПОРЯДОЧИТЬ ПО Кат.Код ВОЗР";
    ПроверитьСвоиПолныеСтроки(Проверка.Выполнить().Выгрузить(), СвоиОжидания(Ссылки));
    Сообщить(Вариант + "; три свои полные записи сверены ДО удержания; ожидание секунд: " + Строка(Ожидание));
КонецПроцедуры

Функция ПроверитьКонкуренцию(ИД001, ИД002, ИД003, Первая) Экспорт
    ПроверитьСвойСеанс("УчебныйПроверяющий85");
    Вариант = ВариантСвоейБазы();
    Ожидание = ПроверитьСвоёКонечноеОжидание();
    Если ТипЗнч(Первая) <> Тип("Булево") Тогда
        ВызватьИсключение "Выбор собственного контрольного опыта должен быть Булево";
    КонецЕсли;
    Индекс = ?(Первая, 0, 2);
    Ссылки = СобственныеСсылки(ИД001, ИД002, ИД003);
    ВсеОжидания = СвоиОжидания(Ссылки);
    Запрос = Новый Запрос;
    Запрос.Текст = "ВЫБРАТЬ Кат.Ссылка КАК Ссылка, Кат.Код КАК Код,
    |Кат.Наименование КАК Наименование, Кат.ПометкаУдаления КАК ПометкаУдаления
    |ИЗ Справочник.УчебныеЗаблокированные85 КАК Кат
    |ГДЕ Кат.Ссылка = &СвояСсылка
    |ДЛЯ ИЗМЕНЕНИЯ";
    Запрос.УстановитьПараметр("СвояСсылка", Ссылки[Индекс]);
    Ошибка = Неопределено;
    Выполнен = Ложь;
    КодОшибки = "";
    Диагностика = "";
    НачатьТранзакцию();
    Попытка
        Попытка
            Результат = Запрос.Выполнить();
        Исключение
            Ошибка = ИнформацияОбОшибке();
        КонецПопытки;
        Если Ошибка = Неопределено Тогда
            ПроверитьСвоиПолныеСтроки(Результат.Выгрузить(), СвояОднаСтрока(ВсеОжидания, Индекс));
            Выполнен = Истина;
        Иначе
            Если Ошибка.ЯвляетсяОшибкойКатегории(КатегорияОшибки.НарушениеПравДоступа, Истина)
                Или Ошибка.ЯвляетсяОшибкойКатегории(КатегорияОшибки.ОшибкаКомпиляцииВстроенногоЯзыка, Истина)
                Или Ошибка.ЯвляетсяОшибкойКатегории(КатегорияОшибки.ОшибкаИспользованияВстроенногоЯзыка, Истина) Тогда
                ВызватьИсключение "Ошибка прав/компиляции/использования не является опытом блокировки: " + Ошибка.Описание;
            КонецЕсли;
            КодОшибки = Ошибка.Код;
            Диагностика = Ошибка.Описание;
        КонецЕсли;
        ОтменитьТранзакцию();
    Исключение
        Если ТранзакцияАктивна() Тогда
            ОтменитьТранзакцию();
        КонецЕсли;
        ВызватьИсключение;
    КонецПопытки;
    Если ТранзакцияАктивна() Тогда
        ВызватьИсключение "Своя транзакция проверяющего не завершилась";
    КонецЕсли;
    Сообщить("Фактическая настройка ожидания своей базы, секунд: " + Строка(Ожидание));
    Возврат Новый Структура("Вариант,КодЗаписи,ЗапросВыполнен,КодОшибки,Диагностика", Вариант, ВсеОжидания[Индекс].Код, Выполнен, КодОшибки, Диагностика);
КонецФункции

Функция СоздатьСвоиДанные() Экспорт
    ПроверитьСвойСеанс("УчебныйХранитель85");
    Проверка = Новый Запрос;
    Проверка.Текст = "ВЫБРАТЬ Кат.Ссылка КАК Ссылка, Кат.Код КАК Код,
    |Кат.Наименование КАК Наименование, Кат.ПометкаУдаления КАК ПометкаУдаления
    |ИЗ Справочник.УчебныеЗаблокированные85 КАК Кат
    |УПОРЯДОЧИТЬ ПО Кат.Код ВОЗР";
    Пустой = Проверка.Выполнить().Выгрузить();
    Если Пустой.Количество() <> 0 Тогда
        ВызватьИсключение "Свой каталог не пуст: существующие записи не изменять и не удалять";
    КонецЕсли;
    НачатьТранзакцию();
    Попытка
        Ссылки = Новый Массив;
        Ссылки.Добавить(СоздатьСвоюЗапись("001", "Своя первая запись"));
        Ссылки.Добавить(СоздатьСвоюЗапись("002", "Своя вторая запись"));
        Ссылки.Добавить(СоздатьСвоюЗапись("003", "Своя третья запись"));
        Ожидания = СвоиОжидания(Ссылки);
        ПроверитьСвоиПолныеСтроки(Проверка.Выполнить().Выгрузить(), Ожидания);
        СвоиИД = Новый Массив;
        Для Каждого Ссылка Из Ссылки Цикл
            СвойИД = Строка(Ссылка.УникальныйИдентификатор());
            Если СсылкаИзСвоегоИД(СвойИД) <> Ссылка Тогда
                ВызватьИсключение "Не совпал полный собственный ИД";
            КонецЕсли;
            СвоиИД.Добавить(СвойИД);
            Сообщить("Квитанция своих ещё не подтверждённых данных: " + СвойИД);
        КонецЦикла;
        ЗафиксироватьТранзакцию();
    Исключение
        Если ТранзакцияАктивна() Тогда
            ОтменитьТранзакцию();
        КонецЕсли;
        Сообщить("Создание не подтверждено; при неопределённом сбое фиксации не удалять данные вслепую");
        ВызватьИсключение;
    КонецПопытки;
    Если ТранзакцияАктивна() Тогда
        ВызватьИсключение "Своя фиксация не завершила транзакцию";
    КонецЕсли;
    ПроверитьСвоиПолныеСтроки(Проверка.Выполнить().Выгрузить(), Ожидания);
    Возврат СвоиИД;
КонецФункции

Процедура УдалитьСвоиДанные(ИД001, ИД002, ИД003) Экспорт
    ПроверитьСвойСеанс("УчебныйХранитель85");
    Ссылки = СобственныеСсылки(ИД001, ИД002, ИД003);
    Ожидания = СвоиОжидания(Ссылки);
    Проверка = Новый Запрос;
    Проверка.Текст = "ВЫБРАТЬ Кат.Ссылка КАК Ссылка, Кат.Код КАК Код,
    |Кат.Наименование КАК Наименование, Кат.ПометкаУдаления КАК ПометкаУдаления
    |ИЗ Справочник.УчебныеЗаблокированные85 КАК Кат
    |УПОРЯДОЧИТЬ ПО Кат.Код ВОЗР";
    НачатьТранзакцию();
    Попытка
        ПроверитьСвоиПолныеСтроки(Проверка.Выполнить().Выгрузить(), Ожидания);
        Объекты = Новый Массив;
        Для Каждого Ожидание Из Ожидания Цикл
            СвойОбъект = Ожидание.Ссылка.ПолучитьОбъект();
            ПроверитьСвойОбъектПередУдалением(СвойОбъект, Ожидание);
            Объекты.Добавить(СвойОбъект);
        КонецЦикла;
        Для Каждого СвойОбъект Из Объекты Цикл
            СвойОбъект.Удалить();
        КонецЦикла;
        Если Проверка.Выполнить().Выгрузить().Количество() <> 0 Тогда
            ВызватьИсключение "После своих трёх удалений каталог не пуст";
        КонецЕсли;
        ЗафиксироватьТранзакцию();
    Исключение
        Если ТранзакцияАктивна() Тогда
            ОтменитьТранзакцию();
        КонецЕсли;
        Сообщить("Очистка не подтверждена; не повторять удаление без проверки собственных данных");
        ВызватьИсключение;
    КонецПопытки;
    Если ТранзакцияАктивна() Тогда
        ВызватьИсключение "После своей очистки осталась активная транзакция";
    КонецЕсли;
    Если Проверка.Выполнить().Выгрузить().Количество() <> 0 Тогда
        ВызватьИсключение "Пустота собственного каталога после фиксации не подтверждена";
    КонецЕсли;
КонецПроцедуры

Процедура ПроверитьСвойСтендБлокировок()
    Если Метаданные.Справочники.Количество() <> 1 Или Метаданные.Документы.Количество() <> 0 Или Метаданные.Перечисления.Количество() <> 0 Или Метаданные.РегистрыСведений.Количество() <> 0 Или Метаданные.РегистрыНакопления.Количество() <> 0 Или Метаданные.РегистрыБухгалтерии.Количество() <> 0 Или Метаданные.РегистрыРасчета.Количество() <> 0 Или Метаданные.ПланыВидовХарактеристик.Количество() <> 0 Или Метаданные.ПланыВидовРасчета.Количество() <> 0 Или Метаданные.ПланыСчетов.Количество() <> 0 Или Метаданные.ПланыОбмена.Количество() <> 0 Или Метаданные.БизнесПроцессы.Количество() <> 0 Или Метаданные.Задачи.Количество() <> 0 Или Метаданные.Последовательности.Количество() <> 0 Или Метаданные.ОбщиеРеквизиты.Количество() <> 0 Или Метаданные.ПодпискиНаСобытия.Количество() <> 0 Или Метаданные.РегламентныеЗадания.Количество() <> 0 Или Метаданные.ВнешниеИсточникиДанных.Количество() <> 0 Или Метаданные.Роли.Количество() <> 1 Тогда
        ВызватьИсключение "Нужен только свой новый минимальный стенд прав";
    КонецЕсли;
    Если Метаданные.ЕстьИзмененияРасширениямиКонфигурации() Или ПривилегированныйРежим() Тогда
        ВызватьИсключение "Изменения расширениями и привилегированный режим запрещены";
    КонецЕсли;
    Источники = Новый Массив;
    Источники.Добавить(ИсточникРасширенийКонфигурации.БазаДанных);
    Источники.Добавить(ИсточникРасширенийКонфигурации.СеансАктивные);
    Источники.Добавить(ИсточникРасширенийКонфигурации.СеансОтключенные);
    Для Каждого Источник Из Источники Цикл
        Расширения = РасширенияКонфигурации.Получить(, Источник);
        Если ТипЗнч(Расширения) <> Тип("Массив") Тогда
            ВызватьИсключение "Нужен массив расширений";
        КонецЕсли;
        Если Расширения.Количество() <> 0 Тогда
            ВызватьИсключение "Свой стенд должен быть без расширений базы и сеанса";
        КонецЕсли;
    КонецЦикла;
    МетаКат = Метаданные.Справочники.Найти("УчебныеЗаблокированные85");
    Если МетаКат = Неопределено Тогда
        ВызватьИсключение "Не найден собственный справочник";
    КонецЕсли;
    Если МетаКат.Иерархический Или МетаКат.Владельцы.Количество() <> 0
        Или МетаКат.ТипКода <> Метаданные.СвойстваОбъектов.ТипКодаСправочника.Строка
        Или МетаКат.ДлинаКода <> 3 Или МетаКат.ДлинаНаименования <> 40 Или МетаКат.Автонумерация
        Или МетаКат.Реквизиты.Количество() <> 0 Или МетаКат.ТабличныеЧасти.Количество() <> 0 Тогда
        ВызватьИсключение "Не совпала собственная схема прав";
    КонецЕсли;
    Имена = МетаКат.ПолучитьИменаПредопределенных();
    Если ТипЗнч(Имена) <> Тип("ФиксированныйМассив") Тогда
        ВызватьИсключение "Нужен фиксированный массив предопределённых имён";
    КонецЕсли;
    Если Имена.Количество() <> 0 Тогда
        ВызватьИсключение "Предопределённые записи запрещены";
    КонецЕсли;
    Свойства = Метаданные.СвойстваОбъектов;
    Если Метаданные.РежимУправленияБлокировкойДанных <> Свойства.РежимУправленияБлокировкойДанныхПоУмолчанию.Автоматический
        Или МетаКат.РежимУправленияБлокировкойДанных <> Свойства.РежимУправленияБлокировкойДанныхПоУмолчанию.Автоматический Тогда
        ВызватьИсключение "Нужны строго автоматические metadata режимы своей конфигурации и каталога";
    КонецЕсли;
КонецПроцедуры

Функция СсылкаИзСвоегоИД(Значение)
    Если ТипЗнч(Значение) <> Тип("Строка") Тогда
        ВызватьИсключение "Передайте строку фактического ИД своих созданных данных";
    КонецЕсли;
    Если СтрДлина(Значение) <> 36 Тогда
        ВызватьИсключение "Нужен полный ИД из собственной квитанции";
    КонецЕсли;
    Идентификатор = Новый УникальныйИдентификатор(Значение);
    Если НРег(Строка(Идентификатор)) <> НРег(Значение) Тогда
        ВызватьИсключение "Не совпал собственный канонический ИД";
    КонецЕсли;
    Ссылка = Справочники.УчебныеЗаблокированные85.ПолучитьСсылку(Идентификатор);
    Если ТипЗнч(Ссылка) <> Тип("СправочникСсылка.УчебныеЗаблокированные85") Тогда
        ВызватьИсключение "Сформирована ссылка не своего типа";
    КонецЕсли;
    Если Ссылка.Пустая() Тогда
        ВызватьИсключение "Пустой ИД запрещён";
    КонецЕсли;
    Возврат Ссылка;
КонецФункции

Функция СобственныеСсылки(ИД001, ИД002, ИД003)
    Ссылки = Новый Массив;
    Ссылки.Добавить(СсылкаИзСвоегоИД(ИД001));
    Ссылки.Добавить(СсылкаИзСвоегоИД(ИД002));
    Ссылки.Добавить(СсылкаИзСвоегоИД(ИД003));
    Если Ссылки[0] = Ссылки[1] Или Ссылки[0] = Ссылки[2] Или Ссылки[1] = Ссылки[2] Тогда
        ВызватьИсключение "Три собственных ИД должны быть различными";
    КонецЕсли;
    Возврат Ссылки;
КонецФункции

Процедура ПроверитьСвойСеанс(ОжидаемоеИмя)
    ПроверитьСвойСтендБлокировок();
    Если ТранзакцияАктивна() Тогда
        ВызватьИсключение "Чужая или вложенная активная транзакция запрещена";
    КонецЕсли;
    Если Метаданные.Роли.Найти("УчебныеБлокировки85") = Неопределено Или Не РольДоступна("УчебныеБлокировки85") Тогда
        ВызватьИсключение "Нужна только своя единственная роль блокировок";
    КонецЕсли;
    Текущий = ПользователиИнформационнойБазы.ТекущийПользователь();
    Если ТипЗнч(Текущий) <> Тип("ПользовательИнформационнойБазы") Тогда
        ВызватьИсключение "Нет текущего собственного пользователя";
    КонецЕсли;
    Если Текущий.Имя <> "УчебныйХранитель85" И Текущий.Имя <> "УчебныйПроверяющий85" Тогда
        ВызватьИсключение "Нужен один из двух своих именованных сеансов";
    КонецЕсли;
    Если ОжидаемоеИмя <> "" И Текущий.Имя <> ОжидаемоеИмя Тогда
        ВызватьИсключение "Операция запущена не в своём нужном сеансе";
    КонецЕсли;
    Для Каждого Право Из СвоиПраваЗаписи() Цикл
        Если Не ПравоДоступа(Право, Метаданные.Справочники.УчебныеЗаблокированные85) Тогда
            ВызватьИсключение "Не хватает собственного права " + Право;
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Функция СвоиПраваЗаписи()
    Права = Новый Массив;
    Права.Добавить("Чтение");
    Права.Добавить("Добавление");
    Права.Добавить("Изменение");
    Права.Добавить("Удаление");
    Возврат Права;
КонецФункции

Функция ВариантСвоейБазы()
    Соединение = СтрокаСоединенияИнформационнойБазы();
    Если ТипЗнч(Соединение) <> Тип("Строка") Тогда
        ВызватьИсключение "Не получена строка регистрации собственной базы";
    КонецЕсли;
    Префикс = Лев(ВРег(Соединение), 5);
    Если Префикс = "FILE=" Тогда
        Возврат "Файловая";
    ИначеЕсли Префикс = "SRVR=" Тогда
        Возврат "КлиентСервер";
    КонецЕсли;
    ВызватьИсключение "Для своего стенда нужна минимальная регистрация, начинающаяся File= либо Srvr=; нестандартную строку не угадываем";
КонецФункции

Функция ПроверитьСвоёКонечноеОжидание()
    Ожидание = ПолучитьВремяОжиданияБлокировкиДанных();
    Если ТипЗнч(Ожидание) <> Тип("Число") Тогда
        ВызватьИсключение "Нужно фактически настроенное числовое ожидание своей базы";
    КонецЕсли;
    Если Ожидание < 1 Или Ожидание > 30 Или Ожидание <> Цел(Ожидание) Тогда
        ВызватьИсключение "Ожидание своей базы не подходит этому короткому опыту: нужны целые1..30секунд, код настройку не меняет";
    КонецЕсли;
    Возврат Ожидание;
КонецФункции

Функция СвоиОжидания(Ссылки)
    Ожидания = Новый Массив;
    Ожидания.Добавить(Новый Структура("Ссылка,Код,Наименование,ПометкаУдаления", Ссылки[0], "001", "Своя первая запись", Ложь));
    Ожидания.Добавить(Новый Структура("Ссылка,Код,Наименование,ПометкаУдаления", Ссылки[1], "002", "Своя вторая запись", Ложь));
    Ожидания.Добавить(Новый Структура("Ссылка,Код,Наименование,ПометкаУдаления", Ссылки[2], "003", "Своя третья запись", Ложь));
    Возврат Ожидания;
КонецФункции

Функция СвояОднаСтрока(ВсеОжидания, Индекс)
    Ожидания = Новый Массив;
    Ожидания.Добавить(ВсеОжидания[Индекс]);
    Возврат Ожидания;
КонецФункции

Процедура ПроверитьСвоиПолныеСтроки(Таблица, Ожидания)
    Если ТипЗнч(Таблица) <> Тип("ТаблицаЗначений") Тогда
        ВызватьИсключение "Нужен реальный полный результат своего запроса";
    КонецЕсли;
    Если Таблица.Количество() <> Ожидания.Количество() Или Таблица.Колонки.Количество() <> 4 Тогда
        ВызватьИсключение "Не совпали полные размеры собственного результата";
    КонецЕсли;
    Поля = Новый Массив;
    Поля.Добавить("Ссылка");
    Поля.Добавить("Код");
    Поля.Добавить("Наименование");
    Поля.Добавить("ПометкаУдаления");
    Для Каждого Поле Из Поля Цикл
        Если Таблица.Колонки.Найти(Поле) = Неопределено Тогда
            ВызватьИсключение "Нет полной собственной колонки " + Поле;
        КонецЕсли;
    КонецЦикла;
    Для Индекс = 0 По Ожидания.Количество() - 1 Цикл
        Факт = Таблица[Индекс];
        Ожидание = Ожидания[Индекс];
        Если ТипЗнч(Факт.Ссылка) <> Тип("СправочникСсылка.УчебныеЗаблокированные85")
            Или ТипЗнч(Факт.Код) <> Тип("Строка") Или ТипЗнч(Факт.Наименование) <> Тип("Строка")
            Или ТипЗнч(Факт.ПометкаУдаления) <> Тип("Булево") Тогда
            ВызватьИсключение "Не совпали типы всех четырёх своих полей";
        КонецЕсли;
        Если Факт.Ссылка <> Ожидание.Ссылка Или Факт.Код <> Ожидание.Код
            Или Факт.Наименование <> Ожидание.Наименование Или Факт.ПометкаУдаления <> Ложь Тогда
            ВызватьИсключение "Не совпали все ссылки, значения и порядок своего результата";
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Функция СоздатьСвоюЗапись(Код, Название)
    СвойОбъект = Справочники.УчебныеЗаблокированные85.СоздатьЭлемент();
    СвойОбъект.Код = Код;
    СвойОбъект.Наименование = Название;
    СвойОбъект.ПометкаУдаления = Ложь;
    СвойОбъект.Записать();
    Если ТипЗнч(СвойОбъект.Ссылка) <> Тип("СправочникСсылка.УчебныеЗаблокированные85") Тогда
        ВызватьИсключение "Своя запись получила ссылку не того типа";
    КонецЕсли;
    Если СвойОбъект.Ссылка.Пустая() Тогда
        ВызватьИсключение "Своя записанная ссылка не должна быть пустой";
    КонецЕсли;
    Возврат СвойОбъект.Ссылка;
КонецФункции

Процедура ПроверитьСвойОбъектПередУдалением(Объект, Ожидание)
    Если Объект = Неопределено Тогда
        ВызватьИсключение "Своя запись отсутствует; удаление остановлено";
    КонецЕсли;
    Если ТипЗнч(Объект) <> Тип("СправочникОбъект.УчебныеЗаблокированные85") Тогда
        ВызватьИсключение "Удаление чужого типа запрещено";
    КонецЕсли;
    Если ТипЗнч(Объект.Код) <> Тип("Строка") Или ТипЗнч(Объект.Наименование) <> Тип("Строка")
        Или ТипЗнч(Объект.ПометкаУдаления) <> Тип("Булево") Тогда
        ВызватьИсключение "Не совпали все типы своих полей перед удалением";
    КонецЕсли;
    Если Объект.Ссылка <> Ожидание.Ссылка Или Объект.Код <> Ожидание.Код
        Или Объект.Наименование <> Ожидание.Наименование Или Объект.ПометкаУдаления <> Ложь Тогда
        ВызватьИсключение "Изменена своя квитанция или содержимое; удаление запрещено";
    КонецЕсли;
КонецПроцедуры
УчебнаяАвтоблокировка85.Форма

Своя новая внешняя обработка УчебнаяАвтоблокировка85 → управляемая Форма → Модуль

Полный отдельный клиентский/серверный модуль формы; три строковых ИД36 и шесть своих команд. Показывает наблюдение, не считает любое исключение блокировкой.

&НаКлиенте
Процедура СоздатьДанные(Команда)
    СвоиИД = СоздатьДанныеНаСервере();
    ИД001 = СвоиИД[0];
    ИД002 = СвоиИД[1];
    ИД003 = СвоиИД[2];
    Сообщить("Собственные три записи созданы; сохраните реальные ИД по кодам");
КонецПроцедуры

&НаСервереБезКонтекста
Функция СоздатьДанныеНаСервере()
    Возврат УчебныйЯзык.СоздатьСвоиДанные();
КонецФункции

&НаКлиенте
Процедура ПроверитьГотовность(Команда)
    ПроверитьГотовностьНаСервере(ИД001, ИД002, ИД003);
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ПроверитьГотовностьНаСервере(Первый, Второй, Третий)
    УчебныйЯзык.ПроверитьГотовность(Первый, Второй, Третий);
КонецПроцедуры

&НаКлиенте
Процедура Выполнить(Команда)
    ВыполнитьНаСервере(ИД001, ИД002, ИД003);
    Сообщить("Собственная транзакция хранителя отменена; блокировка освобождена");
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ВыполнитьНаСервере(Первый, Второй, Третий)
    УчебныйЯзык.ВыполнитьПример(Первый, Второй, Третий);
КонецПроцедуры

&НаКлиенте
Процедура ПроверитьПервую(Команда)
    Итог = ПроверитьНаСервере(ИД001, ИД002, ИД003, Истина);
    ПоказатьНаблюдение(Итог);
КонецПроцедуры

&НаКлиенте
Процедура ПроверитьТретью(Команда)
    Итог = ПроверитьНаСервере(ИД001, ИД002, ИД003, Ложь);
    ПоказатьНаблюдение(Итог);
КонецПроцедуры

&НаСервереБезКонтекста
Функция ПроверитьНаСервере(Первый, Второй, Третий, Первая)
    Возврат УчебныйЯзык.ПроверитьКонкуренцию(Первый, Второй, Третий, Первая);
КонецФункции

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

&НаКлиенте
Процедура УдалитьСвоиДанные(Команда)
    УдалитьСвоиДанныеНаСервере(ИД001, ИД002, ИД003);
    ИД001 = "";
    ИД002 = "";
    ИД003 = "";
    Сообщить("Только свои проверенные три записи удалены; собственный каталог пуст");
КонецПроцедуры

&НаСервереБезКонтекста
Процедура УдалитьСвоиДанныеНаСервере(Первый, Второй, Третий)
    УчебныйЯзык.УдалитьСвоиДанные(Первый, Второй, Третий);
КонецПроцедуры

Разбор по шагам

  1. Фаза подготовки: войти своим УчебныйХранитель85, открыть обработку и нажать СоздатьДанные. Непустой каталог отвергается без удаления. Создаются ровно001/Своя первая запись,002/Своя вторая запись,003/Своя третья запись с Булево Ложь пометки. Все четыре поля/разные собственные ссылки сверяются до и после собственного commit; реальные три UUID возвращаются в поля формы. Диагностические UUID до commit ещё не доказывают фиксацию.

  2. Открыть второй свежий сеанс именно УчебныйПроверяющий85 в ЭТОЙ ЖЕ собственной базе. Передать ему три фактических ИД своей квитанции по кодам, не ID другого стенда. В ОБОИХ сеансах нажать ПроверитьГотовность ДО начала удержания: полный каталог должен иметь ровно три четырёхколоночные строки с теми же ссылками/кодами/названиями/Ложь. Здесь же проверяются автоматические режимы, реальные вариант базы и конечная настройка ожидания. Это не SELECT FOR UPDATE и не создание блокировки.

  3. В собственном серверном общем модуле УчебныйЯзык → ВыполнитьПример поставьте точку останова РОВНО на строке Сообщить("ТОЧКА УДЕРЖАНИЯ: собственная запись001 прочитана ДЛЯ ИЗМЕНЕНИЯ внутри своей транзакции"); Останов должен быть серверным только в сеансе хранителя. Строка расположена после успешного Выполнить/Выгрузить/полного сравнения записи001, но до ОтменитьТранзакцию. Ничего не дописывайте в код и не используйте Сон/Приостановить/бесконечный цикл.

  4. В сеансе хранителя нажать Выполнить и дождаться указанного останова. Запрос использует только свой справочник, параметр СвояСсылка=своя001 и голое ДЛЯ ИЗМЕНЕНИЯ после ГДЕ: дополнительный OF/список таблиц здесь не нужен, потому что источник единственный. НачатьТранзакцию() без параметра допустим из-за строгого metadata Автоматический; вложенная/чужая транзакция заранее запрещена.

  5. В остановленном сеансе проверить ТранзакцияАктивна()=Булево Истина и фактический результат001: своя точная ссылка, Строка001, полное собственное название, Булево Ложь. Код не записывает эту строку: нужны именно блокировки читающего FOR UPDATE. Не продолжать хранителя до завершения двух отдельных конкурирующих наблюдений ниже.

  6. Проверяющий нажимает ПроверитьПервую: его собственная транзакция конкурирует тем же FOR UPDATE по своей001. Guard/парсинг квитанции НЕ читают каталог; полный preflight уже был ДО удержания. Внутренний catch охватывает только Запрос.Выполнить, не проверку состава. После любого наблюдения своя транзакция проверяющего отменяется и проверяется отсутствие активной транзакции. Код фиксирует диагностику, но не объявляет любую ошибку доказанным таймаутом.

  7. Пока хранитель всё ещё удерживает001, проверяющий отдельно нажимает ПроверитьТретью. Для файлового варианта native описывает блокировку таблицы: этот запрос также должен ожидать освобождения/дать соответствующую диагностику. Для клиент-серверного варианта native описывает только выбранные записи: контроль003 должен реально вернуть ровно свою полную четырёхколоночную строку и завершить свою транзакцию. Не путайте контроль с обычным MVCC-чтением: обе конкурирующие операции используют FOR UPDATE.

  8. Зафиксировать обе реальные наблюдаемые диагностики/успехи и состояние именно остановленного хранителя. Неправильные права/ошибки компиляции/использования сразу отклоняются. Другая ошибка ещё не равна блокировке: вручную сверить её описание/код/причины, фактическую конечную настройку ожидания и контекст. Не invent фиксированное число миллисекунд или универсальный код таймаута всех СУБД.

  9. Освобождение: в отладчике ПРОДОЛЖИТЬ хранителя с той же точки, чтобы код выполнил свою ОтменитьТранзакцию, проверил её завершение и весь неизменённый состав3строк. Не обрывать процесс/сеанс и не делать F7 вместо отмены. После успешного возврата формы снять точку останова. Теперь проверяющий повторяет ПроверитьПервую и ПроверитьТретью: оба запроса должны реально вернуть свои001/003 и закончить свои транзакции. Только вместе с предыдущим контекстом это подтверждает различие удержания и освобождения.

  10. Контроль сохранности: после освобождения в обоих сеансах снова ПроверитьГотовность — те же3полные собственные строки, без записи/изменения. Полный каталог НЕ читается в конкурирующей фазе при уже удерживаемой файловой блокировке: иначе такой readback мог бы заблокироваться раньше исследуемого FOR UPDATE и испортить объяснение.

  11. После всех наблюдений завершить второй свой сеанс и убедиться, что хранитель не остановлен в транзакции. В первом сеансе УдалитьСвоиДанные использует только ту же собственную квитанцию. Сверяются весь каталог ровно3строки и ВСЕ3реальные объекты до первого удаления; очистка только своего нового неиерархического стенда, с rollback на ошибке, проверкой пустоты до/после commit и отсутствия активной транзакции. Неверный ИД/четвёртая строка/изменённый объект запрещают удаление.

Как проверить результат

  1. До удержания: оба сеанса проходят ПроверитьГотовность, получают одинаковый фактический вариант и настройку ожидания, ровно три полные строки с теми же своими GUID. Обычные запросы здесь только для preflight/сохранности, а не доказательство отсутствия всех MVCC-эффектов.

  2. При серверном останове: хранитель внутри своей транзакции уже прочёл001 FOR UPDATE и полностью сверил данные. Проверяющий по001 не должен получить немедленное успешное приобретение при фактически удерживаемой блокировке. При файловом варианте проверка003 также конфликтует с таблицей; при серверном варианте контроль003 должен успешно прочитать свою запись. Если этот протокол не выполняется, опыт не подтверждён: проверить настройки/отладчик/реальный вариант и причины, не писать об успехе.

  3. После продолжения хранителя и его rollback: ТранзакцияАктивна()=Ложь, состав3строк неизменён; оба повторных FOR UPDATE проверяющего по001 и003 успешно возвращают полные реальные строки и сами отменяются. Свободная строка003 и успешный повтор001 отделяют действительную конкуренцию от посторонней постоянной ошибки запроса/схемы.

  4. Контроль неверного режима: в отдельной подготовке вне транзакций/сеансов изменить режим только своего каталога на Управляемый, сохранить/F7, свежие сеансы. Guard должен отказать ДО опыта. Восстановить Автоматический, опять F7 до свежих сеансов; никогда не присваивать readonly metadata свойству из BSL.

  5. Контроль квитанции: поменять местами своиИД001/ИД003 либо передать пустую/чужую диагностическую строку. Парсинг/непустота/полный preflight должны отказать, данные не создавать/не удалять. Для реального опыта вернуть точные собственные ИД; ПолучитьСсылку не доказывает существование записи.

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

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

Ожидаемый результат

Будущий ручной опыт проверяет реальную конкуренцию и освобождение FOR UPDATE по собственным данным для двух вариантов базы. Статические тесты и Python-модели не создают блокировки и не являются runtime доказательством.

На что обратить внимание

  1. В файловом варианте native описывает блокировку таблиц, в клиент-серверном — выбранных записей. Не расширяйте вывод на произвольные запросы/соединения/обычное MVCC-чтение/эскалации конкретной СУБД. Не считать любую сетевую/хранимую ошибку таймаутом: реальное наблюдение требует данного контекста и успешного повторного приобретения после освобождения.

  2. Режим metadata свойства РежимУправленияБлокировкойДанныхПоУмолчанию отличается от системного РежимУправленияБлокировкойДанных параметра НачатьТранзакцию. Metadata Автоматический у конфигурации делает все транзакции автоматическими; режим смешанный/управляемый здесь намеренно отклоняется, не меняется во время опыта.

  3. Setter ожидания работает с общей настройкой информационной базы, требует администратора/временного монопольного доступа и имеет особенности IBM DB2. Он не используется ни для проверяющего, ни для хранителя. Число20 в его default-аргументе не доказывает реальное текущее ожидание. Наш policy1..30 не гарантирует такую же длительность сетевого сбоя/остановленного всего сервера.

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

  5. Точка останова обязательна для удержания: без неё собственная транзакция быстро отменяется и проверяющий может получить успех. Приостановка всего сервера вместо одного сеанса может лишить проверяющего возможности работать: это не доказательство строки/таблицы. Не делать F7 и не редактировать код внутри удержания.

  6. Create/cleanup фиксируют только свои3данных, чтобы два сеанса работали с реальными записями. Удалить непосредственно удаляет без восстановления/проверки ссылок; для иерархии также подчинённые. Наш каталог неиерархический, без других прикладных объектов/владельцев/ссылочных реквизитов; все3строки/объекты заранее сверяются. Не применять к чужой базе или частично известной квитанции.

  7. Флаги модулей/пустота BSL/RLS и работа отладчика — ручные обязательные условия, не доказанные одним metadata guard. После сбоя commit/rollback/послекоммитной проверки состояние может быть неопределено; очистку/освобождение не утверждать и не повторять удаление вслепую. Код и право доступа в рабочих конфигурациях не менялись; runtimeVerifiedScenarios=0.

Пример не запускался в вашей базе. Перед применением проверьте его в указанном учебном контексте.

Не хотите разбираться сами?

Адаптируем это решение под вашу базу. Оценка бесплатно.

Оставить задачу

1С, 1С:Предприятие — товарные знаки ООО «1С». Сайт не является официальным представительством фирмы «1С» и не аффилирован с ней.