# Занятие 1. Контексты ## Оглавление - [Занятие 1. Контексты](#занятие-1-контексты) - [Оглавление](#оглавление) - [Что это такое](#что-это-такое) - [Зачем нужно](#зачем-нужно) - [Примеры использования](#примеры-использования) - [Создание глобального контекста](#создание-глобального-контекста) - [Дочерний контекст с простой отменой](#дочерний-контекст-с-простой-отменой) - [Дочерний контекст с таймаутом внешнего ресурса](#дочерний-контекст-с-таймаутом-внешнего-ресурса) ## Что это такое Передача данных от обработчика к обработчику требует сохранения промежуточного состояния -- контекста. Контекст можно расширять или сужать, но по частям он смысла не имеет. Контекст -- весь необходимый набор данных для выполнения операций. Кроме полезных данных -- контекст содержит сопровождающие данные, например метрики. Также контексты в подавляющем большинстве случаев -- связаны между собой отношением `предок-потомок`, и позволяют прокидывать сверху вниз наборы данных и событий. [Посмотреть запись](https://disk.yandex.ru/d/jrm62aMT6eGsNw) ## Зачем нужно - накопление, удаление обрабатываемых данных между запросами; - контроль родительского процесса за дочерними; - охрана запросов в реальном времени за внешними ресурсами. ## Примеры использования ### Создание глобального контекста ```go ctxBg := context.Background() ``` **Глобальный контекст использовать нельзя!** [Почитать про API](https://pkg.go.dev/context@go1.20.7) Обратить внимание: - сработка контекста по условию; - контекст с предельным моментом времени; - проброс в контекст объектов по ключу; - контекст `TODO` (когда ещё неизвестно какой именно контекст нужен). Также следует внимательно посмотреть на тип `type Context interface{}` -- поскольку это интерфейс -- открываются широкие возможности по реализации свои специфичных контекстов, которые будут совместимы с контекстом из стандартной библиотеки. ### Дочерний контекст с простой отменой Дочерний контекст с простой отменой. Он используется тогда ,когда время выполнения операции *не важно*. Пример: ```go // 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 } } ``` ### Дочерний контекст с таймаутом внешнего ресурса Дочерний контекст с таймаутом полезен тогда, когда время на исполнение запроса ограничено. Типичные случаи использования: - пользователь ждёт ответа; - данные за конкретный промежуток времени успевают гарантированно протухнуть; - отвалился или завис внешний ресурс; - запрос слишком сильно тормозит очередь и надо его поторопить. ```go // 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 } } ```