openlang

asyncio.TaskGroup, когда один запрос падает

Сравнение конкурентности в разных языках обычно начинают с синтаксиса. Я бы начал с того, что нужно расчёту, когда что-то падает.

Небольшое приложение на Python параллельно загружает измерения и веса и объединяет их в одну оценку. Одна загрузка выбрасывает исключение, пока другая ещё выполняется.

Первый шаг. Делает ли одна неудачная загрузка весь расчёт бесполезным? Если да, подходит asyncio.TaskGroup. Когда участник группы падает с любым исключением, кроме CancelledError, группа отменяет остальных участников и дожидается своих задач, прежде чем завершить выход. Одно легко упустить. Сам вызов корутинной функции ничего не планирует, поэтому загрузки нужно оформить как задачи в группе.

Второй шаг, и решение здесь другое. Обе задачи вернулись нормально, но веса не годятся. Сбор завершён. Теперь посмотрим, что пришло. Отклонить входные данные, запросить замену или выдать частичный результат? TaskGroup это за нас не решает.

Третий шаг, отмена со стороны вызывающего. Код очистки в загрузках должен пропускать CancelledError дальше. Если его проглотить, поведение отмены в группе может нарушиться.

Для этого приложения я выбираю TaskGroup, потому что сбой любой загрузки завершает общий запрос, а область должна дождаться своих участников. Проверка полученных данных идёт следом, до любого расчёта. Это и есть то конкретное, что сравнение с другими языками должно сохранить.

Разговор продолжается 4

Следуя доводам
  1. Ссылка на ответ

    Небольшой эксперимент на вашей второй остановке. Измерения оставим как есть, а запрос весов пусть вместо исключения вернёт []. Для TaskGroup это два обычных возврата. Отмены нет, ничего не всплывает, и результатом становится то, что шаг объединения сделает с пустым списком.

    Если оценка считается как взвешенное среднее, становится интересно. В Python деление на пустую сумму весов даёт ZeroDivisionError. В Julia та же арифметика на пустых векторах Float64 молча возвращает NaN.

    Теперь опустошим и измерения. Две пустые коллекции могут означать вполне законный пустой расчёт, а могут означать, что обязательный вход тихо пропал. Группа задач их не различит. Так что сначала стоит решить, что значит пустота для каждого входа, и только потом выбирать, какие неподходящие результаты считать сбоем.

  2. Ссылка на ответ

    Запросов два. Это вся популяция работы, так что пул воркеров здесь был бы просто церемонией.

    Проследить стоит тот запрос, который ещё выполняется. Соседняя задача падает, группа отменяет этот запрос и потом всё равно ждёт. Отмена здесь означает просьбу, не больше. Когда она приходит, запрос может читать из сокета или сидеть в блоке очистки. Завершён он тогда, когда задача действительно закончилась. В Go у errgroup.WithContext та же форма. Контекст отменяется на первой ошибке, а Wait всё равно блокируется, пока не вернутся все горутины.

    Поэтому требование у меня одно. Объединённый запрос не покидает свою область, пока хоть один участник работает. Если кому-то нужны уже пришедшие измерения, запишите, можно ли ими пользоваться после того, как группа сообщила о сбое. Сейчас об этом нигде не сказано.

  3. Ссылка на ответ

    Слово одно, передача управления разная. В Lua 5.4 coroutine.create возвращает сопрограмму, которая ещё не выполнила ни строчки. Хост вызывает resume. Она работает до yield. Потом стоит, пока кто-нибудь снова её не возобновит.

    Кто возобновит её дальше? Тот код, который написал хост. Никто не ждёт соседа и не отменяет его за вас. Поэтому сравнивайте возможности языков с тем, что TaskGroup делает в вашем расчёте, то есть ждёт участников и отменяет их при сбое. Общее слово эти свойства не переносит.

Другие стороны того же вопроса

  1. Смена языка конфигурации не лечит дрейф

    Обычный довод за то, чтобы заменить файлы HCL языком общего назначения, это циклы и функции. Иногда он справедлив. Но вместе…

    Ответы 4hcl