• コンテキストにはアプリケーションを中断したり、複数のゴルーチンを制御したりもできる
  • 複数のゴルーチンが起動し、どれか一つがエラーを起こしたら場合に処理を中止することをキャンセレーション(cancellation) と呼ぶ
  • キャンセル可能なコンテキストを作成するにはcontext.WithCancelcontext.WithCancelCauseを使用する
    • キャンセル時の原因を知りたい場合はcontext.WithCancelCauseを使用し、context.Causeでキャンセレーションエラーを取得する
      • context.CauseはGo 1.20で追加された関数で、後方互換性を保つためにcontext.Contextのメソッドにはならなかった
package main
 
import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"sync"
	"time"
)
 
func makeRequest(ctx context.Context, url string) (*http.Response, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return nil, err
	}
	return http.DefaultClient.Do(req)
}
 
func main() {
	ctx, cancelFunc := context.WithCancelCause(context.Background())
	defer cancelFunc(nil) // main終了前にキャンセル用ゴルーチンを解放する(nil=正常終了)。解放しないとメモリリークが発生する                       
	ch := make(chan string)
	var wg sync.WaitGroup
	wg.Add(2)
 
	go func() {
		defer wg.Done()
		for {
			resp, err := makeRequest(ctx, "http://httpbin.org/status/200,200,200,500")
			if err != nil {
				cancelFunc(fmt.Errorf("in status goroutine: %w", err))
				return
			}
			if resp.StatusCode == http.StatusInternalServerError {
				cancelFunc(errors.New("bad status"))
				return
			}
			ch <- "success from status"
			time.Sleep(1 * time.Second)
		}
	}()
 
	go func() {
		defer wg.Done()
		for {
			resp, err := makeRequest(ctx, "http://httpbin.org/delay/1")
			if err != nil {
				fmt.Println("in delay goroutine:", err)
				cancelFunc(fmt.Errorf("in delay goroutine: %w", err))
				return
			}
			ch <- "success from delay: " + resp.Header.Get("date")
		}
	}()
 
loop:
	for {
		select {
		case s := <-ch:
			fmt.Println("in main:", s)
		case <-ctx.Done(): // 最初に呼ばれたcancelFunc(err)が保持され、後続のキャンセルでは上書きされない
			fmt.Println("in main: cancelled with error", context.Cause(ctx)) // キャンセレーションエラーが出力される
			break loop
		}
	}
	wg.Wait()
	fmt.Println("context cause:", context.Cause(ctx)) // キャンセレーションエラーが出力される
}