lesson01.md 8.9 KB

Занятие 1. Контексты

Оглавление

Что это такое

Передача данных от обработчика к обработчику требует сохранения промежуточного состояния -- контекста. Контекст можно расширять или сужать, но по частям он смысла не имеет.

Контекст -- весь необходимый набор данных для выполнения операций.

Кроме полезных данных -- контекст содержит сопровождающие данные, например метрики.

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

Посмотреть запись

Зачем нужно

  • накопление, удаление обрабатываемых данных между запросами;
  • контроль родительского процесса за дочерними;
  • охрана запросов в реальном времени за внешними ресурсами.

Примеры использования

Создание глобального контекста

ctxBg := context.Background()

Глобальный контекст использовать нельзя!

Почитать про API

Обратить внимание:

  • сработка контекста по условию;
  • контекст с предельным моментом времени;
  • проброс в контекст объектов по ключу;
  • контекст TODO (когда ещё неизвестно какой именно контекст нужен).

Также следует внимательно посмотреть на тип type Context interface{} -- поскольку это интерфейс -- открываются широкие возможности по реализации свои специфичных контекстов, которые будут совместимы с контекстом из стандартной библиотеки.

Дочерний контекст с простой отменой

Дочерний контекст с простой отменой. Он используется тогда ,когда время выполнения операции не важно.

Пример:

// FindRecord -- найти запись по её имени
func FindRecord(ctxApp app.AppContext, numRecord string) error {
    // Дочерний контекст с простой отменой
    ctxWork, fnCancel := context.WithCancel(ctxApp)
    // Обязательно отменить контекст при выходе, иначе будет утечка памяти
    defer fnCancel()

    // Канал, который по факту ничего не передаёт.
    // Но его закрытие будет сигналом об окончании работы конкурентного потока
    chWork := make(chan int, 2)

    // Функция-обработчик. Должна работать конкурентно.
    fnRead :=func(){
        // Обязательно закрыть канал при выходе. Это:
        // 1) Сигнал об окончании работы в любом случае
        // 2) Если канал не закрыть -- будет утечка памяти
        defer close(chWork)
        err:=file.FindRecord(numRecord)
        if err!=nil{
            ctxApp.Cancel()
        }
    }
    go fnRead() // Запустить конкурентно
    select { // Устроить соревнование между контекстом и выполняемым запросом
        case <-ctxApp.Done(): // Отмена контекста приложения, всё остальное уже не интересно
            return fmt.Errorf("FindRecord(): cancel ctxApp")
        // Ожидание окончания работы, всё-равно
        // когда-нибудь закончится так или иначе.
        case <-chWork:
            return nil
    }
}

Дочерний контекст с таймаутом внешнего ресурса

Дочерний контекст с таймаутом полезен тогда, когда время на исполнение запроса ограничено.

Типичные случаи использования:

  • пользователь ждёт ответа;
  • данные за конкретный промежуток времени успевают гарантированно протухнуть;
  • отвалился или завис внешний ресурс;
  • запрос слишком сильно тормозит очередь и надо его поторопить.

    
    // LocalStorage -- локальное хранилище ключ-значение
    type LocalStorage interface {
    // Получить значение п оключу
    Get(string) (*IndexResponse, error)
    }
    
    // IndexRequest -- запрос индексной страницы
    type IndexRequest struct {
    Name string // Имя на индексной странице
    }
    
    // IndexResponse -- ответ на запрос инжексной страницы
    type IndexResponse struct {
    // Определение полей структуры IndexResponse
    }
    
    var (
    db LocalStorage // Объект локального хранилища
    )
    
    // Index -- запрос индексной страницы
    func Index(ctx context.Context, request *IndexRequest) (*IndexResponse, error) {
    
    // Дочерний контекст и ег офункция отмены
    ctxTimeout, fnCancel := context.WithTimeout(ctx, time.Millisecond*500)
    // Обязательный вызов отмены контекста при выходе, иначе будет утечка памяти!!
    defer fnCancel()
    
    type Resp struct {
        resp *IndexResponse // Полезный ответ (если нет ошибки)
        err error           // Ошибка (если была)
    }
    
    chResp := make(chan *Resp, 1) // Канал для получения результата после обработки
    
    // Эта функция должна работат ьв отдельном потоке. Иначе невозможно
    // устроить соревнование между глобальным контекстом и дочерним.
    fnWork := func() {
        defer close(chResp) // Обязательн озакрыт ьканал при выходе
        resp, err = db.Get(request.Name)
        if err != nil { // Была ошибка -- вернём только ошибку
            resp := &Resp {
                err: err,
            }
            chResp <- resp
            return
        }
        resp := &Resp { // Ошибки не было -- значит вернём только результат.
            err: err,
        }
        chResp <- resp
    }
    
    go fnWork() // Обязательно! Запуск в отдельном потоке
    
    select { // Здесь гонки между таймаутом и полезным результатом -- кто быстрее.
        case <-ctxTimeout.Done(): // Был ли таймаут дочернего контекста?
            return nil, fmt.Errorf("Index(): timeout in make response")
        case result := <-chResp: // Что там с результатом?
            if result.err != nil { // Увы. но была ошибка
                return nil, fmt.Errorf("Index(): in make response, err=\n\t%w", result.err)
            }
            // Всё ок, возвращаем полезный результат
            return result.resp, nil
    }
    }