-
Notifications
You must be signed in to change notification settings - Fork 1
Description
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.
даёт пользователюбиблиотеки возможность оставить фоновый обработчик, преобразующий поток данных в удобные дл пользователя вызовы метод.
[отвергнутые идеи - полное переписывание интерфейса]