Skip to content

Commit 2f110f4

Browse files
Marat Zimnurovclaude
andcommitted
Процессы flang поехали на настоящей BEAM: печать в Elixir и сверка по набору исходов
Процесс flang печатается в GenServer, надзор — в дерево супервизоров OTP, почтовый ящик — в почтовый ящик процесса BEAM. Своей машины под конкурентность не пишется: контракт (flang/conc/SPEC.md, «Свою BEAM писать не надо») решает печатать в чужую, и вытеснение по счёту редукций, куча со своим сборщиком у каждого процесса, планировщик на каждое ядро и горячая загрузка достаются в настоящем виде, а не в приблизительном. ТРИ СУПЕРВИЗОРА OTP НА ОДИН НАДЗОР FLANG — не нагромождение, а перевод. У OTP интенсивность перезапусков принадлежит супервизору, а в flang стратегия названа за каждым ребёнком по отдельности, и «передать выше» настройкой ребёнка не выражается вовсе. Поэтому дети «перезапустить» живут под супервизором с порогом отказов, дети «передать выше» — под супервизором с интенсивностью 0 (первое же падение обязывает его перезапустить, а перезапускать нельзя — он умирает и уносит надзор наверх), дети «остановить» — :temporary напрямую. Процесс при этом :transient, а не :permanent: остановка «норма» обязана оставить его остановленным. Вложенный надзор наоборот :permanent: умирает он с причиной :shutdown, а её :transient считает нормальным концом. «Продолжить» совпало с BEAM точно: handle_continue выполняется перед любым сообщением из ящика, но уже после возврата обработчика — ровно «в голову ящика, отдав управление». Притворяться не пришлось ничем. Ящик, переживающий перезапуск, пришлось печатать руками: на BEAM он умирает вместе с процессом, а контракт обещает обратное. Умирающий процесс вычерпывает остаток в хранилище, следующее воплощение забирает его в init. Без этого прогон «третий отказ в окне» дал бы один отказ вместо трёх. Запас витков остался, и это решение, а не инерция. Вытеснение спасает соседей, а не сам процесс: незавершающийся обработчик на BEAM не остановится никогда — он съест память узла, и никакого исхода из этого не следует, а контракт требует именно исхода. Счётчик при этом лежит в словаре процесса, а словарь у процесса BEAM свой: два обработчика не мешают друг другу по устройству, а не по договорённости. СВЕРКА. Сравнивать значения нельзя: семантика — любое чередование атомарных пробегов, а семени у планировщика BEAM нет. Поэтому эталон гоняется на тысяче семян, из итогов собирается множество исходов и множество журналов доставки, и каждый запуск на настоящей BEAM обязан в них попасть. Отдельно проверено, что множество насыщено (половина сетки даёт тот же набор) и узко (изменение одного числа выводит исход из набора; наборы разных прогонов не пересекаются). Измерение, которого в контракте не было: редукционный квант BEAM много больше пробега обработчика. На race.flang сорок запусков дают одно чередование, а триста — четыре, и все четыре лежат в наборе эталона из 84. Значит сверка доказывает включение, а не покрытие, и перебор семян прогоном на настоящей машине не заменяется. Что не влезло и записано в контракт: остальные семь целей процессов не печатают; порог отказов у OTP считает только перезапуски одной группы, а окно измеряется секундами, а не пробегами; таймер идёт по настоящим часам, и журнал программы с таймером поэтому не сверяется; «покой» на BEAM — вывод из наблюдения, а не знание; между смертью процесса и регистрацией нового воплощения есть окно, в котором сообщение пропадает; ожидания прогона в напечатанный код не едут, потому что они про одно чередование, а на BEAM законно любое. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P6ehGNnEcrkCK1V5iMHNYi
1 parent 5bfdce0 commit 2f110f4

8 files changed

Lines changed: 2132 additions & 47 deletions

File tree

README.md

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -658,7 +658,7 @@ in [`flang/cat/SPEC.md`](flang/cat/SPEC.md) and are not implemented.
658658

659659
---
660660

661-
## Concurrency: two steps of seven
661+
## Concurrency: four steps of seven
662662

663663
Values are immutable by construction, so two computations looking at one value cannot interfere:
664664
there are no data races to lose, because there is nothing to build one out of. What the language
@@ -699,14 +699,32 @@ flang test flang/conc/examples/counter.flang --pretty
699699
```
700700

701701
The contract is [`flang/conc/SPEC.md`](flang/conc/SPEC.md), and it names a seven-step plan of
702-
which **steps one, two and three are done**: the surface, the checks, the reference scheduler and
703-
supervision. Strategies are applied for real: a restart returns the state to the very same initial
704-
value, the failure threshold is counted in the scheduler's virtual time, and "escalate" reaches the
705-
supervisor one step up — or, if there is none above, stops the whole program with the outcome
706-
`"отказ дошёл доверху"`. Processes are not printed into any target — emitting such a program gives
707-
the handlers as ordinary functions and nothing more. The examples are in
702+
which **the first four are done**: the surface, the checks, the reference scheduler, supervision
703+
and emission to Elixir. Strategies are applied for real: a restart returns the state to the very
704+
same initial value, the failure threshold is counted in the scheduler's virtual time, and
705+
"escalate" reaches the supervisor one step up — or, if there is none above, stops the whole program
706+
with the outcome `"отказ дошёл доверху"`. The examples are in
708707
[`flang/conc/examples/`](flang/conc/examples).
709708

709+
No virtual machine is being written for this: the language already targets **Elixir**, so a flang
710+
process is printed as a BEAM process and supervision as an OTP supervisor tree. Preemption by
711+
reduction count, a heap with its own collector per process, a scheduler per core, distribution and
712+
hot code loading all come for real, not approximately.
713+
714+
```bash
715+
flang emit flang/conc/examples/counter.flang --target elixir --out /tmp/counter
716+
cd /tmp/counter && make build
717+
echo '{"run":"два прибавления и доклад"}' | elixir -pa _build -e 'Flang.Cli.main(["SchyotchikIZhurnal"])'
718+
```
719+
720+
The emitted program cannot be checked against the reference by value: the semantics is *any*
721+
interleaving of atomic handler runs, and the BEAM scheduler takes no seed. So the check is on the
722+
*set*: the reference sweeps a thousand seeds to build the set of outcomes and the set of delivery
723+
logs, and every outcome of a real BEAM run must land inside it. Both weak spots are covered — the
724+
set is shown to be saturated (half the seed grid gives the same set) and narrow (change one number
725+
in an outcome and it falls out). The other seven targets do not print processes yet: there, a
726+
program with `процесс` gives the handlers as ordinary functions and nothing more.
727+
710728
---
711729

712730
## The shell and the language server

README.ru.md

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -671,7 +671,7 @@ FLANG_COMPOSE_MISMATCH: композиция «оформить» не стык
671671

672672
---
673673

674-
## Конкурентность: два шага из семи
674+
## Конкурентность: четыре шага из семи
675675

676676
Значения неизменяемы по устройству, поэтому два вычисления, глядящие на одно значение, не могут
677677
помешать друг другу: гонок по данным нет не потому, что их ловит анализ, а потому, что их нечем
@@ -712,13 +712,30 @@ flang test flang/conc/examples/counter.flang --pretty
712712
```
713713

714714
Контракт — [`flang/conc/SPEC.md`](flang/conc/SPEC.md), и в нём порядок работ из семи шагов, из
715-
которых **сделаны первый, второй и третий**: поверхность, проверки, планировщик эталона и надзор.
716-
Стратегии применяются по-настоящему: перезапуск возвращает состояние к тому же самому начальному
717-
значению, порог отказов считается по виртуальному времени, «передать выше» доходит до надзора
718-
ступенью выше, а если выше никого — останавливает всю программу с исходом
719-
`"отказ дошёл доверху"`. Процессы не печатаются ни в одну цель: печать такой программы даёт
720-
обработчики как обычные функции и больше ничего. Примеры лежат в
721-
[`flang/conc/examples/`](flang/conc/examples).
715+
которых **сделаны первые четыре**: поверхность, проверки, планировщик эталона, надзор и печать в
716+
Elixir. Стратегии применяются по-настоящему: перезапуск возвращает состояние к тому же самому
717+
начальному значению, порог отказов считается по виртуальному времени, «передать выше» доходит до
718+
надзора ступенью выше, а если выше никого — останавливает всю программу с исходом
719+
`"отказ дошёл доверху"`. Примеры лежат в [`flang/conc/examples/`](flang/conc/examples).
720+
721+
Своей виртуальной машины под это не пишется: у языка уже есть цель **Elixir**, и процесс flang
722+
печатается в процесс BEAM, а надзор — в дерево супервизоров OTP. Вытеснение по счёту редукций,
723+
куча со своим сборщиком у каждого процесса, планировщик на каждое ядро, распределённость и горячая
724+
загрузка достаются при этом в настоящем виде, а не в приблизительном.
725+
726+
```bash
727+
flang emit flang/conc/examples/counter.flang --target elixir --out /tmp/счётчик
728+
cd /tmp/счётчик && make build
729+
echo '{"run":"два прибавления и доклад"}' | elixir -pa _build -e 'Flang.Cli.main(["SchyotchikIZhurnal"])'
730+
```
731+
732+
Сверять напечатанное с эталоном по значению нельзя: семантика определена **любым** чередованием
733+
атомарных пробегов, а семени у планировщика BEAM нет. Поэтому сверяется набор: эталон на тысяче
734+
семян даёт множество исходов и множество журналов доставки, и каждый исход настоящего запуска на
735+
BEAM обязан в это множество попасть. Проверено и то, что множество насыщено (половина сетки семян
736+
даёт тот же набор), и то, что оно узко (достаточно изменить в исходе одно число, и он из набора
737+
выпадает). Остальные семь целей процессов пока не печатают: там программа с `процесс` даёт
738+
обработчики как обычные функции и больше ничего.
722739

723740
---
724741

0 commit comments

Comments
 (0)