Детали реализации

Дизайн данной библиотеки вдохновлен дизайном Future из языка Rust. Однако сейчас содержит некоторые значимые изменения.

Принцип работы типа Future

Весь мeханизм организации асинхронного вычисления на основе Future сводится к опросу самой Future путём вызова Poll: IContext -> Poll<'a> до завершения её вычисления. Future может быть отброшена вызовом Future.Drop: unit -> unit

Результатом опроса является текущее состояние вычисления Future:

  1. Pending - вычисление Future находится в процессе и еще не завершено.
  2. Ready x - значение вычислено и опрос больше не требуется.
  3. Transit fut - говорит о подмене текущей Future другой такого же типа. Уменьшает глубину опроса и предназначен для оптимизации bind выражаемых конструкций.

Если Future не совершает асинхронную работу и может сразу вернуть результат(возможно с некоторой работой), то при первом опросе должен возвращаться Ready. В противном случае Future должна запомнить переданный Context и вызвать его метод Wake в случае завершения асинхронной работы, чтобы планировщик пробудил её от ожидания этой работы.

При написании Future стоит учитывать, что пока в ней выполняются блокирующие операции -- блокируется поток планировщика, на котором она запушена. Для долгих блокирующих операций (когда это возможно, например, в циклах которые сами не возвращают управление) можно применить Future.yieldWorkflow, который возвращает поток планировщику одновременно с пробуждением Future.

Дизайн отбрасывания Future прокидывает отбрасывание ко всем дочерним Future независимо от способа их создания. Это позволяет отбрасывать запущенную на планировщие Future через IFutureTask (который может быть преобразован в Future). А также отбрасывать любую Future в альтернативах (например внутри Future.first).

Ожидаемые инварианты Future

  • Future полностью синхронна извне: методы Poll и Drop взаимно не потоко-безопасны и не должны вызываться одновременно (в т.ч. нельзя вызывать у одной Future метод Poll или Drop несколько раз одновременно).

  • Future может опрашиваться (Poll) повторно, даже если она не пробудилась.

  • Future может пробуждаться несколько раз до повторного опроса.

  • Future считается терминальной (т.е. выполневшейся) после:

    • Вызова Poll с результатом Ready x,
    • Вызова Poll с результатом Transit fut
    • Вызова Poll с броском исключения (логический эквивален Ready (Error exn))
    • Вызова Drop (в т.ч. с броском исключения, хотя бросать исключения в Drop нельзя)
  • Когда Future переходит в терминальное состояние, она должна остановить (или отвязать от себя) её запущенные фоновые процессы и освободить занятые резурсы.

  • После достижения терминального состояния, Future не должна больше использоваться (т.е. не должны вызываться Poll и Drop)

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

  • Контекст для Future должен быть одним и тем же от первого опроса, до терминального состояния. Future достаточно сохранить контекст при первом опросе и не перезаписывать его. (Можно сказать, что каждый контекст соответствует одному запуску.)

  • Контекст каждой запущенной Future должен быть уникальным. Мотивация этого поведения в том чтобы предотвратить пробуждение новых Future в планировщике при утечке контекста старыми.

  • При достижении терминального состояния Future не следует пробуждаться, т.е. следует позаботиться о том, чтобы Wake её контекста не мог больше использоваться. Однако повторное пробуждение после достижения терминального состояния допустимо по вышеописанному правилу.

  • Drop не должен бросать исключения и должен отработать корректно, чтобы произвести освобождение ресурсов. (Реализациям Future НЕ нужно заботиться об этом, но если рансай)

Примечания

Для реализаций Future любое поведение не следующее инвариантам -- UB, и может как обрабатываться особым способом, так и игнорироваться.

IStream по сути является расширением модели Future, поэтому инварианты Future как правило верны и для IStream

В отличие от реализации в Rust, я не заставляю Future обновлять последний сохраненный ею IContext при каждом опросе. Преимущества на обоих платформах мне не очевидны, а это ощутимо усложняет реализацию. Поэтому это может измениться в будущем.

Методы Future не предназначены для ручного вызова, т.к. требуют с собой деликатного обращения. По этой причине IFutureTask не наследует Future, а имеет отображение в неё

Каждая Future может быть использована как-либо (забайнджена, запихнула в комбинатор, запущена) только ОДИН раз

Есть некоторая коллизия с тем, что Future может выкидывать исключения исключения как предметные, так и свои. Например, неявным результатом IVar может быть любое исключение. И одно из них это FutureTerminatedException. В этой точке будет не так прозрачно, исключение произошло в читающей Future или в записывающей. Но 0. Исключениям можно, у них сложный переезд из места появление в абсолютно случайное

  1. Возможные решения слишком муторные и не стоят того из-за исключения, которое означает проблему в библиотечном, а не пользовательском коде