Передача данных от обработчика к обработчику требует сохранения промежуточного состояния -- контекста. Контекст можно расширять или сужать, но по частям он смысла не имеет.
Контекст -- весь необходимый набор данных для выполнения операций.
Кроме полезных данных -- контекст содержит сопровождающие данные, например метрики.
Также контексты в подавляющем большинстве случаев -- связаны между собой отношением предок-потомок
, и позволяют прокидывать сверху вниз наборы данных и событий.
ctxBg := context.Background()
Глобальный контекст использовать нельзя!
Обратить внимание:
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
}
}