Tym razem zapraszam na nagranie o używaniu i stosowaniu Context (po polsku: kontekst - jakby nie patrzeć) w Go. Ten osobliwy interfejs, który w bibliotece standardowej pojawił się w sumie całkiem niedawno, posiada kilka ważnych zastosowań, z czego spróbuję te najciekawsze omówić na filmie. Z tego też względu, zdecydowałem się na zupełnie osobny odcinek o tym zagadnieniu w moim kursie programowania w Go. Tematykę tego odcinka można potraktować jako uzupełnienie odcinka 13 o goroutines i wielowątkowości.

Go i Context

Wstęp

Z samej dokumentacji języka Go, konkretnie paczki context, dowiemy się, że ten package (ten, który zawiera ten interfejs) definiuje typ Contextu, który jakby “transportuje” sygnały związane z deadline zadanych akcji (twardym limitem czasowym), cancellation akcji (ich przerywaniem) i innego tego typu informacjami, którymi operuje pojedynczy request (np. żądanie http). Służy to “łączeniu” wielu endpointów API oraz procesów, w taki sposób, aby w pewnych sytuacjach można było reagować na pewne specyficzne przypadki, które mogą zajść w aplikacji (np. nagłe zerwanie połączenia tcp).

W każdym razie, można to sobie wyobrazić jako hipotetyczne urządzenie, które np. wystrzeliwuje jednocześnie dużą ilość piłek przyczepionych do niego gumkach/linkach, a które to linki/gumki mają różną długość i elastyczność. Tu dla uproszczenia, zakładamy, że urządzenie wystrzeliwuje każdą piłkę z identyczną siłą i prędkością. Jedna linka może mieć długość 1m, druga 2m, trzecia 10m, z kolei elastyczność może być taka, że linka o długości 1m może się naciągnąć aż do 5m, a linka o długości 10m od razu pęknie, bo takiego “zapasu” mieć nie będzie. To urządzenie musi jednak “obsłużyć” wszystkie ich długości, elastyczności i “wykrywać”, aby np: nie czekać za długo na piłkę, na lince o długości 10m, bo jeśli nie wróci w czasie ustalonym dla takiego scenariusza siły wyrzutu, długości i elastyczności 10m linki, to na pewno linka “pękła” i nie ma co “czekać” na powrót piłki po wystrzale. Ten mechanizm, który pozwala urządzeniu w “orientowaniu” się w takiej sytuacji, te linki i algorytmy, pod tym możemy rozumieć właśnie kontekst.

Kompletnie nie wiem czy ta karkołomność przełożenia tego na nasz język w moim wykonaniu jest poprawna. Na pewno jest mocno eksperymentalna. W każdym razie mój opis może zabijać na miejscu strachliwych przed zdaniami wielokrotnie złożonymi, ale sam Context może być również używany do synchronizowania goroutines, niekoniecznie jednak w każdej sytuacji będzie to “optymalne” zastosowanie.

Nagranie z wyjaśnieniem interfejsu Context



Kod do prostego przykładu zastosowania Context

 1package main
 2
 3import (
 4	"context"
 5	"fmt"
 6	"time"
 7)
 8
 9// based on http://jgao.io/
10func main() {
11	fmt.Println("Program started...")
12	start := time.Now()
13
14	ch := make(chan struct{})
15
16	// Initialise top-level context
17	ctx := context.Background()
18
19	// Context to pass to function
20	// contextWithTimeout, cancelTimeout := context.WithTimeout(ctx, 3100*time.Millisecond)
21	// go computation(contextWithTimeout, 3, "Timeout", ch)
22
23	contextWithDeadline, cancelDeadline := context.WithDeadline(ctx, time.Now().Add(4*time.Second))
24	go computation(contextWithDeadline, 3, "Deadline", ch)
25
26	defer cancelDeadline()
27
28	select {
29	case val := <-ch:
30		fmt.Println("Got result: ", val)
31		// case <-contextWithTimeout.Done():
32		// 	fmt.Println("Computation cancelled by timeout.")
33	case <-contextWithDeadline.Done():
34		fmt.Println("Computation cancelled by deadline.")
35		// default:
36		// 	cancel()
37	}
38
39	elapsed := time.Since(start)
40	fmt.Printf("Program finished... It took %s \n", elapsed)
41}
42
43func computation(ctx context.Context, seconds int, name string, c chan struct{}) {
44	fmt.Printf("%s: computing started...\n", name)
45
46	time.Sleep(time.Duration(seconds) * time.Second)
47	c <- struct{}{}
48
49	fmt.Printf("%s: computing finished...\n", name)
50}


Kod do przykładu z serwerem http

Do testowania tego przykładu użyj wybranego klienta http, np. curl, httpie, postman, itp.

 1package main
 2
 3import (
 4	"context"
 5	"io"
 6	"log"
 7	"net/http"
 8)
 9
10func main() {
11	ctx, cancel := context.WithCancel(context.Background())
12
13	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
14		io.WriteString(w, "Hi\n")
15	})
16
17	http.HandleFunc("/quit", func(w http.ResponseWriter, r *http.Request) {
18		io.WriteString(w, "Bye\n")
19		cancel()
20	})
21
22	srv := &http.Server{Addr: ":8123"}
23
24	go func() {
25		if err := srv.ListenAndServe(); err != nil {
26			log.Printf("Httpserver: ListenAndServe() error: %s", err)
27		}
28	}()
29
30	<-ctx.Done()
31
32	if err := srv.Shutdown(ctx); err != nil && err != context.Canceled {
33		log.Println(err)
34	}
35	
36	log.Println("done.")
37}