İçeriğe geç
Muhammet Şafak
en
Soran: Tolga Cevaplandı:

İç içe geçmiş servis çağrılarında context iptalini doğru şekilde nasıl yayarım?


Soru

Go ile yazdığım HTTP handler'larında, client bağlantıyı kestiğinde downstream Postgres sorgusunun ve dışarı giden HTTP çağrısının da iptal olmasını istiyorum. Ama pratikte istek çoktan gitmiş olsa bile handler'larım çalışmaya devam ediyor; sorgu tamamlanıyor, dış API çağrılıyor. Zinciri `handler → service → repository → driver` şeklinde katmanladım. context iptalini bu katmanlar boyunca doğru şekilde nasıl yayarım, nerede yanlış yapıyor olabilirim?

Cevap

Kısa cevap: İptal ancak context’i gerçekten aşağı geçirirseniz yayılır. r.Context()’ten başlayıp her downstream çağrıya (db.QueryContext, http.NewRequestWithContext) o context’i vermeniz gerekir; zincirin herhangi bir yerinde context.Background() kullanır veya context’i düşürürseniz iş çalışmaya devam eder.

  1. En sık sebep: context’in koparılması. Bir yerde context.Background()/context.TODO() kullanılmış ya da Context’siz metot çağrılmıştır: db.Query yerine db.QueryContext, http.Get yerine req üzerine context konmuş client.Do. Önce bu çağrıları grep’leyin; hata neredeyse her zaman burada.

  2. r.Context()’ten başlayın. net/http, client bağlantıyı kestiğinde bu context’i iptal eder. Onu service → repository → driver boyunca aşağı taşıyın. Context’i struct’a saklamayın; her fonksiyona ilk argüman olarak geçin (ctx context.Context).

  3. DB: QueryContext/ExecContext kullanın. database/sql + pgx ile context iptal edildiğinde driver Postgres’e bir cancel isteği gönderip çalışan sorguyu gerçekten durdurur. Driver’ınızın bunu desteklediğini doğrulayın — pgx destekler; iptal DB’ye kadar inmezse sorgu boşuna dönmeye devam eder.

  4. Dışarı giden HTTP: NewRequestWithContext. İsteği bu context’le kurun ki client, iptal ve deadline’a uysun. http.Get/http.Post gibi kısayollar context almaz ve iptali hiç görmez.

  5. Fazla iptal etmeyin — yaşaması gerekeni ayırın. Bir side-effect isteğin ömründen bağımsız tamamlanmalıysa (outbox’a yazma, event yayınlama), yeni bir context türetin: Go 1.21+ ile context.WithoutCancel(ctx), ya da kendi timeout’una sahip taze bir context. Aksi halde client’ın kopması side-effect’lerinizi de öldürür.

  6. İptalin yanına timeout ekleyin. Her dış çağrı için context.WithTimeout koyun; böylece bağlantı hiç kopmasa bile takılan bir bağımlılık sizi sonsuza kadar bekletmez.

  7. Kendi goroutine’leriniz ve döngüleriniz de ctx’i gözlemlesin. Blocking bir çağrı yoksa iptal kendiliğinden fark edilmez; uzun süren döngülerde select { case <-ctx.Done(): return ctx.Err() ... } ile çıkış kontrolü koyun. Handler içinde başlattığınız goroutine’lere de aynı ctx’i verin, yoksa istek gitse bile arka planda çalışmaya devam ederler.

İskelet şöyle:

func (h *Handler) Create(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context() // client kopunca iptal olur

    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()

    if err := h.svc.Do(ctx, id); err != nil {
        if errors.Is(err, context.Canceled) {
            return // client gitti, sessizce çık
        }
        http.Error(w, "internal", http.StatusInternalServerError)
    }
}

// repository:
row := db.QueryRowContext(ctx, "SELECT ...", id)
// dış çağrı:
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)

Sonuç: Ben olsam tüm çağrı yerlerini denetlerdim: içeri r.Context(), her katmanda *Context’li metotlar, ve yalnızca kopmadan sağ çıkması gereken birkaç side-effect için WithoutCancel. Bir kez bu disiplini oturttuğunuzda client bağlantıyı kestiği an Postgres sorgusu ve dış çağrı da kendiliğinden düşer; boşa yanan CPU ve bağlantı da onlarla birlikte kaybolur.

İlgili Yazılar

Etiketler: #go#context#concurrency
Paylaş:

Yorumlar

Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.

Diğer Sorular

Tüm sorular

Sitede Ara

Yazı, proje ve sayfalarda arama yapmak için yazmaya başlayın.

Esc ile kapat Pagefind ile güçlendirildi