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

> r.Context()'i her downstream çağrıya geçirin: QueryContext, NewRequestWithContext. Zincirin bir yerinde context.Background() kullanırsanız iptal kopar. Yaşaması gereken side-effect'ler için WithoutCancel.

- Soruldu: 2026-08-05
- Yanıtlandı: 2026-08-07
- Soran: Tolga
- Etiketler: go, context, concurrency
- Kaynak: https://www.muhammetsafak.com.tr/sor-bakalim/ic-ice-gecmis-servis-cagrilarinda-context-iptalini-dogru-sekilde-nasil-yayarim/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
**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?


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:

```go
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.
