# VC-080 · Dış servis çağrısı süre sınırı olmadan yanıt veya gövde bekliyor

Dış servis yavaşlayınca uygulama isteği ne kadar bekleyeceğini bilmiyor. Yanıt başlığı gelse bile tamamlanmayan gövde işi açık tutabiliyor ve yeni istekler bekleyenlerin üzerine birikiyor.

- Önem: ORTA. Etkisi orta. Trafik artınca ya da istek tekrarlanınca tetiklenir.
- Önem notu: Dış servisin yük altında yavaşladığı ve uygulama işlerinin bekleyerek biriktiği akış temel alınır. Ödeme veya bütün uygulamayı durduran ortak bağımlılıkta etki artar. Altyapının en son süreci kapatması uygulamaya uygun çağrı bütçesi yerine geçmez.
- Eksen ve kategori: Sağlamlık, 12 Hata yolları ve dış çağrılar
- Yığın: Node.js, Her yığın
- Yapay zekâ kodunda: ölçülmedi. Dayanak: uzman görüşü.
- Ne zaman bakılır: İlk yayından önce, Her ay
- CWE: CWE-770
- Checklist ifadesi: Her dış servis çağrısının bir süre sınırı var.
- Son inceleme: 4 Ekim 2026, Komünite editörlüğü
- Adres: https://vibecheck.komunite.com.tr/madde/dis-servis-cagrisinda-zaman-asimi-yok

## 60 saniyelik kontrol

Yalnız kendi uygulamanda ya da yazılı izin aldığın sistemde dene. Bu bir sızma testi değildir.

1. Dış fetch ve SDK çağrılarında uygulamanın belirlediği süre sınırını bul.
2. Sürenin yalnız bağlantıyı mı, yanıt gövdesini de mi kapsadığını kontrol et.
3. Yerel sunucuda başlığı geciktir ve çağrının kendi süre sınırıyla kesildiğini doğrula.
4. Başlık gönderip gövdeyi bitirmeyen yerel yanıtla aynı kontrolü tekrarla.
5. Normal yanıtın çalıştığını ve çağıranın iptalinin kaybolmadığını doğrula.

## Ne oluyor

Uygulama dış katalog servisini çağırıyor. Geliştirme ortamında yanıt hemen geldiği için basit bir `fetch` yeterli görünüyor. Servis yavaşladığında ise uygulamanın ne zaman vazgeçeceği belli değil. Kullanıcı bekliyor, sunucudaki iş açık kalıyor ve yeni istekler aynı bağımlılığın önünde birikiyor. Altyapının sonunda işlemi kapatması kullanıcı akışı için uygun bir bütçe kurmuş olmuyor.

Bekleme yalnız yanıt başlığı gelene kadar sürmez. Servis başlığı gönderip gövdeyi tamamlamayabilir. Süre ölçümünü ilk yanıtta bitiren kod, JSON okunurken yine uzun süre bekleyebilir. Kullanıcının ihtiyaç duyduğu veri hazır olana kadar hangi aşamaların bütçeye dahil olduğu açık olmalıdır. Aynı şekilde bağlantı kurulma süresiyle bütün işlemin süresi farklı şeylerdir.

Bir zamanlayıcıyla yalnız beklemeyi bırakmak da yeterli olmayabilir. Arka plandaki çağrı çalışmaya devam ediyorsa kaynak tüketimi sürer. Kullandığın API destekliyorsa iptal sinyali gerçek işleme iletilmelidir. Bu sinyal uzak sunucuda başlamış yazma işlemini geri almaz. Bekleme yönetimi ile işin uzaktaki sonucu birbirinden ayrı değerlendirilir.

## Gerçek olay

Bu maddede belirli bir ürün kesintisinin bu satır yüzünden yaşandığı söylenmiyor. [Node.js belgeleri](https://nodejs.org/api/globals.html), süre sonunda iptal olan sinyali ve birden fazla iptal kaynağını birleştirmeyi tanımlar. Bu araçlar uygulamanın kendi bekleme bütçesini kurmasına imkân verir. Seçilen sürenin ürün için uygunluğunu belge belirlemez, uygulamanın ihtiyacı belirler.

Yerel deney gerçek HTTP sunucusu ve Node `fetch` kullanır. Sunucu yalnız test makinesinin döngü adresinde dinler. Bir yol başlık göndermez, başka yol gövdeyi yarım bırakır. İyi sürüm iki durumda da kendi süre sınırıyla durur. Kötü sürümü sonsuza kadar bekletmemek için testin ayrı güvenlik iptali vardır. Bu ikinci sınır uygulamanın başarılı zaman aşımı sayılmaz.

## Yapay zekâ bunu neden üretiyor

**Kısa örnek olumlu yolu gösterir.** Ajan API entegrasyonunu anlatırken istek ve JSON ayrıştırmasını birkaç satırda kurabilir. Hızlı yanıt veren yerel denemede eksik süre sınırı görünmez. Hata yolu sonradan eklenecek ayrıntı gibi kalır. Oysa dış bağımlılığın cevap vermediği durumda uygulamanın davranışı da entegrasyonun parçasıdır.

**Altyapı sınırı yeterli sanılır.** Sunucusuz ortamın veya ters vekilin genel süre sınırı olduğu bilinir. Ajan bunu tek çağrı için yeterli bütçe olarak kabul edebilir. Ancak uygulama o sınıra kadar kaynak tutabilir ve kontrollü hata döndürme fırsatını kaybedebilir. Bir istekteki birden fazla dış çağrının toplamı da ayrıca düşünülmelidir.

**Promise yarışması iptal sanılır.** Ajan `Promise.race` ile zamanlayıcıyı yarıştırarak hızlı hata döndürebilir. Yarışı kaybeden işlem otomatik olarak durmaz. Böylece görünür bekleme azalsa da alt çağrı yaşamaya devam edebilir. Çözümün gerçek çağrıya iptal iletip iletmediğini kontrol etmek gerekir. Her SDK bu sinyali aynı şekilde desteklemez.

**Başlık tamamlanınca iş bitmiş sayılır.** `fetch` yanıt nesnesi döndürünce zamanlayıcı temizlenebilir. Ardından gövde okunur ve bu aşama sınırın dışında kalır. Normal küçük yanıt testleri bu boşluğu yakalamaz. Yarım gövde testi bu yüzden ayrı gerekir. Bunlar olası üretim mekanizmalarıdır. AI kodlarında eksikliğin sıklığını ölçen bir araştırma sonucu değildir.

## Etki

Bekleyen işler bağlantı, bellek veya eşzamanlı çalışma kapasitesi tutabilir. Kullanıcının tekrar denemesi aynı bağımlılığa daha çok istek gönderir. Yük altında yavaşlayan servis böylece uygulamanın başka yollarını da etkileyebilir. Gerçek etki çalışma ortamının kapasitesine, çağrı sayısına ve mevcut altyapı sınırlarına bağlıdır.

Zaman aşımını boş sonuç gibi göstermek ayrı bir yanlış davranış doğurur. Kullanıcı stok yok veya kayıt bulunamadı sanabilir, oysa servis cevap vermemiştir. Yazma çağrısında ise süre doldu diye işlemin yapılmadığı varsayılamaz. [Yeniden deneme rehberi](https://learn.microsoft.com/en-us/azure/architecture/patterns/retry), yanıt alınamasa bile uzakta tamamlanan işlemin tekrar edilebileceğini vurgular. Tekrar kararı işlemin güvenliğine bağlıdır.

## Nasıl anlarsın

Dış çağrıyı yapan kodda süre ayarını bul. Ayar gerçekten API'ye veriliyor mu, yoksa yalnız tanımlı mı? SDK'nın varsayılanları varsa belgelenmiş kapsamını incele. Bağlantı, başlık, gövde ve yeniden deneme aşamalarını ayır. Çağıranın isteği iptal edildiğinde alt işlem bunu öğreniyor mu?

Yerel sunucuda önce başlığı geciktir, ardından başlığı gönderip gövdeyi açık bırak. Uygulama iki durumda da belirlediğin bütçeyle hata vermeli. Normal yanıtın hâlâ çalıştığını ve çağıran iptalinin korunduğunu kontrol et. Gerçek sağlayıcıyı yavaşlatmaya veya canlı trafiği bozacak deneme yapmaya gerek yoktur. Testin kendi güvenlik zamanlayıcısını ürün kontrolüyle karıştırma.

## Nasıl düzeltirsin

1. **Bütçeyi akışa göre seç.** Etkileşimli bir ekranla uzun arka plan işi aynı süreyi gerektirmez. Örnekteki süre bir açıklama değeridir, evrensel üretim önerisi değildir. Birden fazla çağrının toplam beklemesini de kullanıcı deneyimi içinde değerlendir.
2. **Sinyali gerçek çağrıya bağla.** Node örneğinde süre sinyali `fetch` seçeneğine aktarılır. Çağıranın sinyali varsa aynı iptal düzenine katılır. Bu API'ler için örnek Node 20.3 ve sonrasını varsayar. Başka SDK kullanıyorsan belgelenmiş iptal veya zaman aşımı yöntemini uygula.
3. **Gövdeyi bütçede tut.** JSON okunması bitene kadar aynı sinyal geçerli kalsın. Örnek, gövde iptalinde asıl iptal nedenini koruyarak dışarı verir. Süre sınırı gövde boyutu sınırının yerine geçmez. Büyük yanıtları ayrıca sınırlaman gerekir.
4. **Sonucu doğru taşı.** Zaman aşımı başarıya dönüşmesin. Kullanıcıya uygun hata, operatöre sır içermeyen kayıt üret. Yazma çağrısını tekrar edeceksen aynı işin iki kez yapılmasını engelleyen sözleşmeyi doğrula. İptalin uzaktaki işlemi geri aldığını varsayma.

## Bir daha olmasın

Her dış bağımlılık için yanıt vermeme ve yarım gövde senaryosu tut. Süre bütçesini ve toplam yeniden deneme davranışını aynı incelemede değerlendir.

## Sınır

Bu madde dış çağrının bekleme süresini kapsar. Gövde boyutu, yeniden deneme aralığı ve çok adımlı işlemin telafisi ayrı konulardır. Uygun ve belgelenmiş SDK zaman aşımı varsa ikinci bir sarmalayıcı zorunlu değildir. Yerel deney internet gecikmesini, gerçek sağlayıcı davranışını veya üretim kapasitesini ölçmez.

## Düzeltme kodları

### Node.js: Fetch ve gövde için ortak süre sınırı

Önce:

```js
// servis/katalog.js, açıklama amaçlı. URL güvenilir uygulama ayarından gelir.
export async function readCatalog(url, { timeoutMs = 3000, signal } = {}) {
  if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 30000) {
    throw new Error('Geçersiz süre');
  }
  // Süre ayarı vardır ama çağrıya uygulanmaz.
  const response = await fetch(url, { signal });
  if (!response.ok) {
    await response.body?.cancel();
    throw new Error('Katalog alınamadı');
  }
  const data = await response.json();
  if (typeof data.available !== 'boolean') throw new Error('Geçersiz katalog');
  return data.available;
}
```

Sonra:

```js
// servis/katalog.js, açıklama amaçlı. Node 20.3+ ve güvenilir uygulama URL'si.
export async function readCatalog(url, { timeoutMs = 3000, signal } = {}) {
  if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 30000) {
    throw new Error('Geçersiz süre');
  }
  const deadline = AbortSignal.timeout(timeoutMs);
  const combined = signal ? AbortSignal.any([signal, deadline]) : deadline;
  try {
    const response = await fetch(url, { signal: combined });
    if (!response.ok) {
      await response.body?.cancel();
      throw new Error('Katalog alınamadı');
    }
    const data = await response.json();
    if (typeof data.available !== 'boolean') throw new Error('Geçersiz katalog');
    return data.available;
  } catch (error) {
    // Gövde okumasındaki iptal de asıl iptal nedenini korur.
    if (combined.aborted) throw combined.reason;
    throw error;
  }
}
```

Düzeltmeyi kanıtlayan test:

```js
// servis/katalog.test.mjs, açıklama amaçlı. Yalnız yerel HTTP sunucusu ve yapay veri.
import test from 'node:test';
import assert from 'node:assert/strict';
import { createServer } from 'node:http';
import { once } from 'node:events';
const { readCatalog } = await import(process.env.ORNEK_DOSYA);
test('zaman sınırı hem başlık hem gövde beklemesini keser', async () => {
  const server = createServer((req, res) => {
    if (req.url === '/headers') return;
    res.writeHead(200, { 'Content-Type': 'application/json' });
    if (req.url === '/body') { res.write('{"available":'); return; }
    res.end('{"available":true}');
  });
  server.listen(0, '127.0.0.1');
  await once(server, 'listening');
  const url = `http://127.0.0.1:${server.address().port}`;
  try {
    for (const path of ['/headers', '/body']) {
      const guard = new AbortController();
      const timer = setTimeout(() => guard.abort(new Error('Test güvenlik süresi')), 1000);
      try {
        await assert.rejects(readCatalog(url + path, { timeoutMs: 50, signal: guard.signal }),
          error => error.name === 'TimeoutError');
      } finally { clearTimeout(timer); }
    }
    assert.equal(await readCatalog(url, { timeoutMs: 1000 }), true);
    const reason = new Error('Çağıran iptal etti');
    await assert.rejects(readCatalog(url, { signal: AbortSignal.abort(reason) }), error => error === reason);
  } finally {
    server.closeAllConnections();
    await new Promise(resolve => server.close(resolve));
  }
});
```

## Ajan kuralı (AGENTS.md)

```md
## Dış çağrı süresiz bekliyor (vibecheck VC-080)
- Her dış çağrının uygulama tarafından belirlenmiş süre bütçesi olur.
- Süre sınırı gereken yanıt gövdesi tüketimini de kapsar.
- Beklemeyi kesen düzen gerçek çağrıya iptal sinyali iletir.
- Çağıranın iptali alt çağrılarda korunur.
- Zaman aşımı başarı veya boş veri gibi gösterilmez.
- Yazma çağrısında zaman aşımı işlemin uzakta yapılmadığını kanıtlamaz.
```

## Kaynaklar

1. [Node.js global AbortSignal timeout and any](https://nodejs.org/api/globals.html), Node.js
2. [CWE-770 Allocation of Resources Without Limits or Throttling](https://cwe.mitre.org/data/definitions/770.html), MITRE
3. [Retry pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/retry), Microsoft

---

vibecheck · Komünite editörlüğü. Metin CC BY 4.0, prompt ve kural parçaları MIT-0. Kaynak: https://vibecheck.komunite.com.tr/madde/dis-servis-cagrisinda-zaman-asimi-yok
