Warum Context in Go praktisch ist

Ich glaube jeder der mal mit Go gearbeitet hat, kam irgendwann an den Punkt wo er sich dachte: Wofür context.Context und wieso brauche ich das? Und jetzt soll ich das auch noch überall durchschleifen. Was anfänglich mühselig und eher “verbose” sich anfühlt, stellt sich über länger eher als nützlich aus.

Was ist context.Context in Go?

Im Package context geht es vornehmlich um den Typen Context welcher dazu verwendet wird deadlines, Abbruchsignale oder Request gebundene Variablen durchzureichen. D.h. für Dich: Du kannst Anfragen die im Rahmen eines Requests stattfinden entsprechend abbrechen, Timeouts setzen und ähnliches. Auch kannst du Metainformationen innerhalb deiner Anwendung durchreichen - User ID, Request ID, etc.

Simples Beispiel:

ctx, _ := context.WithTimeout(context.Background(), time.Second * 10)
req, _ := http.NewRequest("GET", url, nil)
req = req.WithContext(ctx)

Hier würde der Request abgeborchen werden, wenn er nicht innerhalb von 10 Sekunden beendet ist.

Typische Fehler vermeiden

Gerade am Anfang macht man einige “typische” Fehler, wir verteufeln hier jetzt nichts und niemanden. Diese Fehler machen wir alle mal, wichtiger ist, dass wir sie nicht wiederholen.

  1. Context komplett ignorieren, ihn nicht durchreichen. Es kann nervig sein, aber reicht ihn überall durch, wo es Sinn macht.
  2. context.Background - resultiert meist aus Fehler 1. Es gibt Momente wo es Sinn macht, wenn man den Background Context nutzt - also einen Context der an die Applikations Laufzeit gebunden ist und somit beendet wird, wenn z. Bsp. euer Web-Server beendet wird. Datenbankverbindungen sind hier ein guter Kandiat. Die sind in der Regel nicht an einen Reuqest gebunden und sollten zwecks Connectionpooling auch die ganze Lebenszeit der Anwendung laufen.
  3. Vollstopfen. Da geht noch mehr! context.Context ist generisch gehalten, in der Theorie könnt ihr alles durchschleifen was euch lieb ist, versucht das jedoch zu vermeiden. Am Ende müllt man sich den Context zu und es so ein geschmäckle von globalen Variablen. Beschränkt Euch Metadaten.
  4. Kein Cancel. Schielt nach oben zum “simplen Beispiel”. context.WithTimeout gibt als zweites Argument cancel zurück, ich hab es mal unterschlagen. Ruft es immer auf, so das Resourcen freigegeben werden können, sollte es mal schneller gehen als gedacht. Besser wäre also so
    ctx, cancel := context.WithTimeout(context.Background(), time.Second * 10)
    defer cancel()
    req, _ := http.NewRequest("GET", url, nil)
    req = req.WithContext(ctx)

Best practises

  • ✅ Context immer an untergeordnete Funktionen weitergeben
  • ✅ Nutze context.WithTimeout() oder context.WithDeadline() bei externen Calls
  • ✅ Ruf cancel() immer auf
  • ✅ Halte dich an die Signatur: func(ctx context.Context, …) - Ich mag es ordentlich und gleich. Daher hat context.Context bei mir immer Fastlane in den Signaturen.

Fazit

Ich machs kurz: context.Context gut. Ignorieren böse.