Documentation
¶
Overview ¶
Package servicehost — НОСИТЕЛЬ входящего пути: одна точка входа, поднимающая оба слушателя сервиса с одним и тем же контуром работы с владельцем прав.
Что здесь механизм, а что правило ¶
Serve возвращает `error`, а НЕ сервер, и регистрация получает `grpc.ServiceRegistrar` — интерфейс с единственным методом. Поэтому у вызывающего не остаётся объекта, к которому можно приделать своё звено: восьмой сервис, желающий собственный порядок цепочки, не «нарушит правило» — ему не на чем его записать. Это свойство ПОСТРОЕНИЯ.
Порядок звеньев внутри самой функции построением не держится — его держит то, что функция ОДНА и правка её видна как правка контура, а не как правка сервиса. Это честная граница, и выдавать её за невозможность нельзя.
Чем этот пакет отличается от `pkg/servicecontract` ¶
Тот решает, ЧТО МОЖНО ЗАПИСАТЬ, и его отказы — свойства самого дескриптора (О1, О6, О7, О8, О10). Здесь живут отказы, которым нужен СЛУЖИМЫЙ НАБОР RPC и выведенный из него каталог прав: О2, О3, О4, О5, О9. Разделение не косметическое: половину отказов невозможно вычислить, пока не известно, что процесс реально служит.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func IsGatedMutation ¶
IsGatedMutation сообщает, записывает ли этот метод намерение регистрации, — то есть подпадает ли он под загрузочный гейт.
Форма — `/<пакет>.<Служба>/Create`, кроме служб `Internal*`: админ-каталог (типы дисков, регионы, зоны) владельца не заводит и намерения регистрации не порождает, поэтому гейтить его значило бы закрыть админ-путь по причине, к нему не относящейся.
Чтение — по служимому имени метода, а не по перечню: восьмой ресурс попадает под гейт в момент появления, а не тогда, когда кто-нибудь вспомнит дополнить список.
Сколько копий этого предиката в дереве СЕГОДНЯ — четыре, а не одна ¶
Прежняя редакция этой шапки говорила «перенесён из трёх одинаковых копий, живших по одной в каждом сервисе», и это описывало НАМЕРЕНИЕ фазы, а не дерево: три копии стоят на месте (`services/{vpc,compute,nlb}/internal/fgaboot`), каждая дословно совпадает с этой. Здесь заведена ЧЕТВЁРТАЯ — канонная, — а три снимаются вместе с переводом своих сервисов на носитель, не раньше: снять их сейчас значило бы оставить сервисы без гейта до самого перевода. Читающий эту строку и решивший, что дублей нет, не пошёл бы их искать — поэтому счёт назван.
Предикат ЭКСПОРТИРОВАН ради второго читателя: сервис вправе спросить у носителя, есть ли у него вообще гейтируемая мутация, — и заявление «гейт мне не нужен» тогда судится тем же кодом, который гейт исполняет, а не своей копией предиката в пробе сервиса.
func Serve ¶
func Serve(ctx context.Context, d servicecontract.Descriptor, public, internal Registrar) error
Serve поднимает ОБА слушателя и обслуживает их до отмены ctx.
Порядок, и он несущий: собрать серверы → зарегистрировать → снять СЛУЖИМЫЙ набор у самих серверов → вывести каталог и карту прав → прогнать отказы старта → и только потом слушать. Ни одно соединение не принимается раньше, чем отработали все десять отказов.
func ServeSurface ¶
func ServeSurface(ctx context.Context, d servicecontract.SurfaceDescriptor) (func() error, error)
ServeSurface поднимает НЕ-gRPC поверхность: привязывает порт СИНХРОННО и возвращает ожидание, которое завершается ТОЛЬКО ПОСЛЕ ТОГО, как порт освобождён.
Почему привязка синхронна, а обслуживание — нет ¶
Отказ привязки и остановка обслуживания — разные события, и вызывающему они нужны в разные моменты. Первое означает «поверхности не будет вовсе» и обязано прийти ДО того, как процесс объявит себя поднявшимся: занятый порт — это ошибка посадки, а не режим работы. Второе приходит на выходе и означает «порт свободен, можно завершаться».
Одна блокирующая функция это различие стирала: вызывающему приходилось звать её в горутине (иначе некому обслуживать gRPC) и читать исход в конце — то есть отказ привязки становился кодом возврата процесса, который до этого успешно проработал сколько угодно. Ровно этот дефект здесь и описан как прежний у плоскости данных реестра, и он воспроизвёлся у трёх сервисов сразу — потому что верное использование зависело от дисциплины вызывающего, а не следовало из сигнатуры.
Теперь неверное использование непредставимо: пока функция не вернулась, порт либо занят нами, либо назван в ошибке.
Профиль той же функции: что здесь то же, а что другое ¶
То же: объявление приносится ДАННЫМИ (servicecontract.SurfaceDescriptor), отказы отработали до первого принятого соединения, вызывающему не остаётся объекта, к которому можно приделать своё. Другое: цепочки звеньев здесь нет — у HTTP-поверхности своя аутентификация внутри обработчика либо объявленное её отсутствие, и подмешивать сюда gRPC-цепочку было бы не унификацией, а прочтением намеренно узкой семантики за общий случай.
Почему возвращается ОЖИДАНИЕ, а не пара «запусти» + «погаси» ¶
Пара возвращала вызывающему обязанность позвать гашение, и обязанность эта разошлась: из шести диагностических слушателей дерева двое гасились контекстом БЕЗ СРОКА (то есть ждали последний скрейп неограниченно), двое — сроком дренажа исполнителей операций, а плоскость данных реестра поднималась вовсе мимо синхронной привязки порта — ошибка занятого адреса там попадала в журнал, и процесс продолжал работать, объявляя себя исправным.
Здесь гашение — не обязанность вызывающего, а СВОЙСТВО функции: слушатель живёт ровно столько, сколько живёт ctx, и пережить его не может, потому что сервер вызывающему не отдан. Слушатель, переживший отмену, держит порт — и следующий старт того же процесса спотыкается о собственный предыдущий.
Возвращённое ожидание обязанностью гасить не является: гасит отмена ctx. Оно отвечает на один вопрос — «порт уже свободен?» — и потому идемпотентно.
Порядок гашения, и почему он именно такой ¶
- отмена ctx;
- `Shutdown` со СВЕЖИМ контекстом на объявленный срок — свежим потому, что ctx уже отменён, и передать его сюда значило бы отменить гашение в тот же миг, в который оно началось (запросы в полёте оборвались бы);
- по истечении срока — `Close`: гашение не бывает бесконечным, и ждать дольше объявленного значит держать порт вопреки объявлению.
Возврат происходит после шага 3, а не после шага 1: вызывающий, дождавшийся этой функции, знает, что порт свободен.
Types ¶
type Registrar ¶
type Registrar func(grpc.ServiceRegistrar)
Registrar приносит ТОЛЬКО обработчики.
Тип не позволяет добавить звено — это и есть фиксированный слот. Аргумент — `grpc.ServiceRegistrar`, интерфейс с единственным методом `RegisterService`; сервера вызывающий не получает ни в каком виде.
Регистратор вызывается РОВНО ОДИН раз на слушатель. «Позовём дважды, он же чистый» контрактом не является: чистоту чужой функции проверить нечем, а побочный эффект во втором вызове проявился бы как удвоение фоновой работы.