ermouth: (ang)
[personal profile] ermouth

У меня в CloudWall до сих пор используется патченная PouchDB версии 3.3.1 – а актуальная сейчас уже 5.3.

Причин тому несколько, и главная – я когда смотрел на предыдущую версию внимательно в рамках давешней истории, был местами неприятно удивлён подходами и кое-что прикрыл своими руками. У меня сейчас просто нет мотива проделывать то же самое с новой версией, а второй раз не глядя внутрь я не рискну.

И вчера в этой патченной версии, что я использую, нашёлся один… даже не знаю, баг это или что.

В PouchDB 3.x вкручен не рекурсивный, а итеративный deep cloner объектов. Итеративный, в отличие от рекурсивного, ограничен не глубиной стека вызовов функций, а объёмом доступной памяти вообще, то-есть может обрабатывать объекты очень большой вложенности.

На практике объект вложенностью в 10-100К уровней в глубину представить сложно. Тем не менее, в процессе репликации системные объекты, контролирующие деревья ревизий, при некоторых необычных, но всё же реалистичных обстоятельствах, такой глубины достигать вполне могут. То-есть, оправдан подход.

Итеративному клонеру надо по пути куда-то данные складывать, так вот в Pouch для этого был применён искусственный стек, а вообще-то нужна очередь. Использование стека приводит к тому, что в клоне все ключи в объектах идут в обратном порядке. Всё ещё сложнее, см UPD в конце.

То-есть {b:{c:{}, d:4}, a:1} превращается в {a:1, b:{d:4, c:{}}}.

Формально это не баг. Так как в JS упорядочивание вхождений в словаре (простой Object) не определено, никто и не обязывает их сериализовать в каком-то чётком порядке.

Тем не менее, существует достаточное количество систем, которые полагаются на то, что порядок при сериализации и передаче не меняется. Поэтому все без исключения JS-машины, что я встречал, порядок помнят и сериализуют в том же порядке, в каком узлы включались в объект. (Есть особые случаи, когда порядок ожидаемо разрушается, но про них в другой раз).

Интересно, что до вчерашнего дня я полагал, что jQuery.my как раз система, вообще говоря зависящая от порядка. Как минимум я думал, что сам пишу такие приложения, которые могут не работать при изменении порядка задания привязок контролов к данным.

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

А первое приложение, которое себя повело странно, я написал только на днях, ColorPicker вот домашнего розлива запилил.

Cd9W6r8WEAAaf6J

Странность поведения в том, что иногда из-за ошибок округления неподвижная точка у функции зависит от того, с какой стороны к ней подходить. При конвертации rgb-hsb-rgb и при hsb-rgb-hsb могут получиться разные результаты, если по пути округлять.

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

Думал, маленький баг минут на 15 – а оно вон как обернулось )

Как вот такое называется? Изменение порядка узлов если спецификация не обязывает его сохранять (но все предпочитают сохранять) – это баг? Или это что?

UPD: Баг хоть и починился, но при размышлении после мне подумалось, что патч не во всех случаях должен работать – а он внезапно работает там, где работать не должен. Что-то тут ещё.

UPD2. Копание вглубь выявило ещё один любопытный баг в продолжение изменённого порядка узлов. В обратном порядке клонировались не просто все узлы, а только те, на которых висит не primitive-значение.

Мало того, при клонировании сначала шли все дети с примитивными значениями, а потом дети-объекты, но в обратном порядке ключей. То-есть дети объекты всегда оказывались в конце.

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

6.5 часов поиска логической ошибки в одну строчку, ой вэй (

Date: 2016-03-21 02:42 am (UTC)
From: [identity profile] morfizm.livejournal.com
Это reliance on undefined behavior. На мой взгляд это баг.

Вот здесь пишут, что это такая разновидность of "software weaknesses":
https://cwe.mitre.org/data/definitions/758.html

"The software uses an API function, data structure, or other entity in a way that relies on properties that are not always guaranteed to hold for that entity."

Date: 2016-03-21 02:43 am (UTC)
From: [identity profile] morfizm.livejournal.com
Если делать это вдумчиво, понимая последствия, а ещё лучше - аккуратно тестируя на то, что данный эффект в данной имплементации всегда holds - то можно назвать не багом, а "хаком".

Date: 2016-03-21 06:14 am (UTC)
From: [identity profile] morfizm.livejournal.com
Кстати, родственная группа проблем - это portability issue, но я бы сказал, что это относится к случаям, когда ты точно знаешь, что именно вот на этой системе некоторый undefined behavior зарезолвлен вот так, а на другие системы тебе плевать. Твой случай не такой, т.к. ты точно не знал, просто наблюдал, что обычно было вот так, без даже специфичных для конкретной платформы гарантий.

Date: 2016-03-21 01:40 pm (UTC)
From: [identity profile] ermouth.livejournal.com
Если говорить про порядок ключей – я точно знаю, когда общепринятый порядок может нарушаться и в каком браузере (угадай с одного раза, да). И даже в этом прекрасном браузере надо сделать набор довольно необычных действий – удалить ключ, а потом снова присвоить, и он окажется не в конце списка, а где был.

Также порядок нарушается, если некоторые ключи – целые от 0 до 2^32-2 или их строковые представления (ключи всегда кастятся в строки). Такие ключи при обходе или сериализации пойдут в начале списка и будут отсортированы как _числа_ (хотя они и строки) по возрастанию. Остальные ключи пойдут в том порядке, как присваивались. Это поведение общее для всех известных мне JS систем.

Я даже знаю почему именно таково положение дел. Так что когда я придумывал $.my, я как раз взял за основу положение дел – потому что это круто упрощало синтаксис.

Но не ждешь же, что какая-то бибилиотека при сериализации начнёт использовать pop() вместо shift() – в JS массив работает как deque, никаких специальных усилий прикладывать не надо даже чтоб иметь FIFO.

Date: 2016-03-21 06:10 pm (UTC)
From: [identity profile] morfizm.livejournal.com
IE? :)

Мне кажется, тут более фундаментальный flaw, если Javascript даёт queue-like API для dict (т.е. {}). Его не должно быть, тогда люди не стреляли бы себе в ногу и использовали бы списки для задач, где важен порядок (т.е. []).

Date: 2016-03-21 06:26 pm (UTC)
From: [identity profile] ermouth.livejournal.com
> тут более фундаментальный flaw, если Javascript даёт queue-like API для dict

Тут историческое наследие. Изначально в JS нативной сериализации не было – JSON был придуман Дугласом Крокфордом в 2001, через 6 лет после появления JS.

Должен сказать, это была гениальная по простоте и красоте идея – сериализовать объекты в нативный JS-синтаксис. До этого повсеместно рулил XML – совершенный, но избыточный и дико неудобный.

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

Date: 2016-03-22 05:07 am (UTC)
From: [identity profile] tonsky.livejournal.com
Слушай, ну ужас как раз в приложениях, которые на порядок полей рассчитывают. Это называется «бомба замедленного действия», и когда она рванет, никому смешно не будет. Отладка минимум на день, потеря веры в человечество, etc.

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

Если порядок важен — нужно явно его хранить. Тогда, когда читаешь код, будет очевидно, что вот тут его явно сохраняют, а не полагаются на хитрые свойства платформ. Просто разбираться в коде легче, когда все явное.

Date: 2016-03-22 10:42 am (UTC)
From: [identity profile] ermouth.livejournal.com
> Слушай, ну ужас как раз в приложениях, которые на порядок полей рассчитывают.

Ужас, конечно, но я некорректно выразился. «Вообще говоря» не значит «всегда зависит».

Нужно очень определённое стечение обстоятельств, чтобы оно заглючило, и это стечение обстоятельств не внутри $.my, а в самом коде.

$.my гарантированно обойдёт и попытается согласовать все узлы, и всё равно в каком они порядке – связи то между ними ты явно указываешь.

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

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

> Если порядок важен — нужно явно его хранить.

Иногда важен – как в случае с колорпикером, когда за счёт порядка я пытался сэкономить себе кучу писанины. Когда-то в самом начале в $.my был вариант синтаксиса, поддерживающий строгий порядок – и я его выпилил за ненадобностью. Верну пожалуй.

Date: 2016-03-22 12:27 pm (UTC)
From: [identity profile] ermouth.livejournal.com
> Иногда такие штуки это даже хорошо — специальное перепутывание порядка

Мне подумалось, что это прекрасная идея – добавить в отладчик что-то типа галки Shuffle nodes для тестирования. Спасибо за наводку.

Date: 2016-03-22 12:51 pm (UTC)
From: [identity profile] tonsky.livejournal.com
Я всем рассказываю эту историю, вот и ты подвернулся под руку :) (Вроде бы) у фейсбука (вроде бы) был момент когда они с вероятностью 1/500 на любой API-запрос выдавали ошибку. Просто чтобы клиенты API готовились к тому, что API может сломаться, сразу, а не потом. И это мне кажется единственно возможный способ.

Profile

ermouth: (Default)
ermouth

November 2021

S M T W T F S
 123456
78910111213
14151617181920
21 222324252627
282930    

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Feb. 1st, 2026 10:51 pm
Powered by Dreamwidth Studios