Новый собственный стенд, три настоящих записи и два именованных сеанса. Хранитель читает 001 ДЛЯ ИЗМЕНЕНИЯ в своей транзакции, проверяющий сравнивает ту же запись и контрольную003. После отмены хранителя оба запроса должны снова прочитать свои полные строки; ошибки сами по себе не выдаются за доказательство блокировки.
Где и как запускать
Полный серверный общий модуль УчебныйЯзык только собственного нового стенда УчебныеАвтоблокировки85. Полные точки создания/preflight/удержания/конкуренции/очистки и полный отдельный модуль формы. Запуск в 1С не проверялся.
Подготовка
Создайте НОВУЮ отдельную пустую учебную базу, не копию рабочей, на точной 8.5.1.1343. Имя конфигурации УчебныеАвтоблокировки85; совместимость Не использовать; основной режим запуска Управляемое приложение, толстый клиент. Нет расширений базы/сеанса, других прикладных метаданных, общих реквизитов, подписок/заданий, внешних источников. По очереди можно повторить опыт на отдельном файловом стенде и отдельном клиент-серверном; их данные/квитанции не смешивать.
Свойства конфигурации → Режим управления блокировкой данных=Автоматический. Справочники → УчебныеЗаблокированные85: Режим управления блокировкой данных=Автоматический; Иерархический=Нет; владельцы пусты; Код только Строка длина3, Автонумерация=Нет; Наименование длина40; собственных реквизитов/табличных частей/предопределённых нет. Стандартные поля остаются. Guard читает ОБА metadata свойства; сравнивает с Метаданные.СвойстваОбъектов.РежимУправленияБлокировкойДанныхПоУмолчанию.Автоматический. Это не системный enum параметра НачатьТранзакцию, и readonly-свойствам ничего не присваивается.
Общие → Общие модули → УчебныйЯзык: Сервер=Истина, Глобальный=Ложь, ВызовСервера=Ложь, Привилегированный=Ложь; клиентские/внешнее соединение отключены; повторное использование выключено. Вставьте ВЕСЬ основной модуль с helpers на уровне модуля. Все другие объектные/менеджерские модули, формы справочника, модули приложения/сеанса и обработчики вручную полностью пусты: guard не доказывает это через native МодульОбъекта/МодульМенеджера.
Создайте ровно одну роль УчебныеБлокировки85. На конфигурацию дайте ТолстыйКлиент, РежимОсновногоОкнаОбычный, ИнтерактивноеОткрытиеВнешнихОбработок и только для нового своего стенда Администрирование/АдминистрированиеДанных; на свой каталог Чтение, Просмотр, Добавление, Изменение, Удаление без RLS. В Конфигураторе → Администрирование → Пользователи создайте УчебныйХранитель85 и УчебныйПроверяющий85 только с этой ролью. Свои пароли задайте вручную, рабочие учётные записи не используйте. Привилегированный режим в коде не включается, Текущий.Роли не читается.
В списке запуска зарегистрируйте свой файловый стенд минимальной строкой, начинающейся File=, или свой серверный строкой, начинающейся Srvr=. Возвращаемая СтрокаСоединенияИнформационнойБазы не обязана быть нормализована: helper проверяет только строгий начальный префикс этого нового стенда и отказывается от нестандартного представления, а не ищет File= внутри чужих значений. Полная строка соединения/пароли не выводятся. Не подменяйте реальный вариант ручным Булево.
До опыта прочитайте фактическую настройку времени ожидания своей учебной базы. Для короткого опыта нужны целые1..30секунд; guard читает ПолучитьВремяОжиданияБлокировкиДанных и отказывает при неподходящем значении. Код НИКОГДА не вызывает УстановитьВремяОжиданияБлокировкиДанных: это административная настройка всей базы с временным монопольным доступом, не локальный таймаут сеанса. Оmitted-argument20секунд setter не доказывает текущую настройку новой базы. Не меняйте рабочую базу; если свой стенд не подходит, сначала разберите отдельную настройку вне всех опытов.
Создайте свою новую внешнюю обработку УчебнаяАвтоблокировка85, управляемую Форма. Реквизиты ИД001, ИД002, ИД003 — только Строка длина36, три поля ввода. Команды/кнопки СоздатьДанные, ПроверитьГотовность, Выполнить, ПроверитьПервую, ПроверитьТретью, УдалитьСвоиДанные; их действия — одноимённые процедуры полного отдельного модуля формы. Ссылки восстанавливаются только из реальных ИД, возвращённых собственным созданием; нет готовых демонстрационных GUID.
После ВСЕХ метаданных/флагов/ролей/пользователей и обоих полных модулей сохраните свою конфигурацию. Предыдущие учебные сеансы завершены, активной транзакции/опыта нет. Выполните Конфигурация → Обновить конфигурацию базы данных (F7), подтвердите только свою новую конфигурацию и дождитесь успешного обновления. Затем только свежие явно именованные сеансы. Никаких F7/изменений конфигурации при остановленном серверном коде внутри активной транзакции.
Настройте серверную отладку только своего стенда и возможность приостанавливать выбранный сеанс хранителя, не все сеансы/сервер целиком. Это будущая ручная работа в Конфигураторе/отладчике. Здесь Конфигуратор, 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 = "";
Сообщить("Только свои проверенные три записи удалены; собственный каталог пуст");
КонецПроцедуры
&НаСервереБезКонтекста
Процедура УдалитьСвоиДанныеНаСервере(Первый, Второй, Третий)
УчебныйЯзык.УдалитьСвоиДанные(Первый, Второй, Третий);
КонецПроцедурыРазбор по шагам
Фаза подготовки: войти своим УчебныйХранитель85, открыть обработку и нажать СоздатьДанные. Непустой каталог отвергается без удаления. Создаются ровно001/Своя первая запись,002/Своя вторая запись,003/Своя третья запись с Булево Ложь пометки. Все четыре поля/разные собственные ссылки сверяются до и после собственного commit; реальные три UUID возвращаются в поля формы. Диагностические UUID до commit ещё не доказывают фиксацию.
Открыть второй свежий сеанс именно УчебныйПроверяющий85 в ЭТОЙ ЖЕ собственной базе. Передать ему три фактических ИД своей квитанции по кодам, не ID другого стенда. В ОБОИХ сеансах нажать ПроверитьГотовность ДО начала удержания: полный каталог должен иметь ровно три четырёхколоночные строки с теми же ссылками/кодами/названиями/Ложь. Здесь же проверяются автоматические режимы, реальные вариант базы и конечная настройка ожидания. Это не SELECT FOR UPDATE и не создание блокировки.
В собственном серверном общем модуле УчебныйЯзык → ВыполнитьПример поставьте точку останова РОВНО на строке Сообщить("ТОЧКА УДЕРЖАНИЯ: собственная запись001 прочитана ДЛЯ ИЗМЕНЕНИЯ внутри своей транзакции"); Останов должен быть серверным только в сеансе хранителя. Строка расположена после успешного Выполнить/Выгрузить/полного сравнения записи001, но до ОтменитьТранзакцию. Ничего не дописывайте в код и не используйте Сон/Приостановить/бесконечный цикл.
В сеансе хранителя нажать Выполнить и дождаться указанного останова. Запрос использует только свой справочник, параметр СвояСсылка=своя001 и голое ДЛЯ ИЗМЕНЕНИЯ после ГДЕ: дополнительный OF/список таблиц здесь не нужен, потому что источник единственный. НачатьТранзакцию() без параметра допустим из-за строгого metadata Автоматический; вложенная/чужая транзакция заранее запрещена.
В остановленном сеансе проверить ТранзакцияАктивна()=Булево Истина и фактический результат001: своя точная ссылка, Строка001, полное собственное название, Булево Ложь. Код не записывает эту строку: нужны именно блокировки читающего FOR UPDATE. Не продолжать хранителя до завершения двух отдельных конкурирующих наблюдений ниже.
Проверяющий нажимает ПроверитьПервую: его собственная транзакция конкурирует тем же FOR UPDATE по своей001. Guard/парсинг квитанции НЕ читают каталог; полный preflight уже был ДО удержания. Внутренний catch охватывает только Запрос.Выполнить, не проверку состава. После любого наблюдения своя транзакция проверяющего отменяется и проверяется отсутствие активной транзакции. Код фиксирует диагностику, но не объявляет любую ошибку доказанным таймаутом.
Пока хранитель всё ещё удерживает001, проверяющий отдельно нажимает ПроверитьТретью. Для файлового варианта native описывает блокировку таблицы: этот запрос также должен ожидать освобождения/дать соответствующую диагностику. Для клиент-серверного варианта native описывает только выбранные записи: контроль003 должен реально вернуть ровно свою полную четырёхколоночную строку и завершить свою транзакцию. Не путайте контроль с обычным MVCC-чтением: обе конкурирующие операции используют FOR UPDATE.
Зафиксировать обе реальные наблюдаемые диагностики/успехи и состояние именно остановленного хранителя. Неправильные права/ошибки компиляции/использования сразу отклоняются. Другая ошибка ещё не равна блокировке: вручную сверить её описание/код/причины, фактическую конечную настройку ожидания и контекст. Не invent фиксированное число миллисекунд или универсальный код таймаута всех СУБД.
Освобождение: в отладчике ПРОДОЛЖИТЬ хранителя с той же точки, чтобы код выполнил свою ОтменитьТранзакцию, проверил её завершение и весь неизменённый состав3строк. Не обрывать процесс/сеанс и не делать F7 вместо отмены. После успешного возврата формы снять точку останова. Теперь проверяющий повторяет ПроверитьПервую и ПроверитьТретью: оба запроса должны реально вернуть свои001/003 и закончить свои транзакции. Только вместе с предыдущим контекстом это подтверждает различие удержания и освобождения.
Контроль сохранности: после освобождения в обоих сеансах снова ПроверитьГотовность — те же3полные собственные строки, без записи/изменения. Полный каталог НЕ читается в конкурирующей фазе при уже удерживаемой файловой блокировке: иначе такой readback мог бы заблокироваться раньше исследуемого FOR UPDATE и испортить объяснение.
После всех наблюдений завершить второй свой сеанс и убедиться, что хранитель не остановлен в транзакции. В первом сеансе УдалитьСвоиДанные использует только ту же собственную квитанцию. Сверяются весь каталог ровно3строки и ВСЕ3реальные объекты до первого удаления; очистка только своего нового неиерархического стенда, с rollback на ошибке, проверкой пустоты до/после commit и отсутствия активной транзакции. Неверный ИД/четвёртая строка/изменённый объект запрещают удаление.
Как проверить результат
До удержания: оба сеанса проходят ПроверитьГотовность, получают одинаковый фактический вариант и настройку ожидания, ровно три полные строки с теми же своими GUID. Обычные запросы здесь только для preflight/сохранности, а не доказательство отсутствия всех MVCC-эффектов.
При серверном останове: хранитель внутри своей транзакции уже прочёл001 FOR UPDATE и полностью сверил данные. Проверяющий по001 не должен получить немедленное успешное приобретение при фактически удерживаемой блокировке. При файловом варианте проверка003 также конфликтует с таблицей; при серверном варианте контроль003 должен успешно прочитать свою запись. Если этот протокол не выполняется, опыт не подтверждён: проверить настройки/отладчик/реальный вариант и причины, не писать об успехе.
После продолжения хранителя и его rollback: ТранзакцияАктивна()=Ложь, состав3строк неизменён; оба повторных FOR UPDATE проверяющего по001 и003 успешно возвращают полные реальные строки и сами отменяются. Свободная строка003 и успешный повтор001 отделяют действительную конкуренцию от посторонней постоянной ошибки запроса/схемы.
Контроль неверного режима: в отдельной подготовке вне транзакций/сеансов изменить режим только своего каталога на Управляемый, сохранить/F7, свежие сеансы. Guard должен отказать ДО опыта. Восстановить Автоматический, опять F7 до свежих сеансов; никогда не присваивать readonly metadata свойству из BSL.
Контроль квитанции: поменять местами своиИД001/ИД003 либо передать пустую/чужую диагностическую строку. Парсинг/непустота/полный preflight должны отказать, данные не создавать/не удалять. Для реального опыта вернуть точные собственные ИД; ПолучитьСсылку не доказывает существование записи.
Контроль неподходящего ожидания/регистрации: код не меняет таймаут и не угадывает нестандартный префикс. Отказ до собственной транзакции — ошибка подготовки этого стенда, не наблюдение блокировки. Не менять общую рабочую базу ради опыта.
Если отмена/фиксация/послекоммитная проверка или связь сами дали ошибку, освобождение/очистка не доказаны, вторичная ошибка может заменить исходную. Не использовать положительные сообщения формы как обход этой проверки. После нештатного прекращения остановленного хранителя отдельно проверить свой сеанс/транзакции перед продолжением; здесь никто из них не запускался.
Ожидаемый результат
Будущий ручной опыт проверяет реальную конкуренцию и освобождение FOR UPDATE по собственным данным для двух вариантов базы. Статические тесты и Python-модели не создают блокировки и не являются runtime доказательством.
На что обратить внимание
В файловом варианте native описывает блокировку таблиц, в клиент-серверном — выбранных записей. Не расширяйте вывод на произвольные запросы/соединения/обычное MVCC-чтение/эскалации конкретной СУБД. Не считать любую сетевую/хранимую ошибку таймаутом: реальное наблюдение требует данного контекста и успешного повторного приобретения после освобождения.
Режим metadata свойства РежимУправленияБлокировкойДанныхПоУмолчанию отличается от системного РежимУправленияБлокировкойДанных параметра НачатьТранзакцию. Metadata Автоматический у конфигурации делает все транзакции автоматическими; режим смешанный/управляемый здесь намеренно отклоняется, не меняется во время опыта.
Setter ожидания работает с общей настройкой информационной базы, требует администратора/временного монопольного доступа и имеет особенности IBM DB2. Он не используется ни для проверяющего, ни для хранителя. Число20 в его default-аргументе не доказывает реальное текущее ожидание. Наш policy1..30 не гарантирует такую же длительность сетевого сбоя/остановленного всего сервера.
Строка соединения возвращается как зарегистрирована, не обязана начинаться выбранным префиксом в любых приложениях. Этот учебный helper принимает только собственную минимальную регистрацию, и честно отказывает иначе; не универсальный парсер и не раскрывает всю строку/секреты.
Точка останова обязательна для удержания: без неё собственная транзакция быстро отменяется и проверяющий может получить успех. Приостановка всего сервера вместо одного сеанса может лишить проверяющего возможности работать: это не доказательство строки/таблицы. Не делать F7 и не редактировать код внутри удержания.
Create/cleanup фиксируют только свои3данных, чтобы два сеанса работали с реальными записями. Удалить непосредственно удаляет без восстановления/проверки ссылок; для иерархии также подчинённые. Наш каталог неиерархический, без других прикладных объектов/владельцев/ссылочных реквизитов; все3строки/объекты заранее сверяются. Не применять к чужой базе или частично известной квитанции.
Флаги модулей/пустота BSL/RLS и работа отладчика — ручные обязательные условия, не доказанные одним metadata guard. После сбоя commit/rollback/послекоммитной проверки состояние может быть неопределено; очистку/освобождение не утверждать и не повторять удаление вслепую. Код и право доступа в рабочих конфигурациях не менялись; runtimeVerifiedScenarios=0.
Пример не запускался в вашей базе. Перед применением проверьте его в указанном учебном контексте.