Skip to content

Simplify asyncio without breaking its public  #20

@arhadthedev

Description

@arhadthedev

Simplify asyncio without breaking its public interface

In a hierarchy of Server/Stream -> Protocol -> Transport -> EventLoop the following issues exist that make asyncio control flow studying and hence extending unnecessary complicated and error-prone (the analysis turns into in-file width-first bunny hopping to not miss anything):

  • each layer of the stack has its own reference to an event loop. However, the only working configuration is when all layers are in the same loop as a low level data source (socket/pipe);
  • a concept of protocol leaks into a concept of transport ...
  • many private methods of objects created by the loop (like loop.create_server -> Server(), Server._start_serving) are just thin wrappers around private methods of a loop (like loop._start_serving). Later it becomes clear that this method is called from two public methods of Server but it happens five methods below
  • explicitly stored state hence multiple sources of truth. For example, Server maintains explicit self._serving flag instead of checking existing attributes inherently set up while the serving starting (like an assigned listening socket which presence guarantees serving and which absence/closed state means stopped serving no matters what the flag says)
  • many foo.sleep(0) to give the way for event processing. Instead, the very queue-demanding low-level operation needs to await on a loop skipping primitive.
  • [ ]

[в следуюих версиях заменить пассивное предоставление функций обратного вызова на активное присвоение внутренней функции в свойство источника события]

loop.create_server вызывает для каждого подключившегося функцию, привязывая возвращённый протокол; streams.start_server подвязывает функцией фабрику протоколов

И тут мы видим пример применения протокола: StreamReaderProtocol. То есть протокол диспетчеризует вызов от транспорта согласно своему типу (наример, StreamReaderProtocol хранит в себе пото который затем дёргает). Но в этом случае протокол должен быть тонюсенькой обёрткой, которую мы потом вообще заменим на функции обратного вызова транспорта, раз всё равно между протоколом и транспортом отношение один-к-одному. Всё равно для переключения на TLS и обратно мы обращаемся к очереди, которая сначала создаёт обёртку вокруг транспорта, а потом по требованию снимает её, выполняя в обеих случаях перенос функций обратног вызова. Подход с хранение в источнике функции-точки события вместо хранения ссылки на объект к которому придётся лезть - убирание обработчиков событий их публичного интерфейса. [именно это описание протокола поместить в документацию - диспетчеризация данных по спецефичным пользовательским обработчикам, будь это streams.InputStream или какой-то другой, с возможностью переключать между собой поддерживаемые транспорты, беря на сбя сбросы буферов и синхронизацию]

В связи с этим TLS в протоколах делать нечего. Он не работает с пользовательскими обработчиками.

Ещё, раз протокол жётко привязывает к себе транспорт, нам нет нужды .

Вывод - start_ssl отправляется в 3.11, чтобы в 3.12 спокойно решить все фундаментальные проблемы с полным циклом прогона по тестовым пользователям. Это единственное решение, так как класс TLS является частью низкоуровневой рнализации и не должен торчать на верхнем уровне.

Теперь переходим к протоколам, которые, как мы выяснили, должны быть максимально простыми. Настолько простыми, что мы тупо присваиваем методы нижележащего транспорта полям-методам протокола вместо построения методов-обёрток.

Протоколы сейчас делятcя на буферизованные, небуферизованные и без специализации:

  • class _SendfileFallbackProtocol(protocols.Protocol)
  • class WriteSubprocessPipeProto(protocols.BaseProtocol)
  • class SSLProtocol(protocols.BufferedProtocol)
  • class FlowControlMixin(protocols.Protocol)

Проанализируем необходимость и текущее содержимое протоколов:

  • _SendfileFallbackProtocol - используется в _sendfile_fallback. Первоначальное предположение - держит буфер отправки и отправляет его фрагментами. Первоначальное решение - избавиться, превратив в циклическую постановку задачи на запись и реакцию на готовность в очередь. Реальное содержимое - принять потомка transports._FlowControlMixin, сохранить его протокол к себе в поле ...

[нашли ещё одну проблему - получение протокола через транспорт, что не имеет смысла, так как проще передать исходный протокол напрямую, что, кстати, даст исходному протоколу возможность при необходимости наживую заменить транспорт незаметно для вышестоящих слоёв]

... чтобы вклинить себя между старым протоколом и старым транспортом. Вопрос: кто пользователь _sendfile_fallback, передающий свой протокол с жеданием его сохранить. Если это SSL-сокет, то это числый транспорт, не протокол, то есть мы сразу подцепляем _SendfileFallbackProtocol к транспорту.

  • WriteSubprocessPipeProto.Первоначальное предположение - предоставляет событие уничтожения процесса, получение его кода возврата.

То есть, ещё раз, протокол - это фоновый диспетчер, раскидывающий сырой поток по событиям, то есть дробящий его на специализированные функции обратного вызова. Пример - StreamWriter.

  • SSLProtocol - держит _ssl_buffer, сбрасывает буферы, остальное вляется частью транспорта.

Транспорт ..., Protocol is an adapter between a single, unified stream from/to Protocol and a user-defined I/O interface. For example, StreamReaderProtocol splits the stream into separate InputStream and OutputStream.

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

[отвергнутые идеи - полное переписывание интерфейса]

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions