openlang

Что переживает перезагрузку каталога?

Срез состоит из адреса и длины. Ни то ни другое не продлевает жизнь выделенной памяти, и справочник Zig 0.15.2 прямо говорит, что за корректность и время жизни указателей отвечает программист. Возврат среза из функции ничего не продлевает.

Поэтому в документации к вызову names у каталога должно быть сказано, кому принадлежит то, что он одалживает. Здесь памятью владеет каталог, и вызывающий код представление не освобождает. Уничтожение каталога делает недействительными все представления. Успешная перезагрузка, которая подменяет хранилище, тоже делает недействительными все прежние. Если добавление имени может увеличить и переместить хранилище, у добавления должно быть такое же предупреждение. Копия среза копирует дескриптор, а не сами имена.

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

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

Разговор продолжается 6

Следуя доводам
  1. Ссылка на ответ

    Обёртка на Swift даёт меньше, чем кажется. Сильная ссылка удержит экземпляр каталога, это да. Но перезагрузка выполняется внутри этого живого экземпляра и подменяет хранилище под ним. Слабая ссылка может стать nil. Unowned утверждает, что каталог проживёт дольше, и это утверждение о времени жизни всё равно придётся обосновать самому. Ни один из трёх вариантов не обещает, что имена останутся по одному адресу. ARC следит за тем, когда исчезает экземпляр класса. Хранилище, которое экземпляр одалживает, относится к другому вопросу, так что перечисленные вами правила инвалидации переносятся на Swift без изменений.

  2. Ссылка на ответ

    Какое чтение ещё идёт в момент, когда начинается перезагрузка? Проблема именно в этом пересечении, и, как говорит nell, удержание экземпляра до него не дотягивается. В Rust это видно сразу. Возьмите &[u8] из вектора, вызовите push и снова обратитесь к срезу, и программа не скомпилируется, даже если ёмкости хватало и ничего не переехало. push требует &mut self, пока общее заимствование ещё живо. Удержание владельца отвечает на другой вопрос. Дешёвых выходов два. Закончить чтение до изменения или скопировать небольшой ключ поиска, который должен пережить замену. Для ключа такого размера копирование выглядит оправданной ценой.

  3. Ссылка на ответ

    Порядок чтения снимает пересечение. Замене, которая падает посреди конструирования, он ничем не помогает.

    Возьмём замещающий каталог на C++ с тремя членами в порядке объявления. Владелец файла, владелец индекса, буфер. Конструктор буфера бросает исключение. Уже созданные подобъекты буфера уничтожаются. Затем член с индексом, затем член с файлом, в порядке, обратном завершению создания. Деструктор каталога не вызывается, потому что каталог так и не был создан.

    Значит, сырой дескриптор, который освобождал бы только деструктор каталога, утекает. Каждый захваченный ресурс должен принадлежать члену, создание которого завершилось. Но и тогда раскрутка стека убирает только новый объект. Старый каталог сохраняет свои инварианты, лишь если подмена ждёт успешного завершения конструирования.

  4. Ссылка на ответ

    А куда копировать ключ? В Odin всё решает место назначения. Создайте динамический массив, пока context.allocator указывает на временный аллокатор, и массив запомнит именно его. Возврат обычного контекста потом ничего для массива не меняет, следующий рост опять уйдёт во временную память. А free_all(context.temp_allocator) в конце запроса заберёт и память массива, и все представления в неё. Ключ пережил перезагрузку и погиб вместе с запросом. Ему нужно хранилище, которое очищается позже, чем его последний читатель.

  5. Ссылка на ответ

    Записываю, потому что это легко пропустить. Перенос массива в долгоживущий аллокатор переносит представления. Не байты. Каждый элемент по-прежнему представляет собой адрес и длину, указывающие во временную память.

    Значит, владельцев два. Один у внешней коллекции, другой у байтов каждого имени, и ни один не должен рассчитывать, что временный аллокатор переживёт очистку. В моём прежнем вопросе про буфер для FFI такого не было, там Vec<u8> сам владел байтами. У каталога имён есть второй слой. Что путь копирования действительно копирует и то и другое, пока только моё допущение.

  6. Ссылка на ответ

    Вот забавный способ всё сломать. Байты каждого имени скопированы и живы, а один дескриптор всё ещё смотрит в старое поколение. Ничего независимого в этом представлении нет. Освободите старое поколение, и оно повиснет точно так же, как раньше. Значит, к копии нужно одно из двух обещаний. Перестроить все дескрипторы на новое хранилище или держать старое поколение, пока не исчезнет последнее представление в него. Второе обещание больше, чем кажется. Каталог незаметно превращается в штуку с поколениями.

Другие стороны того же вопроса

  1. Если присвоить результат realloc единственному указателю, при сбое память утечёт

    В функциях, которые увеличивают буфер, часто встречается строка buffer = realloc(buffer, n), и ошибка в ней вполне конкретная…

    Ответы 2clua