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

> İptalin yayılması için `r.Context()`'i her downstream çağrıya geçirin, zincirde `context.Background()` bırakmayın, side-effect'e `WithoutCancel` verin.

- Soruldu: 2026-08-05
- Yanıtlandı: 2026-08-07
- Soran: Tolga
- Etiketler: go, concurrency
- Kaynak: https://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.

## Kısa cevap

`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. Aynı disiplinin bir başka yüzünü — ctx'i gözetmeyen işlerin arkada birikmesini — [goroutine sızıntıları kaydında](/tr/sor-bakalim/goroutine-sizintilarini-uzun-sure-calisan-bir-go-servisinde-nasil-tespit-edip/) ele almıştım.

## Neden

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`. Hata neredeyse her zaman burada.

2. **İptal işbirlikçidir; kısayollar onu hiç görmez.** net/http, client bağlantıyı kestiğinde `r.Context()`'i iptal eder — ama bu sinyalin bir etkisi olması için çağrının onu gözlemesi gerekir. `http.Get`/`http.Post` gibi kısayollar context almaz. Blocking bir çağrı yoksa da iptal kendiliğinden fark edilmez.

3. **İptal DB'ye kadar inmeyebilir.** `database/sql` + pgx ile context iptal edildiğinde driver varsayılan olarak alttaki bağlantıyı kapatır; sorgunun Postgres tarafında da gerçekten iptal edilmesi içinse `CancelRequestContextWatcherHandler`'ı ayrıca yapılandırmanız gerekir, bu varsayılan davranış değildir. İptal DB'ye kadar inmezse sorgu boşuna dönmeye devam eder.

## Ne yapmalı

1. **`r.Context()`'ten başlayın ve aşağı taşıyın.** Context'i `service → repository → driver` boyunca geçirin. Struct'a saklamayın; her fonksiyona ilk argüman olarak verin (`ctx context.Context`). İ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)
   ```

2. **Çağrı yerlerini grep'leyin.** `context.Background()`, `context.TODO()`, `db.Query`, `http.Get` geçen satırları tarayın; kopma noktası büyük ihtimalle bunlardan biridir.

3. **DB: `QueryContext`/`ExecContext` kullanın.** Driver'ınızın context iptalini nasıl ele aldığını doğrulayın; gerekiyorsa Postgres tarafında iptali de yapılandırın.

4. **Dışarı giden HTTP: `NewRequestWithContext`.** İsteği bu context'le kurun ki client, iptal ve deadline'a uysun.

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

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

- [Go'da eşzamanlılık: goroutine ve channel pratiği](/tr/blog/goda-eszamanlilik-goroutine-ve-channel-pratigi/) — Blog
- [Kanal tampon boyutu seçimi backpressure sorunlarını gizler mi, nasıl karar vermeliyim?](https://muhammetsafak.com/tr/sor-bakalim/kanal-tampon-boyutu-backpressure-sorunlarini-gizler-karar-vermeliyim/) — Sor Bakalım
- [Birbirine bağımlı birden fazla işi, ilk hata hepsini iptal edecek şekilde errgroup ile mi yürütmeliyim?](https://muhammetsafak.com/tr/sor-bakalim/birbirine-bagimli-birden-fazla-isi-ilk-hata-hepsini-iptal-edecek-sekilde/) — Sor Bakalım
- [Goroutine sızıntılarını uzun süre çalışan bir Go servisinde nasıl tespit edip önlerim?](https://muhammetsafak.com/tr/sor-bakalim/goroutine-sizintilarini-uzun-sure-calisan-bir-go-servisinde-nasil-tespit-edip/) — Sor Bakalım
