package entity import "time" // Work — чью работу ждёт запись, стоя на рубеже. От этого зависит предел // простоя: своя работа мерится одним числом, ожидание чужой операции — другим. // Граница проходит по исполнителю, а не по рубежу: число на каждый рубеж // назвало бы разными вещи, различающиеся только им. type Work int const ( // WorkOwn — работу делаем мы сами. WorkOwn Work = iota // WorkForeign — ждём операцию внешнего сервиса. WorkForeign ) // Stage — объявление рубежа одним местом. // // Из этого перечня выводятся все потребители: выбор следующего шага, отбор // захвата, срок протухания захвата и предел простоя. Перечислять рубежи порознь // в каждом потребителе нельзя: рубеж, забытый в отборе захвата, не выдаётся ни // одному воркеру никогда, а пустой прогон по инварианту проекта не пишется в // журнал и не считается в метрику — запись встала бы без единого следа. type Stage struct { Name string // Work — чью работу ждём, стоя на этом рубеже. Work Work // AcquireTimeout — срок протухания захвата. Едет с рубежом, а не с воркером: // воркер не привязан к шагу и не знает заранее, что вытянет. AcquireTimeout time.Duration // Terminal — рубеж, из которого запись в работу не берут. Такой рубеж не // подпадает и под предел простоя: стоять в нём запись будет вечно по // построению. Terminal bool } // Сроки захвата. Каждый не меньше того, что его шаг может занять на самом // длинном допустимом входе: расчётный потолок записи — шесть часов, и приведение // такой записи идёт дольше часа по построению. const ( normalizeAcquireTimeout = 8 * time.Hour submitAcquireTimeout = 8 * time.Hour pollAcquireTimeout = time.Hour finishAcquireTimeout = time.Hour ) // stages — цепочка рубежей в порядке прохождения. var stages = []Stage{ {Name: StateUploaded, Work: WorkOwn, AcquireTimeout: normalizeAcquireTimeout}, {Name: StateNormalized, Work: WorkOwn, AcquireTimeout: submitAcquireTimeout}, {Name: StateSubmitted, Work: WorkForeign, AcquireTimeout: pollAcquireTimeout}, {Name: StateTranscribed, Work: WorkOwn, AcquireTimeout: finishAcquireTimeout}, {Name: StateDone, Terminal: true}, } // WorkingStages — рубежи, с которых запись берут в работу. func WorkingStages() []Stage { out := make([]Stage, 0, len(stages)) for _, s := range stages { if !s.Terminal { out = append(out, s) } } return out } // StageByName находит рубеж по имени. Второе значение ложно у рубежа, которого // в цепочке нет: запись с таким рубежом до шага не доходит. func StageByName(name string) (Stage, bool) { for _, s := range stages { if s.Name == name { return s, true } } return Stage{}, false } // ListFilter — состояние записи, по которому её отбирает список приложения. // // Состояний три, а не два, и это не педантизм. Остановленная запись не в работе // и не завершена: при отборе надвое она выпала бы из обеих половин — исчезла бы // из списка при любом значении отбора, — хотя ради неё человек список и // открывает. type ListFilter string const ( // ListFilterWorking — запись идёт по конвейеру. ListFilterWorking ListFilter = "working" // ListFilterHalted — запись остановлена признаком. ListFilterHalted ListFilter = "halted" // ListFilterDone — запись прошла конвейер. ListFilterDone ListFilter = "done" ) // ParseListFilter узнаёт состояние отбора по его имени. Второе значение ложно у // имени, которого в перечне нет: такой отбор — негодный ввод, а не пустая // выборка. func ParseListFilter(v string) (ListFilter, bool) { switch ListFilter(v) { case ListFilterWorking, ListFilterHalted, ListFilterDone: return ListFilter(v), true } return "", false } // TerminalStages — рубежи, из которых запись в работу не берут. // // Выводится из дескриптора наравне с WorkingStages: отбор списка — очередной // потребитель словаря рубежей, и перечислять их у него строкой запроса нельзя. // Рубеж, добавленный конвейером, иначе молча поменял бы состав всех трёх // состояний отбора. func TerminalStages() []Stage { out := make([]Stage, 0, len(stages)) for _, s := range stages { if s.Terminal { out = append(out, s) } } return out } // StageNames разворачивает рубежи в их имена — для запроса к хранилищу. func StageNames(list []Stage) []string { out := make([]string, 0, len(list)) for _, s := range list { out = append(out, s.Name) } return out } // StuckLimits — пределы простоя, приходящие из настроек. type StuckLimits struct { // Own — предел на своей работе. Own time.Duration // Foreign — предел на ожидании чужой операции. Foreign time.Duration } // Limit — предел простоя для этого рубежа. У конечного рубежа предела нет: // запись стоит в нём вечно по построению. func (s Stage) Limit(limits StuckLimits) (time.Duration, bool) { if s.Terminal { return 0, false } if s.Work == WorkForeign { return limits.Foreign, true } return limits.Own, true }