Как идёт запуск
Запуск начинается с узла Старт, идёт по связям от узла к узлу и считается завершённым, когда закончили все ветки и достигнут узел Финиш. Эта страница объясняет правила, по которым он идёт; что делает каждый узел — в справочнике узлов, а значения, которые путешествуют вместе с запуском, — в разделе данные.
Старт и финиш
В эксперименте ровно один узел Старт и ровно один узел Финиш.
- У узла Старт нет входа. Он проходит сразу, а его строка в ленте запуска показывает seed запуска. У его выхода может быть несколько связей: тогда эксперимент начинается с параллельных веток.
- Каждая ветка, дошедшая до узла Финиш, на нём останавливается. Финиш отображается как выполняющийся с первого прихода и проходит один раз, после того как закончила последняя ветка, — и не проходит вовсе, если какой-либо шаг завершился ошибкой. Именно это прохождение присваивает запуску статус Успешно.
- Запуск, в котором все ветки закончились без ошибок, но ни одна не дошла до финиша, завершается ошибкой
run.no_end.
Выходы и связи
Шаг узла заканчивается выбором выхода, а запуск идёт по каждой связи этого выхода. У большинства узлов один выход, Выход; некоторые выбирают из нескольких:
| Узел | Выходы, которые должны быть соединены | Выходы, которые могут быть соединены |
|---|---|---|
| Финиш | — | — |
| Параллельный запуск | Поток 1, Поток 2 | — |
| Ветвление по статусу, Ветвление по значению | Да, Нет | — |
| Любое ожидание (Ждать OSC, Ждать UDP, Ждать MQTT, Ждать HTTP-запрос, Ожидание WebSocket) | Получено | Таймаут |
| Цикл | Тело, Готово | Лимит |
| Любой другой узел | Выход | — |
Чтобы соединить узлы, перетащите выход на узел; если отпустить его на пустом холсте, там появится новый узел. Если перетащить выход, у которого уже есть связь, добавится ещё одна. Кнопка Добавить следующий, клавиша A и значок + на связи, наоборот, вставляют узел в уже существующую связь.
Незавершённая схема — это черновик: она сохраняется, но не запускается. Сообщение Завершите схему на панели инструментов говорит, чего не хватает, и показывает узел. См. что проверяется перед запуском.
Параллельные ветки
Несколько связей от одного выхода
Когда у выхода несколько связей — в том числе у выхода узла Старт, — все узлы, к которым они ведут, выполняются одновременно. Первая связь продолжает ветку; каждая следующая запускает параллельную ветку. У каждой ветки своя копия переменных и последнего HTTP-ответа, поэтому то, что одна ветка задаёт или получает, другие не видят.
Параллельный запуск и слияние
Узел Параллельный запуск проходит сразу и выходит через оба выхода, Поток 1 и Поток 2, — это то же, что две связи от одного выхода, только нарисованное в виде узла.
Узел Слияние потоков ждёт каждую связь, которая в него ведёт, и затем продолжает как одна ветка, слив копии в порядке этих связей:
- переменные всех веток — если две ветки задают одно имя, побеждает связь, которая в эксперименте указана позже;
- HTTP-ответ последней по этому порядку связи, которая его приносит;
- для ожиданий после слияния — самое раннее из их последних действий.
Решает порядок связей, а не то, какая ветка успела закончить первой.
Сливайте только то, что выполняется параллельно
Слияние считает свои связи, как бы они ни оказались параллельными: от узла Параллельный запуск, от нескольких связей одного выхода, от отдельных путей. За выходами Да и Нет ветвления выполняется только один путь, поэтому слияние, в которое ведут оба, ждёт ветку, которой не будет: запуск завершается ошибкой run.join_waiting с указанием, сколько связей так и не было пройдено. Чтобы свести альтернативные пути вместе, ведите их прямо в следующий узел.
Узел, который не является слиянием и в который приходят две параллельные ветки, выполняется по разу для каждой из них.
Когда шаг завершается ошибкой
Первая же ошибка завершает запуск неудачей. Остальные ветки не начинают новых шагов: серия или нагрузка заканчивается досрочно, а любой другой шаг, в котором они находятся, доходит до конца. Ошибка, с которой они столкнутся за это время, попадает в ленту, но ошибкой запуска не становится. Шаг, упёршийся в таймаут при наличии связи на выходе Таймаут, ошибкой не завершился — см. ожидания.
Ветвление
| Узел | Выходит через Да, когда |
|---|---|
| Ветвление по статусу | последний HTTP-ответ на этом пути имеет заданный статус |
| Ветвление по значению | выполняется его сравнение — см. сравнение значений |
В остальных случаях каждый выходит через Нет. Для ветвления по статусу на каждом пути перед ним нужен HTTP-запрос; для ветвления по значению имена, которые оно читает, должны быть известны в этом месте. Когда пути после Да и Нет снова сходятся, узел, где они сошлись, выполняется один раз, и в нём известны только переменные, заданные на обоих путях (где известна переменная).
Повтор при ошибке
Шаг, который отправляет или слушает, может попробовать ещё раз, если завершился ошибкой: включите повторять при ошибке в его свойствах.
| Настройка | Что это | Диапазон | Начальное значение |
|---|---|---|---|
| Попыток | всего попыток, включая первую | 1–10 | 3 |
| Пауза, мс | пауза перед второй попыткой | 0–60 000 мс | 500 |
| Паузы | одинаковые: пауза всякий раз одна и та же; удваиваются: каждая следующая пауза вдвое длиннее предыдущей | — | одинаковые |
- Повтор действует для узлов HTTP-запрос, TCP-сообщение, Публикация MQTT, OSC-сообщение, UDP-датаграмма, Подключение WebSocket, Отправка WebSocket и для любого ожидания. Остальные узлы его не принимают (
node.retry_unsupported), а у HTTP-запроса под нагрузкой повтора нет. - Ни одна пауза не бывает длиннее 60 с, как бы ни росло удвоение.
- Каждая неудачная попытка появляется в ленте как Повтор с номером и причиной. Затем шаг либо проходит, либо завершается ошибкой по причине последней попытки.
- Повторяется только выполнение. Поле, шаблон которого не разрешается, завершается ошибкой сразу.
- Отправка, которая ждёт ответа, отправляет снова. Ожидание ждёт снова, отсчитывая, как и прежде, от последнего действия ветки.
- Ожидание со связью на выходе Таймаут по таймауту не завершается ошибкой, поэтому не повторяется: оно идёт по выходу Таймаут.
- Остановка сразу прерывает паузу.
Серия отправок
Шаг, который отправляет, может отправлять снова и снова — сигнал «жив», опрос, ровный поток, — без цикла в схеме: включите отправлять серией.
| Настройка | Что это | Диапазон | Начальное значение |
|---|---|---|---|
| Повторять | заданное число раз или в течение времени | — | заданное число раз |
| Отправок | всего отправок, включая первую | 2–10 000 | 10 |
| В течение, мс | как долго отправлять, считая от первой отправки | 1–300 000 мс | 10 000 |
| Каждые, мс | пауза между двумя отправками | 10–60 000 мс | 1000 |
| Разброс, мс | каждая пауза случайно удлиняется не более чем на это значение | 0–60 000 мс | 0 |
- Серия действует для узлов HTTP-запрос, TCP-сообщение, Публикация MQTT, OSC-сообщение, UDP-датаграмма и Отправка WebSocket (на остальных —
node.repeat_unsupported). У HTTP-запроса бывает либо серия, либо нагрузка, но не обе сразу. - Каждая отправка выполняется так, как выполнялась бы единственная: её шаблоны разрешаются заново —
{{counter}}равен номеру отправки,{{now}}— её времени, — а повтор при ошибке, если он включён, применяется к каждой отправке. Отправка, которая ждёт ответа, ждёт собственного. - Для серии на заданное время отправка делается, только если может начаться до истечения этого времени.
- Разброс берётся из seed запуска: один и тот же seed даёт одни и те же паузы.
- Лента сообщает о ходе как Серия не чаще раза в секунду. Шаг проходит после последней отправки, с исходом этой отправки; отправка, окончательно завершившаяся ошибкой, приводит к ошибке шага.
- Ошибка в другой ветке прекращает отправки; Остановка сразу прерывает паузу.
Серия должна укладываться в один запуск: отправки и их самые длинные паузы ((count − 1) × (interval + jitter)) — не более 300 с (node.repeat_too_long), а серия на заданное время — не более 10 000 отправок (node.repeat_too_many).
Цикл
Узел Цикл снова и снова выполняет шаги на своём выходе Тело; последний из них соединён обратно с циклом.
| Настройка | Что это | Диапазон |
|---|---|---|
| Итераций не больше | наибольшее число итераций | 1–1000 |
| выйти раньше, если | условие выхода: Значение, Условие, Ожидается, как в узле Проверка значения | необязательно |
- Если в цикл пришли снаружи, он начинает итерацию 1 на выходе Тело.
- Каждый раз, когда тело возвращается, проверяется условие выхода — после итерации, поэтому тело выполняется как минимум один раз и может задать то, что проверяет условие.
- Когда условие выполняется, цикл выходит через Готово.
- Иначе начинается следующая итерация, пока они остаются.
- Если итерации заканчиваются раньше, цикл выходит через Лимит, когда к этому выходу подключена связь, а если нет — завершает запуск ошибкой
loop.limit. Без условия тело выполняется на каждой итерации, а цикл выходит через Готово.
Внутри тела {{counter}} — номер итерации, поскольку каждый узел считает собственные выполнения. Условие и шаги после выхода Готово или Лимит могут использовать то, что задала каждая итерация тела, — например, статус, который тело извлекает; само тело видит только то, что было известно, когда запуск дошёл до цикла.
Шаблон Опрос до готовности запрашивает у устройства статус каждые 0,3 с, пока оно не ответит ready, но не более 10 раз.
Что может быть в теле
Тело выполняется как одна ветка, итерация за итерацией. Связь обратно к циклу — единственный цикл, который может быть в эксперименте; любой другой — graph.cycle.
| Правило | Ошибка |
|---|---|
| Что-то на выходе Тело ведёт обратно к циклу | loop.no_return |
| У каждого выхода в теле одна связь | loop.body_parallel |
| Каждый выход в теле ведёт дальше в теле или обратно к циклу | loop.body_leaves |
| В тело ведёт только выход Тело цикла | loop.body_entered |
| В теле нет узлов Старт, Финиш, Параллельный запуск, Слияние потоков и других узлов Цикл | loop.body_unsupported |
Ожидания
Ожидание проходит, когда приходит сообщение, которого оно ждёт: Ждать OSC, Ждать UDP, Ждать MQTT, Ждать HTTP-запрос и Ожидание WebSocket. Что именно сопоставляет каждое из них, описано в справочнике узлов; здесь — как они слушают.
Слушают с самого начала
Запуск открывает то, на чём слушают его ожидания, до первого шага, поэтому ответ, который пришёл быстрее следующего шага, не будет пропущен:
| Ожидание | Что открывается до первого шага |
|---|---|
| OSC, UDP | один сокет UDP на каждый адрес Слушать (IP:порт), общий для всех ожиданий на нём |
| HTTP-запрос | один слушатель на адрес — HTTP-эмулятор запуска, если он есть, иначе слушатель, отвечающий 204 |
| MQTT | одно подключение на брокер и фильтр топика, с подпиской; retained-сообщения, которые брокер при этом воспроизводит, игнорируются |
| WebSocket | ничего: оно читает соединение, которое открыл узел Подключение WebSocket, когда выполнялся |
Поскольку всё это открывается первым, адреса известны ещё до запуска: ожидание OSC, UDP или HTTP слушает на буквальном IP:port с портом, отличным от 0, а брокер и топик ожидания MQTT принимают только параметры. Порт, который не удаётся открыть — он занят или не является адресом этого компьютера, — останавливает запуск до какого-либо трафика, на поле этого ожидания. Всё закрывается, когда запуск заканчивается, как бы это ни произошло.
Какие сообщения учитываются
Ожидание рассматривает сообщения, пришедшие после начала последнего действия в его ветке — последнего запроса, сообщения, публикации, подключения WebSocket или отправки, — а до всякого действия — после начала запуска. Сообщение, пришедшее до запроса, не учитывается, а пауза (Delay) или запись в лог (Log) между запросом и ожиданием не прячет ответ на него. После слияния учитывается самое раннее из последних действий слитых веток.
Ожидание берёт первое подходящее сообщение и расходует его: два ожидания никогда не получат одно и то же сообщение.
Каждый сокет, подписка или соединение хранит для своих ожиданий не более 1024 сообщений и 64 МиБ; сверх этого самые старые отбрасываются и подсчитываются.
Таймаут
Поле Таймаут, мс — от 1 до 120 000 мс, изначально 2000. Если за это время ничего не подошло:
- при наличии связи на выходе Таймаут ожидание идёт по ней;
- без неё шаг завершается ошибкой
wait.timeout, которая сообщает, сколько других сообщений пришло за это время — неверный шаблон выглядит иначе, чем молчащее устройство, — а в подробностях — сколько более старых сообщений было отброшено, когда очередь переполнилась.
Переменная ожидания — reply, а для HTTP request — существует только после выхода Получено. Когда на вкладке Инспектор включён захват, шаг ещё и ссылается на кадр, с которым совпало сообщение: см. ленту запуска.
Ответ на том же шаге
Узел OSC-сообщение или UDP-датаграмма может ждать собственного ответа: установите флажок ждать ответ.
| Настройка | Что это | Начальное значение |
|---|---|---|
| Ответ на (IP:порт) | адрес, на котором ждут ответ; порт 0 — любой свободный порт | 0.0.0.0:0 |
| Шаблон адреса ответа (OSC), Содержимое ответа (UDP) | каким должен быть ответ, как в соответствующем ожидании | любой |
| Таймаут, мс | 1–120 000 мс | 2000 |
| Переменная ответа | переменная, в которую записывается ответ | reply |
Сокет, указанный в поле Ответ на (IP:порт), открывается до первого шага, как у ожидания, и сообщение уходит с него: устройство, отвечающее на порт отправителя, будет услышано, а то, что отвечает на фиксированный порт, — если это тот самый порт, который указан. Шаг проходит при подходящем ответе, а переменная существует после его выхода. Выхода Таймаут здесь нет: если ответа нет вовремя, шаг завершается ошибкой, а повтор при ошибке может отправить снова. Чтобы ветвиться по молчанию, используйте отдельное ожидание.
Что проверяется перед запуском
Редактор проверяет эксперимент по мере правки; кнопка запуска проверяет его ещё раз. В сообщении о проблеме указан узел и, если оно есть, поле.
| Правило | Ошибка |
|---|---|
| У эксперимента есть имя | doc.name_required |
| От 1 до 64 узлов, ровно один Старт и один Финиш | doc.node_count, doc.start_end_count |
| У старта нет входа | graph.start_input |
| Связь ведёт к другому существующему узлу | doc.connection_invalid |
| Одна и та же связь не повторяется | doc.connection_duplicate |
| Каждый выход, который должен быть соединён, соединён | graph.outputs_required |
| У узла нет связи на выходе, которого у него нет | graph.port_unexpected |
| Любой узел достижим от старта | graph.unreachable |
| Нет циклов, кроме обратной связи цикла | graph.cycle и правила тела |
| Перед проверкой или извлечением на каждом пути есть HTTP-запрос; запрос под нагрузкой не считается | graph.needs_http |
| Каждый шаблон разбирается, а каждое имя в нём известно на каждом пути | template.*, name.* — см. данные |
| Каждое поле заполнено и в допустимых пределах | node.* |
| Узел, который указывает другой, — Сменить помехи, Эмулятор: выкл./вкл., узлы WebSocket, — указывает существующий, а узел WebSocket стоит после своего подключения | impair.relay_unknown, emulator.node_unknown, ws.connection_unknown, ws.connection_after |
| Два сокета запуска не используют один порт | см. сбои |
Затем запуск проверяет то, что нужно для старта: каждый секрет сохранён, каждый порт открыт. Пока не выполнено всё это, ни один шаг не выполняется и ничего не отправляется.
Ограничение по времени
Запуск длится не более 300 с. Тот, что к этому моменту ещё идёт, останавливается и завершается ошибкой run.timeout. Из командной строки и через API предел можно сделать короче — от 1 до 300 с.
Остановка
Пока запуск идёт, кнопка запуска превращается в Стоп. Она сразу завершает запуск: каждую ветку, каждую паузу повтора или серии, каждое ожидание и каждую нагрузку — запросы в пути отбрасываются. Его сокеты, подписки, эмуляторы и реле закрываются, а соединения WebSocket отправляют кадр закрытия. Остановить всё в шапке делает то же со всеми задачами. Остановленный запуск не сохраняет отчёт; см. запуски.