Модульні уроки (5/5)
`context.Context`: скасування та крайні терміни
Пакет context є стандартом де-факто для розповсюдження
скасування, кінцеві терміни та обмежені значення вздовж ланцюжка викликів,
у кількох горутинах. Всі стандартні "мережеві" пакети
(net/http, database/sql, os/exec) прийняти context.Context як
їх перший параметр.
Інтерфейс
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}Done(): канал, який закривається, коли контекст скасовано.Err(): причина скасування (context.Canceledабоcontext.DeadlineExceeded).Deadline(): необов’язковий крайній термін.Value(): пошук значень, пов’язаних із областю дії (використовуйте помірно).
Чотири основні функції
ctx := context.Background() // radice: usalo in main, init, test
ctx := context.TODO() // "non so ancora": stub esplicito
ctx, cancel := context.WithCancel(parent) // cancel manuale
ctx, cancel := context.WithTimeout(parent, d) // scadenza relativa
ctx, cancel := context.WithDeadline(parent, t) // scadenza assoluta
ctx := context.WithValue(parent, key, value) // valore scope-boundУсі похідні форми повертають функцію **cancel: завжди викликайте її
(в ідеалі з defer), щоб звільнити внутрішні ресурси, навіть якщо
контекст уже минув.
Шаблон тайм-ауту
func fetchUser(id string) (*User, error) {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
return queryUser(ctx, id) // queryUser deve rispettare ctx
}Якщо queryUser займає більше 2 секунд, ctx.Done() закривається і
функція повинна перервати операцію.
Дотримуючись контексту: виберіть Done()
func slowWork(ctx context.Context) error {
for {
select {
case <-ctx.Done():
return ctx.Err() // canceled o deadline exceeded
case <-time.After(100 * time.Millisecond):
// un tick di lavoro
}
}
}Правило: скрізь, де ваша функція може блокуватися на тривалий час, це має обробляти ctx.Done(). В іншому випадку контекст "увімкнено" для нічого.
Угода про перший параметр
func Fetch(ctx context.Context, url string) ([]byte, error) { ... }
// ^^^^^^^^^^^^^^^^^^^
// SEMPRE primo, mai dentro a una struct, mai opzionalectxЗАВЖДИ як перший параметр з назвоюctx.- Ніколи не ставте
Contextяк поле структури (дуже рідкісні винятки). - Ніколи не пропускайте
nil: використовуйтеcontext.TODO()як заглушку.
context.WithValue: помірно
type userKey struct{}
ctx := context.WithValue(ctx, userKey{}, currentUser)
// più sotto:
if u, ok := ctx.Value(userKey{}).(*User); ok { ... }Рекомендації:
- ЛИШЕ для даних області запиту (ідентифікатор запиту, ідентифікатор трасування, ідентифікатор користувача).
- НІКОЛИ для параметрів функції: передавайте їх звичайно.
- Ключі спеціального неекспортованого типу, щоб уникнути зіткнень.
Поширення по ланцюжку
func handleHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() // context legato al ciclo di vita della richiesta
user, err := fetchUser(ctx, r.URL.Query().Get("id"))
if err != nil { ... }
// ...
}net/http автоматично скасовує r.Context(), коли клієнт
відключається або коли обробник повертається. Розповсюджуючи це на всіх
низхідних викликів, усе дерево операцій скасовується
каскад: відсутність марної роботи після відключення.
Спробуйте
Створіть контекст із 1-секундним тайм-аутом, отриманим від context.Background() і не забудьте викликати скасування з відкладенням.
Показати підказку
`context.WithTimeout(parent, d)` повертає `(ctx, cancel)`; завжди скасувати виклик.
Рішення доступне після 3 спроб
Використовуйте <-ctx.Done() у select, щоб дочекатися скасування контексту.
Показати підказку
`ctx.Done()` — це канал, який закривається, коли контекст скасовується.
Рішення доступне після 3 спроб
За домовленістю, куди йде параметр ctx у сигнатурі функції?
func Fetch(???, url string) ([]byte, error)Підведення підсумків
context.Contextпоширює значення скасування, крайнього терміну та обсягу.Background()/TODO()як корінь;WithCancel/WithTimeout/WithDeadlineотримати.- Завжди
defer cancel()для звільнення ресурсів. - "Довгі" функції повинні
selectнаctx.Done()або передавати ctx стандартним пакетам. - Конвенція:
ctxяк ПЕРШИЙ параметр, ніколи не всередині структури. WithValueлише для даних із областю запиту (ідентифікатор запиту, ідентифікатор трасування).- У
net/httpr.Context()скасовується після відключення клієнта.