flang the compiler proves your program cannot hang 0.7.29 GitHub Русский

Processes, supervision, distribution

Processes in flang work like Erlang/OTP processes: each one has its own state and a mailbox, handles one message at a time, and talks to the others only by messages. A supervisor restarts a process that failed. The difference from Erlang: a handler is a pure function. It does not send anything itself; it returns the new state and a list of actions ("send this message", "stop"), and the scheduler performs them.

This page builds a small program of two processes under a supervisor. Every piece comes from flang/concurrency/examples/supervision.flang.

In flangErlang/OTP
процессa gen_server
состояние, начинает сthe state and init/1
обрабатываетhandle_cast/2
the action отправитьgen_server:cast/2
надзорa supervisor
стратегия «перезапустить»a restart strategy
порог отказов N за M миллисекундintensity and period

Declare a process

процесс «Работник»
  состояние «Счёт»
  начинает с «нулевой счёт»
  принимает «Задача»
  обрабатывает «шаг работника»
LineWhat it names
состояниеthe type of the state
начинает сa function with no arguments that returns the initial state
принимаетthe type of incoming messages, usually an enum
обрабатываетthe handler

Write the handler

The handler is an ordinary total function. It takes the state and a message and returns a record with two fields: the new state and a list of actions. Branch on the message with разбор (pattern matching):

тотальная функция «шаг работника»
  принимает счёт: «Счёт», сообщение: «Задача»
  возвращает «Отклик работника»
  разбор сообщение
    случай вариант «прибавить» с «сколько» как сколько
      пусть новое равно (запись «Счёт» с «всего» равным (счёт.«всего» плюс сколько))
      запись «Отклик работника» с «состояние» равным новое и «действия» равным []

You declare the return record yourself, with exactly these two fields:

объект «Отклик работника»
  «состояние»: «Счёт»
  «действия»: список «Действие»

The type «Действие» (action) is built into the language: it appears as soon as the file declares a process. Because the handler is a pure function, you test it like any other function, with пример (unit tests), and the prover can prove обеспечивает (postconditions) about it — for example, "the handler always returns exactly one action".

Send a message

To send a message, put the action отправить (send) into the returned list:

пусть отметка равно (вариант «отправить» с «кому» равным "Учётчик"
                     и «что» равным (вариант «записать» с «текст» равным "3"))
запись «Отклик работника» с «состояние» равным новое и «действия» равным [отметка]

«кому» (to) is the name of the recipient process; «что» (what) is the message. Write the name as a string literal. Then the compiler checks it: a literal name of an undeclared process is the error FLANG_UNKNOWN_PROCESS, and the message must be a value of the type the recipient принимает. A name computed at run time is accepted, but the compiler cannot check it.

Other actions: породить (spawn) starts a new process of a declared kind under a name you choose, with its first message; отложить (defer) puts the current message back at the end of the mailbox; через N отправить sends after a timer.

Stop

Stopping is an action too:

вариант «остановить» с «почему» равным "норма"

The reason "норма" (normal) means the work is done, and the supervisor is not involved. Any other reason is a failure, and the supervisor decides what to do.

Put a supervisor over it

надзор «Цех»
  процесс «Работник» стратегия «перезапустить»
  порог отказов 2 за 5000 миллисекунд иначе «остановить»
StrategyWhat happens to the failed process
«перезапустить» (restart)it starts again with the initial state
«остановить» (stop)it no longer runs
«передать выше» (escalate)the supervisor above decides

порог отказов N за M миллисекунд иначе «стратегия» is the restart limit: while there are at most N failures within M milliseconds, the process strategy applies; failure N+1 inside the window switches to the fallback strategy after иначе. A supervisor may hold another supervisor:

надзор «Цех»
  процесс «Счётчик» стратегия «перезапустить»
  надзор «Связь» стратегия «передать выше»
  порог отказов 1 за 5000 миллисекунд иначе «передать выше»

A restart sets the state back to the initial value. There is nothing to clean up: states are immutable, so a failed handler cannot leave a half-applied change behind.

What happens when a process failsprocess returned«остановить»reason is «норма»?work done, supervisor notwokenfailure, supervisor wokenfailure threshold spent?strategy of the processfallback strategy of thethresholdrestart: state is the initialvaluestop: the process nolonger runsescalate: the supervisorabove decidesyesnonoyes
What happens when a process fails

Limit the mailbox (back pressure)

By default a mailbox is unbounded. To bound it, give the size after принимает:

процесс «Сток»
  состояние «Счёт»
  начинает с «нуль»
  принимает «Тик» с ящиком 4 сообщений
  обрабатывает «глотать»

When the mailbox is full, the sender fails with FLANG_MAILBOX_FULL, and its supervisor decides what to do. So a process that sends to a bounded mailbox needs a supervisor. The full example is flang/concurrency/examples/backpressure.flang. A bounded mailbox cannot be generated into Elixir: a BEAM mailbox is unbounded, and flang emit --target elixir refuses such a program with code 1.

Check the program

$ flang check flang/concurrency/examples/supervision.flang
модуль «Цех и учёт»: функций 4, из них с доказанным завершением 4; типов 7
объявления эти сверены НЕ ДО КОНЦА — processes, supervisors, runs: сверено, что имена сходятся, что у нетотального обработчика назван запас витков, что у отказа один судья, что начальное состояние помечено тотальным, что вид «породить» назван литералом и что адресату послан вариант объявленного им типа. НЕ сверено одно: достижим ли отказ ВНУТРИ тотальной функции — множество отказов считает отдельный слой, в замыкание бинарника не ввезённый. Адресат «отправить», названный вычислением, не судится и судиться не может: до запуска такого имени не существует
flang/concurrency/examples/supervision.flang: проверено НЕ ДО КОНЦА — разбор, типы, завершаемость, ядро и примеры прошли
$ echo $?
2

Exit code 2 means "checked, but not completely". The handlers, their types and their unit tests are checked; for процесс and надзор the compiler checks names and message types. Two things it does not check, and the output says so: whether a total handler can fail, and a recipient name computed at run time.

flang test runs the unit tests of the handlers:

$ flang test flang/concurrency/examples/supervision.flang
flang/concurrency/examples/supervision.flang: примеров 2, прошло 2, не прошло 0
$ echo $?
0

The file also has прогон blocks: scenarios that send messages to processes under a fixed random seed. Neither flang check nor flang test runs them, and nothing compares the result with their ожидается lines; the program generated into C runs a scenario on request and prints the final states and the supervisor's decisions.

Which targets run processes

TargetProcesses and supervision
c, elixir, js, tsthe code is generated together with a scheduler
cpp, csharp, go, java, python, rustflang emit refuses with code 1: the target has no scheduler, and a handler is not generated as a plain function nobody calls

The binary compiler itself does not run processes: they run in the generated C, Elixir, JavaScript or TypeScript.

Nodes

A node is a separate running program that holds some of the declared processes. The program text is the same on every node; which process lives on which node is described in a separate file:

{
  "программа": "flang/concurrency/examples/distributed.flang",
  "узлы": {
    "счёт": { "слушать": "127.0.0.1:0", "процессы": ["Счётчик"],
              "звонить": { "учёт": "127.0.0.1:0" } },
    "учёт": { "слушать": "127.0.0.1:0", "процессы": ["Учётчик"] }
  }
}

The binary compiler does not read this file yet: flang check rejects the key --размещение with exit code 2 as unknown. The format is described in docs/flang/concurrency/DISTRIBUTED.md.

Next