# scheduler Пакет `scheduler` реализует планировщик системы. `scheduler.Scheduler` хранит полное состояние кластера и принимает решение на каком воркере и какой джоб нужно запустить. Шедулер является точкой координации между воркерами и билдами. Бегущие билды обращаются к шедулеру, передавая джобы в функцию `ScheduleJob`. Воркеры забирают джобы из шедулера вызывая функцию `PickJob`. После того, как воркер завершил выполнение джоба, он вызывает функцию `OnJobComplete`. Эту функцию могут вызвать даже для того джоба, который никто не шедулил. В этом случае планировщик просто должен запомнить, что результаты джоба сохранены в кеше на воркере. Фукнция `LocateArtifact` должна возвращать имя любого воркера, который хранит в кеше заданный артефакт. Эта функция не нужна в этой задаче, но он потребуется вам для реализации передачи артефактов между воркерами. Для того, чтобы зачесть домашнее задание, достаточно реализовать упрощённый алгоритм планирования с одной глобальной очередью. Функция `ScheduleJob` должна помещать `job` в очередь или возвращать ссылку на существующий `pendingJob`. Функция `PickJob` должна извлекать первый элемент из очереди. Обратите внимание, что функция `PickJob` принимает контекст. Поскольку это блокирующая операция, она должна поддерживать отмены. Если вы забудете реализовать отмену в этом месте, интеграционные тесты будут зависать. ## Алгоритм планирования *Далее описывается продвинутый алгоритм планирования. Алгоритм проверяется в отдельной задаче `smartsched`. Для зачёта по домашнему заданию, реализовывать этот алгоритм необязательно.* Планировщик поддерживает множество очередей: 1. Одна глобальная очередь. 2. По две локальные очереди на воркер. При запросе нового джоба воркер выбирает случайную джобу из трех очередей - глобальной и двух локальных, относящихся к этому воркеру. Случайная очередь выбирается одним вызовом `select {}`. Ожидающий исполнения джоб всегда находится в первой локальной очереди воркеров, на которых есть результаты работы этого джоба. Если выполнено одно из следующих условий, то джоб попадает во все вторые локальные очереди воркеров, на которых есть хотя бы один артефакт из множества зависимостей этого джоба: 1) В момент вызова `ScheduleJob` джоба не было в кеше ни на одном из воркеров 2) Джоб ждёт выполнения дольше `CacheTimeout` Определения первой и второй локальной очереди не зависят от того, в каком порядке в шедулер пришли джобы и информация о кеше артефактов. То есть, если джоб уже находится в глобальной очереди и в этот момент приходит информация, что этот джоб находится в кеше на воркере w0, то джоб должен быть добавлен в первую локальную очередь w0. Если джоб ждёт выполнения дольше `DepsTimeout`, то он помещается в глобальную очередь. Отсчет этого таймаута начинается уже после обработки предыдущего условия, то есть не нужно вычитать из `DepsTimeout` никакое другое число. ## Тестирование Вместо реального времени, юниттесты шедулера используют библиотеку `clockwork`. Это накладывает ограничения на детали вашей реализации. Ожидание `CacheTimeout` и `DepTimeout` должно быть реализовано как `select {}` на канале, который вернула функция `TimeAfter`. Мы считаем, что `CacheTimeout < DepsTimeout`, и ожидание этих таймаутов происходит отдельно в двух разных вызовах `select {}`: ``` select { case <-TimeAfter(timeout): ... ... } ``` Среди двух условий попадания во вторые локальные очереди, если выполнено первое из них, делать ожидание `CacheTimeout` через `select {}` не нужно, иначе ваша реализация может проходить тесты с недетерминированным исходом.