programowanieBackend Go Developer

Jak działa wbudowany pakiet context w Go? Do czego jest używany, jak poprawnie tworzyć i kończyć konteksty? Jakie błędy są popełniane przy jego stosowaniu?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

Pakiet context to standard do zarządzania cyklem życia (anulowanie, timeout) oraz przekazywania metadanych między częścią kodu, szczególnie przy pracy z goroutine i zewnętrznymi wywołaniami (HTTP, DB).

Tworzenie:

  • context.Background() — kontekst główny
  • context.WithCancel(parent) — nowy z anulowaniem
  • context.WithTimeout(parent, duration) — z timeoutem
  • context.WithValue(parent, key, value) — z danymi

Kończenie kontekstu jest ważne dla poprawnego uwolnienia zasobów. Przykład:

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() // dalej ctx jest używane w goroutine/żądaniu HTTP

Typowy wzorzec — przekazywanie kontekstu jako pierwszego argumentu funkcji, przykład:

func process(ctx context.Context) error { ... }

Pytanie podchwytliwe

Kiedy można przekazywać context.TODO() i context.Background() zamiast rzeczywistego kontekstu, i jaka jest różnica między nimi?

Wielu używa context.Background() jako placeholder. Jednak:

  • context.Background() — korzeń drzewa, używaj tylko w main() i testach, podczas inicjalizacji samego "drzewa kontekstów".
  • context.TODO() jest potrzebny, gdy nie jest jeszcze jasne, jaki powinien być kontekst — do tymczasowego "zakrywania dziur" podczas refaktoryzacji. W kodzie produkcyjnym TODO jest nieakceptowalne: trzeba dokładnie wiedzieć, co i gdzie jest przekazywane.

Przykłady rzeczywistych błędów z powodu braku znajomości niuansów tematu


Historia

W mikrousłudze zapomniano wywołać cancel() po stworzeniu context.WithTimeout() — zapytania stawały w miejscu, zakończone goroutine zostawiały "wiszące" timery w runtime, co prowadziło do wycieków pamięci.


Historia

Przekazywanie context.Background() zamiast przychodzącego z żądania kontekstu w handlerach traciło całą sieć anulowania i trace-id, przez co ograniczenia timeout nie działały, anulowanie zapytań nie zachodziło.


Historia

Poprzez context.WithValue() deweloperzy przekazywali dane, ale klucze były stringami. W rezultacie, z powodu możliwych kolizji kluczy z różnych pakietów, występowały nieoczekiwane błędy ("klucz już zajęty"). Poprawna praktyka: używać unikalnych typów dla kluczy.