Алгоритм запусков и перезапусков (08.2016)

Группы вызовов

Группа вызовов - синхронно исполняемая группа вызвов подфрагментов, внутри которой порядок исполнения определяется рантаймом.

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

Объединяя вызовы подфрагментов в группы, фрагмент высказывает желание выполнить эту группу в одном ярусе. Реальные ярусы определяет runtime. Объединение фрагментов в группы позволяет устанавливать внутри фрагменов "точки отката". Иначе говоря, появляется возможность не перезапускать весь фрагмент целиком в случае конфликтов между подфрагментами, а перезапускать лишь ограниченный набор фрагментов внутри группы. Группы вызовов уменьшают потенциальный параллелизм, явно разделяя все множество асинхронных вызовов подфрагментов на ярусы.

Программист должен стараться делать так, чтобы группы удовлетворяли следующим условиям:

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

Фрагмент может вызвать другой фрагмент двумя спобобами:

  • call - синхронный вызвов. Равносилен запуску группы (яруса) из одного фрагмента.
  • call_async - асинхронный вызов. Реальный вызов подфрагмента может не произойти в момент вызова call_async(). На самом деле последовательные вызвовы call_async() формируют группу вызовов.

Группа вызвов считается завершенной и "сбрасывается" (отправляется рантайму на исполнение) если исполнение фрагмента достигает точки "сброса группы". Этой точкой может быть:

  • Явный вызвов исполнения группы: execute_async_group().
  • Любая операция tvm. Группа исполняется ДО выполнения операции.
  • Синхронный вызвов фрагмента: call(). Группа исполняется ДО вызова call.
  • Конец исполнения фрагмента.

Из этих правил следует вывод, что некторые фрагменты не могут исполнится параллельно, даже если они полностью независимы:

    call_async('f1')
    tvm_write('vid', 12345)
    call_async('f2')

Фрагменты f1, f2 никогда не будут исполнятся параллельно. Эту проблему можно решить отключением точек сброса перед любой tvm-операцией, но тогда будет существовать вероятность конфликта с родителем. Вполне вероятно, подобное поведение следует сделать опциональным для каждого фрагмента в отдельности.

Фрагменты внутри группы необязательно начнут исполнение именно в момент вызова execute_async_group():

  • Runtime может принять решение исполнять некоторые фрагменты группы, до того как сама группа окончательно сформируется (до момента вызова execute_async_group). Это может происходить в случае, если часть фраментов уже содержащихся в незаконченной группе образуют законченный ярус (предположительно). Логика определения законченного яруса по неполной группе не реализована, но теоритически возможна (планы).
  • Runtime может отложить исполнение группы фрагментов.

Жизненный цикл фрагмента

Завершенный фрагмент - это фрагмент, который runtime исключает из всех индексов исполняющихся фрагментов и рапортует инициатору исполнения о статусе завершения. Реальное исполнение фрагмента (исполнение кода) может продолжаться бесконечно (в случае исполнения внутри потоков). Фрагмент считается успешно завершенным только если успешно завершены все его подфрагменты, втч те которые исполняются асинхронно. Фрагмент завершается со статусом "CONFLICT" если произошел конфликт, который фрагмент не может разрулить самостоятельно. Это происходит только в случае конфликта одного из подфрагментов (втч глубоко вложенных) с фрагментом, исполняющимся вне ветки текущего фрагмента. В этом случае общая часть DAM конфликтующих фрагментов не будет совпадать с DAM текущего фрагмента.

Реальная остановка фрагмента (исполнение кода) может произойти если фрагмент вызвает любое системное API. В противном случае остановка может не случится вообще, если фрагмент завис и исполняется в потоке, а не в отдельном процессе.

При локальном запуске фрагмента (не путать с перезапуском!) нода делает следующее:

  • Создает задачу запуска фрагмента, по завершению которой, нода отчитывается инициатору запуска о результате выполнения фрагмента. Задача ассоциируется с ключом исполнения, который представляет собой пару (transactionId, DAM). Ключ исполнения однозначно идентифицирует запущенный экземпляр фрагмента в пределах всего кластера.
  • Создает поток для исполнения фрагмента, передает ему параметры и привязывает его к только что созданной задаче.
  • Завершение исполнения потока фрагмента не озанчает что фрагмент завершился. Завершение фрагмента - это завершение задачи. Завершение исполнения потока означает, что наступает этап обработки результата исполнения экземпляра фрагмента. Если в процессе выполнения не возникло ошибок и конфликтов - задача завершается со статусом ОК.

Перезапуск фрагментов

Перезапуск инициируется только в случае когда один из подфрагментов завершился со статусом CONFLICT и текущий фрагент может разрулить конфликт самостоятельно (см. п. "Жизненный цикл фрагмента").

При конфликте в группе фрагмент делает tvm rollback для ветки фрагментов исполняющихся в текущей группе. Далее нода делает запрос на перезапуск группы фрагментов. При перезапуске группа фрагментов сохраняет натуральный порядок исполнения.

Возможная ситауция конфликта с родителем:

  • перезапускается root
  • фрагменты отчитавшиеся о выполнении OK перезапускаются заново

"Схема запуска"

Алгоритм перезапуска фрагмента

  1. Если ключ запуска не зарегестрирован в индексах текущих локальных задач, нода прокидывает просьбу о перезапуске той ноде, из которой пришел запрос на запуск текущего фрагмента, далее переход к шагу 1. В противном случае идем далее.
  2. Фрагменту посылается сигнал остановки.
  3. Экземпляр запущенного фрагмента отвязывается от текущей задачи.
  4. Запускается новый экземпляр фрагмента с новым DAM и привязывется к задаче.

Порядок запуска определяется с помощью:

  • FragSeq LPF - информация о допустимости выполнения в одном ярусе (ЯПФ)
  • частичного или полного отключения параллельности (call_async превращается в call). На первых этапах работы системы отсутствует информация о конфликтах и FragSeq будет выдавать неопределенный результат.