[1.2.5] Play Framework büyük dosya stream etme sorunu

40 views
Skip to first unread message

Ersen Öztoprak

unread,
May 26, 2014, 5:58:05 AM5/26/14
to play-fra...@googlegroups.com
Merhaba,

Projemizde büyük boyutlu (örn. 8GB) dosyaları response'a yazarken stream'ı flush'lamamıza ve hatta kapatmamıza rağmen bellek kullanımının azalmadığını, stream'i client tarafında iptal ettiğimizde belleği hiçbir şekilde boşaltamadığımızı gördük.

Nedenini bulabilmek adına Play kodunu incelediğimizde ByteArrayOutputStream kullanıldığını; ByteArrayOutputStream'in ise (kodları JDK 1.7'de) flush, close, reset gibi metodların hiçbir şey yapmadığını gördük. Bunun üzerine Response objesindeki out isimli stream'in referansını null'layıp System.gc() ile garbarage collection'a zorladığımızda dahi sonuç alamadık.

Aktarım yaparken 8 KB'lık buffer'lar kullanıyoruz. Kod örneğimiz aşağıda. Özetle, dosya sistemindeki 6.5GB'lik bir dosyayı bir client'a stream ediyoruz.

ThrottledInputStream (Apache'nin kodu): http://pastebin.com/FTCAGcbh

Play 1.x sürümlerinde çözüm için bildiğiniz bir yöntem varsa paylaşmanızı rica ederiz.

Teşekkürler..

Fehmi Can Sağlam

unread,
May 26, 2014, 6:20:40 AM5/26/14
to play-fra...@googlegroups.com
Su anda elimin altinda hizlica ulasabilecegim hazir kod olmadigi icin
genel bir cozum tanimi yapayim. Play response render ederken yalnizca
BAOS kullanmiyor. Buradaki temel sorun http protokolu. Veri boyutunun
veriden once response'a yazilmasi gerektigi icin eger siz Play'e veri
boyutunu vermezseniz o da tum stream'i bellege alip boyutunu
hesaplamak zorunda kaliyor. Bunun cozumu ise oldukca kolay. Ya File
vereceksiniz(Play, File objectinden length alabiliyor), ya da stream
ile birlikte length de belirteceksiniz. Bunun icin ornek bulamazsaniz
bile kaynak kodun icerisinde bir yerlerde goreceksiniz.
> --
> Bu iletiyi Google Grupları'ndaki "Play Framework Türkiye" grubuna abone
> olduğunuz için aldınız.
> Bu grubun aboneliğinden çıkmak ve bu gruptan artık e-posta almamak için
> play-framework...@googlegroups.com adresine e-posta gönderin.
> Daha fazla seçenek için https://groups.google.com/d/optout adresini ziyaret
> edin.

Fehmi Can Sağlam

unread,
May 26, 2014, 6:28:35 AM5/26/14
to play-fra...@googlegroups.com
Koda tekrar bakinca content length setlediginizi goruyorum. Bu durumda
sorun baska yerde olmali. Kod genel olarak duzgun gorunuyor.

yalin....@gmail.com

unread,
May 26, 2014, 8:11:11 AM5/26/14
to play-fra...@googlegroups.com
Merhabalar,

Öncelikle cevabınız için teşekkürler. Dediğiniz gibi Content-Length'i belirtiyorduk, ancak sizin cevabınız üzerine yine de kodları inceleyerek Play!'i bir şekilde bu değerden haberdar etmemizin bir yolu olup olmadığını araştırdık (sonuçta Content-Length değeri response header'larına eklenen string bir değerden ibaret).

Bu esnada Response class'ında "direct" adında bir nesne dikkatimizi çekti, bunun ne olduğunu anlamak için nerelerde kullanıldığına baktık ve karşımıza renderBinary(...) metodu çıktı. Kendi yazdığımız metod yerine bu metodu kullandığımızda hiçbir sıkıntıyla karşılaşmadık.

Düşündük, düşündük, bir türlü renderBinary metodunu neden kullanmadığımızı hatırlayamadık :)

Yaptığımız iş neredeyse birebir olsa da şimdi kendi kodumuzla renderBinary'i karşılaştırıp aradaki farkı anlamaya çalışıyoruz.

Cevabınız için tekrardan teşekkürler.

Erdem Agaoglu

unread,
May 26, 2014, 8:37:42 AM5/26/14
to play-fra...@googlegroups.com
Selamlar,

Gonderdiginiz linklerden kodlari aldim ama problemi tekrarlayamadim. 700kusur MB'lik bir dosya ile deniyorum. play surumu 1.2.5.3. sun-jdk6'da da openjdk7'de de herhangi bir leak olusmuyor. Windows'la alakali bir durum olabilir ya da openjdk6 kullaniyorsaniz mesela o da olabilir. tam bulmak icin heapdump analizine girmek lazim. 

Ama baska bir sorun vardi ki onu zaten su anda duzeltmissiniz. Content-Length kiytirik header'lardan degil. Fehmi'nin dedigi gibi yazilmasi gerekli olan bir header da degil. Normal response'lar icin onerilen bir header sadece. Yazildigi durumda client'lar % falan hesaplayabiliyorlar misal. Ya da keep-alive connectionlarda ise yariyor. Onun disinda chunked transfer-encoding icin yazildiginda isler karisabiliyor.

Sizin kodlardaki ilk senaryoda da Content-Length belirtip (ve Transfer-Encoding belirtmeyip) normal bir response gibi ise baslaniyor, ancak sonrasinda response chunked gonderiliyor (writeChunk) ve aralara chunk kodlari uzunluklari falan giriyor. Benim denemelerimde misal client (curl) Content-Length i okuyup o kadar byte aldiginda da transferi kesiyor. Ama aralardaki chunk kodlarina bakmadigi icin ayni uzunlukta cop bir dosya kaydetmis oluyor. Baska client'lar bu uzunluga bakmayip devam etse de aralardaki chunk isaretlerini cozmeleri pek mumkun degil, bu durumda da client farkli boyutta ve yine cop bir dosya indirmis olur. Kisacasi transfer-encoding i yanlis bir response olusuyor.

Ama tabi bunlarin leak yaratmamasi lazim.

Iyi calismalar,
--
erdem agaoglu

Fehmi Can Sağlam

unread,
May 27, 2014, 1:24:37 AM5/27/14
to play-fra...@googlegroups.com
Content-Length zorunlu bir header demis olmasam da bu header'in zorunlu olmadigini bilmiyordum acikcasi. Content-Length belirtilmedigi durumlarda bazi istemciler ile sorun yasadigimdan zorunlu oldugunu benimsemisim sanirim.

Ama mesele bu degil. Mesele renderBinary'nin davranisinin bu sekilde olmasi. Yani content lengthi mutlaka setlemesi ve setlemek icin daha onceden belirttigim durumlarda tum veriyi bellege almasi.

Benim de bir sorum var. Play 1 ile streaming sorunlari ile ugrasali 2 yil kadar oldu. Net olarak hatirlayamiyorum. renderBinary streaming isini Netty'ye pasliyor. Bu durumda requeste hizmet veren worker thread bosa cikar mi? Action icerisinde response.out'a yazilacak stream worker threadi bloklayacaktir. Bu durumda renderBinary aslinda tek gercek cozum sayilir mi?

Erdem Agaoglu

unread,
May 27, 2014, 3:56:52 AM5/27/14
to play-fra...@googlegroups.com
renderBinary'nin bu sekilde calismasi son derece aptalcaymis.
Diger soru icin request thread ilk islemden sonra wait'e geciyor gibi duruyor (jvisualvm de gorunen).
Reply all
Reply to author
Forward
0 new messages