Golang - Nedir bu Broken Pipe Hatası ? Http İstekleri, TCP Bağlantıları, Connection Poollar

Emre Savcı
5 min readFeb 6, 2023
https://content.techgig.com/technology-guide/5-top-advantages-of-using-golang-programming-language/articleshow/82278297.cms

Geliştirdiğiniz uygulamalarda veritabanı işlemleri esnasında veya network bağlantısı kurduğunuz sistemlere istek atarken zaman zaman “broken pipe” hatası ile karşılaştığınız oldu mu? Ya da socket/connection closed?

Bu yazıda, Golang kullanırken yaptığınız network işlemlerinde, TCP bağlantısı kurulan her yerde karşınıza çıkabilecek bazı hataların sebeplerine ve çözümlerine bakacağız. Bu network işlemleri veritabanı bağlantısı kurarak yaptığınız işlemler veya bir HTTP isteği atarak gerçekleştirdiğiniz işlemler olabilir.

Her ne kadar konu başlığında ve içeriğinde Golang geçse de, buradaki konuları birçok programlama dilinde uygulayabillirsiniz. Hatta anlatılacak yaklaşımları bir sistem tasarlarken, birden fazla uygulama ile haberleşirken, Reverse Proxy, Load Balancer kullanırken, client-server haberleşmeleri yaparken uygulayabilirsiniz.

Connection Pool’lar Her Yerde

Connection Pool- by Emre Savcı

Network haberleşmesi yapan çoğu uygulama geliştiricisi bilerek veya bilmeyerek connection pool yapısını kullanır. Sürekli TCP bağlantısı açmak maliyetli bir işlem olduğu için, uygulama performansını ve kaynak kullanımını iyileştirmek için “pool” yapıları biçilmiş kaftandır. HTTP paketleri, veritabanı driverları veya kütüphaneleri, web frameworkler, load balancer ve reverse proxyler kısacası arkaplanda TCP ile haberleşen çoğu sistem (eğer doğası gereği aykırı bir durum yoksa) “pool” barındırır.

Golang ile HTTP Client - Server Haberleşmesi

Pool kullanımının HTTP işlemlerinde nasıl bir yeri olduğunu ve Golang HTTP paketinde nasıl kullanıldığına bir göz atalım.

HTTP Keep Alive

Go ile bir HTTP server yazdığınızda ve standart Go HTTP client’ı ile istek gönderdiğinizde, aksini belirtmediğiniz müddetçe açılan “connection” daha sonraki isteklerde tekrar kullanılmak üzere bir pool üzerinde saklanacaktır.

Şimdi aşağıdaki kod ile basit bir HTTP server örneği oluşturalım:

Server “8080” portunda çalışıyor, ve ConnState fonksiyonu ile server üzerinde bulunan bağlantı durumlarını console çıktısı olarak veriyor.

Şimdi isteğimizi göndereceğimiz HTTP client kodunu yazalım:

Client bağlantı durumlarını izleyebilmemiz için httptrace paketini kullanıyoruz. Burada dikkat edilmesi gereken şey, io.ReadAll() fonksiyonu ile http response tamamen okunuyor. Eğer bu işlemi yapmazsak connection keep-alive işlevi gerçekleşmez.

Server ve client kodumuzu çalıştırıp çıktılarına bakalım:

server.go

Server uygulamamız ilk gönderilen istek için bağlantı durumunu New olarak ekrana basıyor ve istek boyunca durumuActive olarak devam ediyor. İstek tamamlanınca açılan bağlantı Idle durumuna geçiyor.

Sonrasında gelen istekler ise Active ve Idle durumlarında geçiş yapıyor. Çünkü yeni bir bağlantı açılmıyor, ilk açılan bağlantı sürekli tekrar kullanılıyor.

Bu işlem sırasında Client uygulamamızın çıktısına bakalım:

İlk istek gönderildiğinde connection start işleminin gerçekleştiğini görüyoruz. İstek tamamlandığında bağlantı Idle durumuna geçiyor ve sonrasındaki istekler için aynı bağlantı tekrar kullanılıyor.

Go kendi içerisinde HTTP client içinde bulunan Transport tipi üzerinde bir connection pool tutuyor.

Aşağıdaki ekran görüntüsünde Transport tanımında bulunan idle onn poolunu görebiliyoruz.

type Transport struct {
idleMu sync.Mutex
closeIdle bool // user has requested to close all idle conns
idleConn map[connectMethodKey][]*persistConn // most recently used at end
idleConnWait map[connectMethodKey]wantConnQueue // waiting getConns
idleLRU connLRU
...
}

Idle Connection Settings

Eğer istersek hem client hem de server üzerindeki idle connectionlar için çeşitli ayarlamalar yapabiliriz.

HTTP client üzerinde tutulacak idle connectionlar için aşağıdaki ayarları yapabiliriz:

http.Client{
Transport: http.Transport{
DisableKeepAlives: false,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 5,
IdleConnTimeout: 60 ,
}}

DisableKeepAlives : Eğer true yapılırsa keep alive connection tutulmaz

MaxIdleConns : Transport üzerindeki pool’da tutulacak toplam connection sayısı

MaxIdleConnsPerHost : Transport üzerindeki pool’da bir host için tutulacak toplam connection sayısı

IdleConnTimeout : Bir idle connection’ın, pool’da ne kadar süre tutulacağını belirtir

Golang TCP Bağlantısı Oluşturma

HTTP istekleri ile connection pool yapısına hızlıca bir giriş yaptık ve gerçek bir örnek üzerinden uygulanışını gördük.

Şimdi de kendimiz bir TCP server oluşturalım ve bağlantı kuralım.

Golang net paketi ile 8080 portunu dinleyen bir TCP server oluşturduk. Ve gelen tüm bağlantıları bir goroutine ile işleme aldık.

net paketini kullanarak TCP bağlantı isteği yapan bir client oluşturduk. Döngü içerisinde oluşturduğumuz bağlantı üzerinden hello verisini gönderdik.

Şimdi programımızı çalıştırıp uygulama çıktılarına bakalım.

Server uygulamamızı çalıştırdık ve hemen sonrasında client uygulamamızı da çalıştırdık. Server uygulamamız yeni bir istek geldiğini ve gönderilen veriyi ekrana bastı.

Client uygulamamız da başarılı bir şekilde gönderdiği veri boyutunu ekrana yazdı.

Broken Pipe Hatası

Peki tüm bu işlemler gerçekleşirken, iki uygulama da çalışırken server uygulamamızı kapatırsak ne olur?

Şimdi de yukarıdaki örneği çalıştırdıktan bir süre sonra server uygulamasını sonlandırıp çıktılara bakalım.

tcp-client.go

Server uygulamamızı kapattıktan sonra, client uygulamamız yukarıdaki gibi bir çıktı veriyor.

Server kapandıktan sonra client uygulamamız görüldüğü üzere bir hata ile karşılaşıyor. Hata mesajı tam olarakwrite tcp 127.0.0.1:58766->127.0.0.1:8080: write: broken pipe şeklinde beliriyor.

Buradan anlaşıldığı üzere açılan bir TCP bağlantısı server tarafından kapatılırsa client tarafında yazma işlemi yapılana kadar bu durumdan haberdar olunamıyor.

TCP Connection Pool’umuzu Oluşturalım

Broken pipe hatasının nasıl gerçekleştiğini gördük. Açılan bir TCP bağlantısı, hala kullanımda iken server tarafından kapatılırsa client uygulamaları beklenildiği üzere hata alıyor.

Bu hatayı gerçek hayat örneklerine yaklaştırmak için bir connection pool oluşturalım.

Çok basit düzeyde bir pool oluşturduk ve şimdi client uygulamamızı tekrar çalıştıralım.

Uygulamayı çalıştırdıktan bir süre sonra server’ı kapattığımızda, client tarafında pool’dan alınan bağlantının broken pipe hatası aldığını görüyoruz.

Örneklerimizde server uygulamasını kapatmaktan bahsediyoruz fakat durum her zaman böyle gerçekleşmeyebilir. Aslında burada önemli olan server tarafındaki bağlantının kapatılmasıdır.

Server tarafında oluşturulan bağlantılar için idle timeout değeri verilebilir ve belirli bir süre sonra karşılanan isteklerin bağlantıları otomatik olarak kapatılabilir.

Böyle bir durumda, client tarafından açıp kullanılan ve idle pool üzerinde saklanan bir bağlantı, server tarafından kapatılmış olabilir. Böyle bir senaryoda, client idle pool üzerinden aldığı bağlantıyı tekrar kullanmak istediğinde broken pipe hatası ile karşılaşacaktır.

Broken pipe by Emre Savcı

Peki bu hata başka nerelerde karşımıza çıkabilir?

Veritabanı Bağlantıları: Veritabanlarına bağlanırken kullandığımız kütüphaneler kendi içerisinde pool barındırabilir veya pool barındıran paketler kullanılabilir. Bu durumda veritabanı tarafından kapatılan idle connectionlara karşılık uygulama tarafında hata alabileceğimizi göz önünde bulundurmalıyız.

Reverse Proxy & Load Balancer Uygulamaları: Birçok reverse proxy ve load balancer uygulaması kendi üzerinde connection pool yapısı bulundurur. Bu tarz uygulamaların konfigürasyonunda mutlaka idle connection timeout değerlerinin server tarafında belirtilen timeout değerinden küçük olması gerekir ki server kapatmadan önce bağlantı client tarafından sonlandırılsın.

Bir sonraki yazıda görüşmek üzere, hepinize keyifli geliştirmeler dilerim.

--

--

Emre Savcı

Sr. Software Engineer @Trendyol & Couchbase Ambassador | Interested in Go, Kubernetes, Istio, CNCF, Scalability. Open Source Contributor.