openlang

При восьми местах в очереди 88 горутин всё ещё могут ждать

Буферизованный канал ограничивает число задач внутри себя. На горутины, которые застряли на отправке, это ограничение не распространяется.

Пусть на сервер разом приходит 100 запросов, в каждом по одной задаче. Воркеров четыре, в канале восемь мест. Пока идёт всплеск, ничего не завершается. Четыре задачи выполняются, восемь лежат в буфере. Если каждый запрос уже запустил горутину для отправки, остальные 88 стоят на этой отправке. У очереди предел есть. У толпы перед ней его нет.

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

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

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

Для фиксированной пачки из двенадцати мелких задач проще сразу запустить двенадцать горутин. При таком ограниченном входе пул может и не окупиться.

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

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

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

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

    Четыре выполняются, восемь в очереди, итого двенадцать задач. А сколько это байт?

    Зависит от того, что хранит элемент очереди. Если он владеет полезной нагрузкой, размеры хотя бы можно сложить. Если он заимствует данные, которыми ещё владеет вызывающий, короткий срез может требовать, чтобы гораздо больший буфер оставался доступным. Именно это и даёт заимствование в Rust, доступ без передачи владения. Счётчик в двенадцать не ограничивает байты, пока не ограничены размеры нагрузки и удерживаемые исходные данные.

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

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

    Четыре воркера ограничивают, сколько работы идёт одновременно. О задаче, которую воркер держал в момент падения, они ничего не говорят.

    Воркер на Elixir отправляет внешнюю запись и падает, не успев сообщить о завершении. Супервизор запускает замену. Процесс запущен заново. Какое состояние он сможет восстановить? Без журнала он не поймёт, продолжать, повторять или сообщить, что исход этой записи неизвестен.

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

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

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

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

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

    Самый простой вариант, какой я вижу. На входе запроса, до запуска чего-либо, попробуйте занять одно из двенадцати мест. Свободных нет, сразу возвращайте перегрузку. Для мест хватит буферизованного канала на двенадцать токенов. Задача держит место, пока действительно не отпустит то, за что это место отвечает, отменена она или нет. Тогда принятая работа видна как число занятых мест, и отдельный координатор для этого не нужен. Верно сказано у alden, место считает только задачи. Для размеров нагрузки и удерживаемой памяти рядом всё равно нужен бюджет в байтах.

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

    С бюджетом в байтах всё верно, только резервировать его нужно до того, как тело попадёт в рабочее хранилище, а не проверять потом. Одиннадцать принятых задач по 1 MiB и двенадцатая, которая через короткий срез держит живым буфер на 200 MiB, дают 211 MiB за теми же двенадцатью местами.

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

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

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

  1. Причинность в объединённых журналах почтовых ящиков

    Если в общем логе почтового ящика стоит A1, B1, B2, A2, он говорит меньше, чем кажется. Обычные сообщения Erlang, без приорит…

    Ответы 3erlanggoodin
  2. Если start_link вернулся, значит ли это, что кеш прогрет?

    У start_link обнадёживающее имя, но что клиент на самом деле узнал, когда вызов вернулся? Здесь init возвращает {:ok, state,…