Skip to content

context

context bağlam olarak adlandırılır ve orijinal tasarım amacı, özellikle ebeveyn ve çocuk goroutine'ler arasında, çoklu goroutine'ler arasında sinyal ve bazı basit verileri iletmektir. HTTP isteklerini işleme, görev zamanlaması, veritabanı sorguları vb. senaryolarda yaygın olarak kullanılır. Özellikle mikroservis mimarilerinde, gRPC işlem süreci ve ağ metadata iletimi, bağlantı kontrol işlemleri vb. için context kullanır.

go
package main

import (
    "context"
    "fmt"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGKILL, syscall.SIGINT, syscall.SIGTERM)
    defer stop()
    for {
       select {
       case <-ctx.Done():
          fmt.Println("terminate")
          return
       default:
       }
       fmt.Println("running")
       time.Sleep(100 * time.Millisecond)
    }
}

Yukarıdaki örnekte, işlem sinyalleri context aracılığıyla iletilir. Bir sinyal alındığında, program kendini sonlandırır. Bu da context'in bir uygulama senaryosudur.

Yapı

context.Context somut bir uygulama değil, bir dizi yöntem tanımlayan bir arayüzdür:

go
// Context's methods may be called by multiple goroutines simultaneously.
type Context interface {
	Deadline() (deadline time.Time, ok bool)

	Done() <-chan struct{}

	Err() error

	Value(key any) any
}
  • Deadline(), bir son tarih ve bitip bitmediğini belirten bir boolean döndürür
  • Done(), sonlandırma mesajlarını bildirmek için kullanılan bir kanal döndürür
  • Err(), context kapanma nedenini döndürür
  • Value(), anahtara göre belirtilen değeri alır

Standart kütüphanede aşağıdaki kullanılabilir context'ler vardır:

  • Background, boş context, genellikle context'lerin kök düğümü olarak kullanılır
  • WithCancel, WithCancelCause, iptal edilebilir context
  • WithDeadline, WithDeadlineCause, son tarihli context
  • WithTimeout, WithTimeoutCause, zaman aşımı süreli context
  • WithValue, değer taşıyabilen context

Özellikle, ana uygulamalar şunlardır:

  • timerCtx
  • cancelCtx
  • emptyCtx

Yani çekirdek işlevselliği dört noktadır:

  • İptal
  • Son tarih
  • Değer geçirme
  • Yayılma

Bu noktaları anlamak temelde context'in nasıl çalıştığını açıklığa kavuşturur.

İptal

go
type cancelCtx struct {
	Context

	mu       sync.Mutex            // protects following fields
	done     atomic.Value          // of chan struct{}, created lazily, closed by first cancel call
	children map[canceler]struct{} // set to nil by the first cancel call
	err      error                 // set to non-nil by the first cancel call
	cause    error                 // set to non-nil by the first cancel call
}


func withCancel(parent Context) *cancelCtx {
    if parent == nil {
       panic("cannot create context from nil parent")
    }
    c := &cancelCtx{}
    c.propagateCancel(parent, c)
    return c
}

cancelCtx'in çekirdeği propagateCancel metodudur, bu iptal edilebilir davranışı ebeveyn ve çocuk context'lere yaymaktan sorumludur.

go
func (c *cancelCtx) propagateCancel(parent Context, child canceler) {
	...
}
  1. İlk olarak, ebeveyn context'in iptal edilip edilemeyeceğini kontrol eder. Edilemiyorsa, doğrudan döner.

    go
    done := parent.Done()
    if done == nil {
    	return // parent is never canceled
    }
  2. Döner ve ebeveyn context'in zaten iptal edilip edilmediğini kontrol eder. Evet ise, tüm çocuk context'leri iptal eder.

    go
    select {
    case <-done:
    	// parent is already canceled
    	child.cancel(false, parent.Err(), Cause(parent))
    	return
    default:
    }
  3. Ebeveyn context'i cancelCtx tipine dönüştürmeye çalışır. Başarılı olursa, mevcut context'i ebeveyn context'in children'ına ekler.

    go
    if p, ok := parentCancelCtx(parent); ok {
    	// parent is a *cancelCtx, or derives from one.
    	p.mu.Lock()
    	if p.err != nil {
    		// parent has already been canceled
    		child.cancel(false, p.err, p.cause)
    	} else {
    		if p.children == nil {
    			p.children = make(map[canceler]struct{})
    		}
    		p.children[child] = struct{}{}
    	}
    	p.mu.Unlock()
    	return
    }
  4. afterFuncer tipine dönüştürmeye çalışır. Başarılı olursa, mevcut context'i iptal etme yöntemini ebeveyn context'in AfterFunc'una kaydeder.

    go
    if a, ok := parent.(afterFuncer); ok {
    	// parent implements an AfterFunc method.
    	c.mu.Lock()
    	stop := a.AfterFunc(func() {
    		child.cancel(false, parent.Err(), Cause(parent))
    	})
    	c.Context = stopCtx{
    		Context: parent,
    		stop:    stop,
    	}
    	c.mu.Unlock()
    	return
    }
  5. Eğer hala çalışmıyorsa, Done kanalını dinlemek için ayrı bir goroutine başlatır. Bir sinyal alındığında, çocuk context'i iptal eder.

    go
    go func() {
        select {
        case <-parent.Done():
           child.cancel(false, parent.Err(), Cause(parent))
        case <-child.Done():
        }
    }()

Sonra cancelCtx.cancel metodu nihayetinde çocuk context'leri iptal etmekten sorumludur:

go
func (c *cancelCtx) cancel(removeFromParent bool, err, cause error) {
	...
}

Akışı şöyledir:

  1. Mevcut context'in zaten iptal edilip edilmediğini kontrol eder.

    go
    if err == nil {
    	panic("context: internal error: missing cancel error")
    }
    if cause == nil {
    	cause = err
    }
    c.mu.Lock()
    if c.err != nil {
    	c.mu.Unlock()
    	return // already canceled
    }
  2. done kanalını kapatır ve kapanma bildirimi gönderir.

    go
    c.err = err
    c.cause = cause
    d, _ := c.done.Load().(chan struct{})
    if d == nil {
    	c.done.Store(closedchan)
    } else {
    	close(d)
    }
  3. Dolaşır ve çocuk context'leri bildirir.

    go
    for child := range c.children {
    	child.cancel(false, err, cause)
    }
    c.children = nil
    c.mu.Unlock()
  4. Son olarak, geçirilen parametreye göre ebeveyn context'ten kaldırılıp kaldırılmayacağını belirler.

    go
    if removeFromParent {
    	removeChild(c.Context, c)
    }

Son Tarih

WithTimeout ve WithDeadline ikisi de son tarihli context'lerdir. Aynı tiptedirler, sadece anlamları farklıdır ve ikisi de cancelCtx'e dayanır.

go
type timerCtx struct {
	cancelCtx
	timer *time.Timer // Under cancelCtx.mu.

	deadline time.Time
}

WithDeadlineCause son tarihli context'ler oluşturmaktan sorumludur:

go
func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc) {
	...
}

Akışı şöyledir:

  1. Son tarihi kontrol eder. Eğer ebeveyn son tarihi mevcut son tarihten önceyse, o zaman ebeveyn context kesinlikle mevcut context'ten önce iptal edilecektir, bu yüzden doğrudan bir cancelCtx tipi context oluşturur.

    go
    if parent == nil {
    	panic("cannot create context from nil parent")
    }
    if cur, ok := parent.Deadline(); ok && cur.Before(d) {
    	// The current deadline is already sooner than the new one.
    	return WithCancel(parent)
    }
  2. timerCtx oluşturur ve çocuk context'lere yayar.

    go
    c := &timerCtx{
    	deadline: d,
    }
    c.cancelCtx.propagateCancel(parent, c)
  3. Mevcut son tarihi hesaplar. Eğer zaten süresi dolmuşsa, doğrudan iptal eder.

    go
    dur := time.Until(d)
    if dur <= 0 {
    	c.cancel(true, DeadlineExceeded, cause) // deadline has already passed
    	return c, func() { c.cancel(false, Canceled, nil) }
    }
  4. Eğer süresi dolmamışsa, son tarihte mevcut context'i iptal etmek için time.AfterFunc kullanır.

    go
    c.mu.Lock()
    defer c.mu.Unlock()
    if c.err == nil {
    	c.timer = time.AfterFunc(dur, func() {
    		c.cancel(true, DeadlineExceeded, cause)
    	})
    }
    return c, func() { c.cancel(true, Canceled, nil) }

timerCtx için, context'i iptal etme yöntemi sadece timer'ı durdurur ve ayrıca çocuk context'leri de durdurur.

go
func (c *timerCtx) cancel(removeFromParent bool, err, cause error) {
	c.cancelCtx.cancel(false, err, cause)
	if removeFromParent {
		// Remove this timerCtx from its parent cancelCtx's children.
		removeChild(c.cancelCtx.Context, c)
	}
	c.mu.Lock()
	if c.timer != nil {
		c.timer.Stop()
		c.timer = nil
	}
	c.mu.Unlock()
}

Değer Geçirme

valueCtx context'lerde değerleri geçirebilir ve alabilir:

go
type valueCtx struct {
    Context
    key, val any
}

func WithValue(parent Context, key, val any) Context {
	if parent == nil {
		panic("cannot create context from nil parent")
	}
	if key == nil {
		panic("nil key")
	}
	if !reflectlite.TypeOf(key).Comparable() {
		panic("key is not comparable")
	}
	return &valueCtx{parent, key, val}
}

Çekirdeği value fonksiyonunda yatar. Diğer context somut uygulamaları da değer almak için bu fonksiyona girer, örneğin:

go
func (c *valueCtx) Value(key any) any {
	if c.key == key {
		return c.val
	}
	return value(c.Context, key)
}

func (c *cancelCtx) Value(key any) any {
	if key == &cancelCtxKey {
		return c
	}
	return value(c.Context, key)
}

value fonksiyonu belirtilen anahtarın değerini bulmak için sürekli yukarı doğru yineleyen büyük bir for döngüsüdür:

go
func value(c Context, key any) any {
	for {
		switch ctx := c.(type) {
		case *valueCtx:
			if key == ctx.key {
				return ctx.val
			}
			c = ctx.Context
		case *cancelCtx:
			if key == &cancelCtxKey {
				return c
			}
			c = ctx.Context
		case withoutCancelCtx:
			if key == &cancelCtxKey {
				// This implements Cause(ctx) == nil
				// when ctx is created using WithoutCancel.
				return nil
			}
			c = ctx.c
		case *timerCtx:
			if key == &cancelCtxKey {
				return &ctx.cancelCtx
			}
			c = ctx.Context
		case backgroundCtx, todoCtx:
			return nil
		default:
			return c.Value(key)
		}
	}
}

Farklı Context tiplerinin işlenmesi:

  • *valueCtx: Eğer key mevcut Context'in key'i ile eşleşirse, key'e karşılık gelen val'i döndürür.
  • *cancelCtx: Eğer key cancelCtxKey ile eşleşirse, mevcut cancelCtx'i döndürür, yani kendini döndürür.
  • withoutCancelCtx: İptal işlevi olmayan bir Context'i temsil eder. Eğer key cancelCtxKey ile eşleşirse, nil döndürür.
  • *timerCtx: Eğer key cancelCtxKey ile eşleşirse, ilişkili cancelCtx'i döndürür.
  • backgroundCtx ve todoCtx: Genellikle ekstra değer taşımayan özel Context tipleridir. Bu iki tip ile karşılaşıldığında, doğrudan nil döndürür.
  • Bilinmeyen tip ise, aramaya devam etmek için Value metodunu çağırmaya devam eder.

Özet

Bu context'lerin çekirdeği cancelCtx'tir, bu iptal sinyallerini yayar. Diğer context tipleri ve hatta üçüncü taraf context tipleri bunun üzerine katmanlanır, bu temel üzerine çeşitli işlevsellikler uygular.

Golang by www.golangdev.cn edit