Upgrade to Pro — share decks privately, control downloads, hide ads and more …

goroutineで親のctxのkey/valueを引き継ぐ実装


Terry
December 13, 2023

 goroutineで親のctxのkey/valueを引き継ぐ実装


2023/12/13開催のSendai.goの登壇資料です

Terry

December 13, 2023
Tweet

More Decks by Terry

Other Decks in Technology

Transcript

  1. ©Showcase Gig goroutineにおけるctx
 // 注文を永続化する処置 err := uc.OrderRepository.CreateOrder(ctx, order) if

    err != nil { return err } go func() { // ユーザーに注文受付メールを送信する newCtx, cancel := context.WithCancel(context.Background()) defer cancel() err2 := uc.MailService.SendOrderReceivedMail(ctx, order) if err2 != nil { return err2 } }() return nil
  2. ©Showcase Gig goroutineにおけるctx
 // 注文を永続化する処置 err := uc.OrderRepository.CreateOrder(ctx, order) if

    err != nil { return err } go func() { // ユーザーに注文受付メールを送信する newCtx, cancel := context.WithCancel(context.Background()) defer cancel() err2 := uc.MailService.SendOrderReceivedMail(ctx, order) if err2 != nil { return err2 } }() return nil 新しいctxを発行する必要がある
  3. ©Showcase Gig context.Background()
 // Background returns a non-nil, empty [Context].

    It is never canceled, has no // values, and has no deadline. It is typically used by the main function, // initialization, and tests, and as the top-level Context for incoming // requests. func Background() Context { return backgroundCtx{} } https://pkg.go.dev/context#Background まっさらなcontextが生成される
  4. ©Showcase Gig ctxにkey/valueを引き回すユースケース例
 • ログ情報のため ◦ ユーザーID ◦ 注文番号 ◦

    注文した店舗 ◦ …. • RLS(row level security)のため ◦ goroutineでもdbアクセスをしたい
  5. ©Showcase Gig keyが分かっていれば入れ込むことはできる
 var keys = []key{ “order_id”, “restaurant_id”, }

    func CloneBackgroundContext(ctx context.Context) context.Context { newCtx := context.Background() // KeyValueを引き継ぐ for _, key := range keys { value := ctx.Value(key) if value != nil { newCtx = context.WithValue(newCtx, key, value) } } return newCtx }
  6. ©Showcase Gig keyが分かっていれば入れ込むことはできる
 var keys = []key{ “order_id”, “restaurant_id”, }

    func CloneBackgroundContext(ctx context.Context) context.Context { newCtx := context.Background() // KeyValueを引き継ぐ for _, key := range keys { value := ctx.Value(key) if value != nil { newCtx = context.WithValue(newCtx, key, value) } } return newCtx } keyが増えた時に確実にメンテしないといけない
  7. ©Showcase Gig type Context interface { Deadline() (deadline time.Time, ok

    bool) Done() <-chan struct{} Err() error Value(key any) any } contextのinterface
 4つのメソッドが満たせれば自分で作れる
  8. ©Showcase Gig // 元々のctxを引き継ぐ type cloneCtx struct { originalCtx context.Context

    // 元々のcontext newCtx context.Context // 新しく生成したcontext } func (c cloneCtx) Value(key any) any { return c.originalCtx.Value(key) } func (c cloneCtx) Deadline() (time.Time, bool) { return c.newCtx.Deadline() } func (c cloneCtx) Done() <-chan struct{} { return c.newCtx.Done() } func (c cloneCtx) Err() error { return c.newCtx.Err() } key/valueだけ親のctxを引き継ぐ

  9. ©Showcase Gig // 元々のctxを引き継ぐ type cloneCtx struct { originalCtx context.Context

    // 元々のcontext newCtx context.Context // 新しく生成したcontext } func (c cloneCtx) Value(key any) any { return c.originalCtx.Value(key) } func (c cloneCtx) Deadline() (time.Time, bool) { return c.newCtx.Deadline() } func (c cloneCtx) Done() <-chan struct{} { return c.newCtx.Done() } func (c cloneCtx) Err() error { return c.newCtx.Err() } key/valueだけ親のctxを引き継ぐ
 元々のctxからkey/valueを取得する
  10. ©Showcase Gig func DetachWithCancel(ctx context.Context) (context.Context, context.CancelFunc) { c, cancel

    := context.WithCancel(context.Background()) return cloneCtx{ originalCtx: ctx, newCtx: c, }, cancel } ただ、cancel()等は自分で作らないといけない

  11. ©Showcase Gig func WithoutCancel(parent Context) Context { if parent ==

    nil { panic("cannot create context from nil parent") } return withoutCancelCtx{parent} } 嬉しい
 https://pkg.go.dev/context@master#WithoutCancel
  12. ©Showcase Gig 嬉しい
 type withoutCancelCtx struct { c Context }

    func (withoutCancelCtx) Deadline() (deadline time.Time, ok bool) { return } func (withoutCancelCtx) Done() <-chan struct{} { return nil } func (withoutCancelCtx) Err() error { return nil } func (c withoutCancelCtx) Value(key any) any { return value(c, key) } func (c withoutCancelCtx) String() string { return contextName(c.c) + ".WithoutCancel" }
  13. ©Showcase Gig こうなる
 // 注文を永続化する処置 err := uc.OrderRepository.CreateOrder(ctx, order) if

    err != nil { return err } go func() { // ユーザーに注文受付メールを送信する newCtx, cancel := context.WithCancel(context.WithoutCancel(ctx)) defer cancel() err2 := uc.MailService.SendOrderReceivedMail(ctx, order) if err2 != nil { return err2 } }() return nil key/valueを引き継ぎつつ cancel処理を新たに作ってあげる