Правило «это не отказ» без срока — не осторожность, а поломка
В распределённой части flang стояло верное правило: несостоявшийся ПЕРВЫЙ звонок соседу отказом не считается. Довод настоящий — потерять можно только то, что было, а без этого правила порог отказов сгорал бы на старте, когда узлы поднимаются не одновременно.
Правило было верным и при этом бесконечным, и вот это оказалось поломкой: узел, чей сосед не поднимется НИКОГДА, ждал его вечно и молча. Ни записи в журнале, ни отказа, ни решения надзора. Эксплуатации нечем было отличить «ещё поднимается» от «не поднимется» — а это ровно то различение, ради которого человек и смотрит в журнал.
Признак класса, по которому третий случай виден заранее. Ищите правило вида «такое-то молчание/неудача отказом не считается». Спросите: до каких пор? Если ответа нет, правило не осторожное, а неполное: у «пока не считается» обязан быть конец, и конец этот — число, а не настроение. После конца решение принимает тот же самый механизм, что решает обо всём остальном; заводить для просроченного ожидания ОТДЕЛЬНЫЙ путь — вторая ошибка, потому что два пути разойдутся на первой же правке.
Чем подтверждено. Ветка vypusk/raspredelyonnost-do-konca, испытания 14 и 19 в flang/conc/distributed.test.mjs. Улика снята снятием правки: со сроком знакомство 0 («ждать вечно») узел молчит 3000 мс и пишет ноль записей — ровно как вчера. Со сроком 800 мс знакомство названо несостоявшимся через 803 мс по часам узла, дальше три отказа FLANG_LINK_DOWN и те же четыре решения надзора, что даёт перекушенный провод. Второй случай того же класса нашёлся рядом: принятый сокет, который не назвался, держался открытым вечно — теперь при сроке 700 мс закрывается через 704 мс.
Как выбрано число. Не на глаз: подъём соседа — это печать его программы. Узел примера поднимается за 1233–1432 мс (12 замеров, медиана 1338), печать самой крупной программы дерева стоит 3,1–3,5 с. Умолчание 30 000 мс — это 21 худший подъём примера. В эталоне на flang стоит формула, а не цифра, и при ней утверждение: первое знакомство обязано ждаться дольше, чем обнаружение молчания на уже заведённой связи.
Где это в итоге живёт — и это часть урока. Первая редакция правки была строчкой у хозяина узла на JavaScript: таймер, поле, ветка. Она работала и была неверной по месту — рядом с машиной состояний связи (flang/conc/link.flang) она оказалась ВТОРЫМ механизмом решения о связи. После слияния с соседней работой срок переехал внутрь машины: одиннадцатое событие «Срок знакомства вышел», восьмое веление «Доложить о несостоявшемся знакомстве», пятый признак связи «срок вышел» — и решение стало печататься во все восемь целей (flang/test/svyaz-celi.test.mjs: 2184 вопроса каждой цели, 17 472 сверки, расхождений 0). Хозяин остался с тем, что умеет: заводит часы и говорит связи, что срок вышел.
Правило отсюда общее: новое решение ищет себе место рядом с уже принятыми решениями того же предмета, а не рядом с тем, кто его будит.
Чем ограничено. Срок не делает систему точнее: он переводит невидимое ожидание в названное событие. Отличить «умер» от «медленный» по-прежнему нечем (Fischer, Lynch, Paterson, 1985), и ложное срабатывание на медленно встающем соседе теперь возможно — цена названа и вынесена в довод узла.
Связано: distribution-splits-into-world-and-wire, at-most-once-is-the-only-provable-half