Skip to content

Экземпляр и система функций

core.Instance — это центральный объект, который содержит все функции (DNS, маршрутизация, менеджеры прокси и т. д.) и управляет их жизненным циклом.

Структура экземпляра

go
// core/xray.go
type Instance struct {
    statusLock                 sync.Mutex
    features                   []features.Feature
    pendingResolutions         []resolution
    pendingOptionalResolutions []resolution
    running                    bool
    resolveLock                sync.Mutex
    ctx                        context.Context
}

По сути, экземпляр представляет собой контейнер внедрения зависимостей. Функции регистрируют себя, а другие функции могут объявлять зависимости, которые разрешаются, когда все необходимые функции становятся доступны.

Интерфейс Feature

Каждый основной компонент реализует интерфейс features.Feature:

go
// features/feature.go
type Feature interface {
    common.HasType   // Type() interface{}
    common.Runnable  // Start() error, Close() error
}

Типы функций идентифицируются сигнальными значениями указателей:

go
func ManagerType() interface{} { return (*Manager)(nil) }
func ClientType() interface{}  { return (*Client)(nil) }

Последовательность инициализации

mermaid
sequenceDiagram
    participant Config
    participant Instance
    participant Feature
    participant InboundMgr
    participant OutboundMgr

    Config->>Instance: New(config)
    loop Для каждой конфигурации приложения
        Instance->>Instance: CreateObject(settings)
        Instance->>Feature: AddFeature(feature)
        Feature-->>Instance: Разрешение ожидающих зависимостей
    end
    Note over Instance: Авторегистрация значений по умолчанию<br/>(DNS, Policy, Router, Stats)
    Instance->>Instance: InitSystemDialer(dns, obm)
    Instance->>Instance: Проверка: все зависимости разрешены?
    loop Для каждой входящей конфигурации
        Instance->>InboundMgr: AddHandler(handler)
    end
    loop Для каждой исходящей конфигурации
        Instance->>OutboundMgr: AddHandler(handler)
    end
    Note over Instance: Экземпляр готов (не запущен)
    Instance->>Instance: Start()
    loop Для каждой функции
        Instance->>Feature: Start()
    end

Ключевые шаги в initInstanceWithConfig()

  1. Функции приложения: Перебор config.App (диспетчер, маршрутизатор, DNS, политики, статистика и т. д.), создание объектов через CreateObject(), регистрация в качестве функций.

  2. Обязательные значения по умолчанию: Если DNS, Policy, Router или Stats не были сконфигурированы, регистрируются реализации по умолчанию (no-op):

    go
    essentialFeatures := []struct{ Type, Instance }{
        {dns.ClientType(), localdns.New()},
        {policy.ManagerType(), policy.DefaultManager{}},
        {routing.RouterType(), routing.DefaultRouter{}},
        {stats.ManagerType(), stats.NoopManager{}},
    }
  3. Инициализация системного дайлера: Настройка глобального системного дайлера с DNS-клиентом и менеджером исходящих обработчиков (для DNS-разрешения в исходящем обработчике freedom).

  4. Проверка зависимостей: Если какие-либо pendingResolutions остались неразрешёнными, возвращается ошибка.

  5. Обработчики: Добавление всех входящих и исходящих обработчиков.

Разрешение зависимостей

Механизм RequireFeatures() обеспечивает ленивое внедрение зависимостей:

go
// Компонент объявляет, что ему необходимо:
core.RequireFeatures(ctx, func(om outbound.Manager, router routing.Router,
    pm policy.Manager, sm stats.Manager, dc dns.Client) error {
    return d.Init(config, om, router, pm, sm)
})

Когда вызывается RequireFeatures():

  1. Проверяется, зарегистрированы ли уже все типы параметров
  2. Если да, обратный вызов выполняется немедленно
  3. Если нет, сохраняется как resolution в pendingResolutions
  4. При каждом вызове AddFeature() повторно проверяются все ожидающие разрешения

Разрешение использует рефлексию для сопоставления типов параметров:

go
type resolution struct {
    deps     []reflect.Type      // требуемые типы функций
    callback interface{}         // func(features...) error
}

Создание объектов

CreateObject() — это фабричная функция, которая преобразует protobuf-конфигурации в объекты времени выполнения:

go
// Вызывается из core/config.go
func CreateObject(server *Instance, config interface{}) (interface{}, error) {
    ctx := context.WithValue(server.ctx, xrayKey, server)
    return common.CreateObject(ctx, config)
}

Это обращается к реестру конфигураций, где каждый protobuf-тип сопоставлен с функцией-конструктором, зарегистрированной через common.RegisterConfig().

Паттерн реестра конфигураций

Каждый компонент следует этому паттерну:

go
func init() {
    common.Must(common.RegisterConfig((*Config)(nil),
        func(ctx context.Context, config interface{}) (interface{}, error) {
            c := config.(*Config)
            // Создание объекта времени выполнения из конфигурации
            return NewHandler(ctx, c)
        }))
}

Protobuf-тип Config является ключом, а фабричная функция создаёт объект времени выполнения. Обёртка TypedMessage (protobuf Any) обеспечивает типобезопасную десериализацию.

Заметки по реализации

Для повторной реализации системы Instance:

  1. Необходим реестр функций, сопоставляющий типы функций с экземплярами
  2. Резолвер зависимостей, который вызывает обратные вызовы, когда все зависимости удовлетворены
  3. Десериализатор конфигураций, сопоставляющий типы конфигураций с конструкторами
  4. Жизненный цикл функции: регистрация -> разрешение зависимостей -> запуск -> закрытие

Подход на основе рефлексии может быть заменён явной регистрацией в статически типизированных языках:

instance.addFeature(dispatcherFeature)
instance.addFeature(routerFeature)
// Router видит, что dispatcher доступен, и разрешает свою зависимость

Технический анализ для целей повторной реализации.