LINUX.ORG.RU

В линуксе файл дескриптор остается в /proc/<pid>/fd после закрытия сокета

 , ,


0

2

У меня маленькая программа делает 50 соединений к серверу, а по каждому соединению в отдельном треде посылает один реквест, читает респонс и закрывает сокет. И так все повторяется 1000 раз. В результате число коннекшенов показываемых нетстатом и lsof в Mac OS X 10.8.4 выглядит как следует — пилообразно, а в линуксе у меня lsof чето не работает но прога runs out of file descriptors. В /proc/<pid>/fd на каждой итерации добавляется по 50 дескрипторов а старые никуда не исчезают. Есть идеи в чем дело? Код на хаскеле, но могу показать если интересно.

strace в руки. Т.е. для начала не помешало бы убедиться что close() вызывается для каждого сокета.

mashina ★★★★★ ()
Последнее исправление: mashina (всего исправлений: 1)
Ответ на: комментарий от ilovewindows

Мэй би прога не вокает по другой, как это будет по русский, reason, но риад эбаут SO_REUSEADDR.

Мог тупо написать последнее слово, было бы лучше.

false ★★★★★ ()
Ответ на: комментарий от Deleted
main :: IO ()
main = multiBenchmark 1000 >> return ()

-- separate each handshake by delta microseconds
-- set up the proper socket options                    
burst :: Int -> Int -> IO [(Iter BL.ByteString IO (), Onum BL.ByteString IO a)]
burst n delta = do clients <- mapM (const mkClient) [1..n]
                   mapM_ (\hc -> do let s = hcSock hc
                                    setSocketOption s ReuseAddr 1) clients                   
                   mapM (\c -> threadDelay delta >> putStrLn "connect"  >> httpConnect c) clients                   

mkClient :: IO HttpClient
mkClient = do ctx <- sslContext
              mkHttpClient "172.26.151.36" 11605 (Just ctx) True

-- do series of bursts with a given rate
benchmark :: IO String
benchmark = do
  let n = 50 -- number of connections in a burst
  start <- getCurrentTime
  connections <- burst n 0
  -- create one thread per connection and send request -- collect responses
  mvs <- mapM (const newEmptyMVar) [1..n]
  ts <- mapM (\(d, mv, (i, o)) -> forkIO $ go d (i, o) `finally` putMVar mv ()) $ zip3 [1..n] mvs connections
  -- wait for the threads to complete
  mapM_ takeMVar mvs
  stop <- getCurrentTime
  print $ stop `diffUTCTime` start
  -- continue with another burst
  return "success"
  where go :: Int -> (Iter BL.ByteString IO (), Onum BL.ByteString IO ()) -> IO ()
        go d (i, o) = do threadDelay (d * 1000000)
                         let request = defaultHttpReq{reqScheme="https",
                                                      reqMethod="GET",
                                                      reqPath="/somewhere-on-the-server",
                                                      reqVers=(1,1)}
                         count <- newIORef 0
                         o |$ inumHttpClient (request, "") (responseHandler count request 0) .| i
  

-- runs a burst of N connections M times
multiBenchmark :: Int -> IO String
multiBenchmark n = forM [1..n] (const $ benchmark `catch` (\(e::SomeException) -> return (show e))) >>= return . last

responseHandler :: IORef Int -> HttpReq () -> Int -> HttpResponseHandler IO ()
responseHandler count oldReq m resp = do c <- lift $ readIORef count
                                         if c > 0
                                           then return Nothing
                                           else do lift $ writeIORef count 1
                                                   return $ Just (oldReq, "")


sslContext :: IO SSL.SSLContext
sslContext = withOpenSSL $ do
  ctx <- SSL.context
  SSL.contextSetPrivateKeyFile ctx "<key-path>"
  SSL.contextSetCertificateFile ctx "<cert-path>"
  return ctx

nokachi ()

смотри netstat'ом. Если они у тебя не закрываются, то в netstat будут видны в состоянии CLOSE_WAIT

maxcom ★★★★★ ()
Ответ на: комментарий от maxcom

закрываются, нетстат показывает осцилляции между нулем и 50 коннекшнов

nokachi ()
Ответ на: комментарий от Deleted

внутри o. Тут использована библиотека iterIO, которая делает что-то вроде fold на всех данных из сокета, применяя к ним указанную мной функцию. Когда данные заканчиваются или моя функция решает больше не брать, она автоматически закрывает сокет. Это все внутри библиотеки.

nokachi ()
Ответ на: комментарий от nokachi

Мой вопрос был к тому, что в языках со встроенным сборщиком мусора обычно рекомендуют явно закрывать файловые дескрипторы и сокеты в том числе. Иначе сокет будет закрыт только когда сборщик мусора «проснётся», а это процесс малопредсказуемый.

Но опять же, я не знаю как устроен хаскелл и его библиотеки...

Deleted ()
Ответ на: комментарий от Deleted

не, тут GC не при чем. Закрытие сокета делается вручную (в смысле через sClose sock), но вызов гарантирован библиотекой by design. Ну и вообще, я straceом вижу что сокеты закрываются. Остается непонятным почему они остаются в процессе.

nokachi ()

есть две опции для игры

net.ipv4.tcp_tw_recycle = 1

net.ipv4.tcp_tw_reuse =1

но лучше не делать множества коротких соединений к серверу

mashina ★★★★★ ()
Последнее исправление: mashina (всего исправлений: 1)
Ответ на: комментарий от mashina

Спасибо, посмотрю. Много соединений это намеренно для бенчмарка.

nokachi ()

если мне не изменяет память, то сокет реально закрывается через некоторое достаточно продолжительное время после вызова close(), параметр SO_REUSEADDR как раз позволяет реиспользовать недозакрытый сокет тому же процессу

mm3 ★★★ ()
Ответ на: комментарий от mm3

Я вот не уверен что он именно сокет позволяет второй раз использовать — я думал что только разрешает сделать bind() к тому же порту и знает как реагировать на старые аки по sequence number.

nokachi ()
Ответ на: комментарий от vasily_pupkin

сорри, я их должно быть в разное время запустил. В общем, если я вижу что закрывается например fd 70, то я его потом вижу в /fd и никуда он не девается

nokachi ()
Ответ на: комментарий от mm3

если мне не изменяет память, то сокет реально закрывается через некоторое достаточно продолжительное время после вызова close()

Ага.

параметр SO_REUSEADDR как раз позволяет реиспользовать недозакрытый сокет тому же процессу

SO_REUSEADDR позволяет повторно использовать адрес, а не сокет. В коде выше ничего не понятно, но там наверное каждый раз порт новый, а значит адрес тоже новый.

gv ()
Ответ на: комментарий от gv

а значит адрес тоже новый

толково. Наверно SO_LINGER нужен. Но он в ghc как-то криво поддерживается, и я не уверен что работает.

nokachi ()

Сокет создаёт основная программа, а закрыает отдельный поток? Если так, то смотрите strace, каким системным вызовом создаётся поток (clone()?) и с какими опциями.

mky ★★★★★ ()
Ответ на: комментарий от nokachi

Но он в ghc как-то криво поддерживается, и я не уверен что работает.

А там нет setsockopt()? Может быть в хаскеле можно вызывать функции на C? Или перепишите на C/python/etc, больше народу сможет помочь.

gv ()
Ответ на: комментарий от mky

В хаскеле треды в юзерспейсе создаются и мапятся рантаймом на заранее созданные несколько ОС тредов. Так что там не важно в каком треде открыть сокет а в каком закрыть.

nokachi ()
Ответ на: комментарий от gv

Есть, но вместо структуры ему положено передавать инт, и непонятно что бы это значило. Пробовал 1 и 0 для setSocketOption Linger, сразу пишет too many open files. Делать через ffi такое слишком сложно. На чем-то другом писать не хочу.

nokachi ()
Ответ на: комментарий от nokachi

Есть, но вместо структуры ему положено передавать инт, и непонятно что бы это значило. Пробовал 1 и 0 для setSocketOption Linger, сразу пишет too many open files. Делать через ffi такое слишком сложно. На чем-то другом писать не хочу.

Ну тогда остается:

a) Забить.
b) Написать тестовую программу на C (без http и прочего), воспроизвести проблему, проверить, помогает ли SO_LINGER, если да — залезть в исходники вашей библиотеки и искать, что не так.

gv ()

Теперь я знаю, почему хаскель не надо использовать :)
Первое, что надо знать - закрытие сокета не означает его пропадание из ОС.
Второе, в хаскеле как и во всех других ЯП освобождение fd происходит только тогда, когда его освободила (и перед этим закрыла) программа. Иначе, вступают в силу настройки ОС: TIME_WAIT, FIN_WAIT и т.п. Если сокеты висят в ОС даже после закрытия, значит освободи их программой!

Использовать SO_LINGER нужно только тогда, когда клиент/сервер написаны криво и не умеют закрывать сокеты одновременно. Т.е. один из них игнорирует посылку fin ack.

Кроме close() иногда надо прямо использовать shutdown(fd, 2)

gh0stwizard ★★★★★ ()
Последнее исправление: gh0stwizard (всего исправлений: 3)
Ответ на: комментарий от gh0stwizard

Теперь я знаю, почему хаскель не надо использовать :)

тебе хацкель в любом случае противопоказан, т.к. ты начинаешь делать выводы не зная матчасти. Пожалуйста, не давай по нему советов.

qnikst ★★★★★ ()
Ответ на: комментарий от nokachi

я или слепой или не вижу в библиотеке закрытие и открытие сокета. Вроде как стандартное правило, кто открывает - тот и закрывает. P.S. почему iterIO?

qnikst ★★★★★ ()
Ответ на: комментарий от vasily_pupkin

Правильно, что они висят. Такое поведение по дефолту сделано для стороны, которая закрывает коннект. Сервер, который получил закрытие сокета вообще не уходит в это состояние. Сделано это для защиты клиента.

Тут все расписано:

http://www.serverframework.com/asynchronousevents/2011/01/time-wait-and-its-d...

http://www.isi.edu/touch/pubs/infocomm99/infocomm99-web/

gh0stwizard ★★★★★ ()
Ответ на: комментарий от qnikst

держи и импорты сразу перечисляй плз, удобнее, SSL убрал, т.к. разницы быть не должно, а тестить надо:

--- nokashi.hs.orig	2013-08-17 09:33:01.000000000 +0400
+++ nokashi.hs	2013-08-17 09:31:04.000000000 +0400
@@ -1,17 +1,33 @@
+{-# LANGUAGE ScopedTypeVariables, OverloadedStrings #-}
+import Control.Monad
+import Control.Monad.Trans
+import Control.Exception
+import Control.Concurrent
+
+import Data.IORef
+import Data.IterIO
+import Data.IterIO.HttpClient
+import Data.IterIO.Http
+import Data.Time.Clock
+import qualified Data.ByteString.Lazy as BL
+import OpenSSL
+import OpenSSL.Session as SSL
+import Network.Socket
+
 main :: IO ()
-main = multiBenchmark 1000 >> return ()
+main = void $ multiBenchmark 10
 
 -- separate each handshake by delta microseconds
 -- set up the proper socket options                    
-burst :: Int -> Int -> IO [(Iter BL.ByteString IO (), Onum BL.ByteString IO a)]
-burst n delta = do clients <- mapM (const mkClient) [1..n]
-                   mapM_ (\hc -> do let s = hcSock hc
-                                    setSocketOption s ReuseAddr 1) clients                   
-                   mapM (\c -> threadDelay delta >> putStrLn "connect"  >> httpConnect c) clients                   
+burst :: Int -> Int -> IO [(IO (),(Iter BL.ByteString IO (), Onum BL.ByteString IO a))]
+burst n delta = do clients <- replicateM n mkClient
+                   mapM_ (\hc -> setSocketOption (hcSock hc) ReuseAddr 1) clients                   
+                   mapM (\c -> threadDelay delta >> putStrLn "connect"  >> 
+                               fmap ((,) (sClose (hcSock c))) (httpConnect c)) clients
 
 mkClient :: IO HttpClient
-mkClient = do ctx <- sslContext
-              mkHttpClient "172.26.151.36" 11605 (Just ctx) True
+mkClient = do -- ctx <- sslContext
+              mkHttpClient "ya.ru" 80 Nothing False
 
 -- do series of bursts with a given rate
 benchmark :: IO String
@@ -20,8 +36,8 @@
   start <- getCurrentTime
   connections <- burst n 0
   -- create one thread per connection and send request -- collect responses
-  mvs <- mapM (const newEmptyMVar) [1..n]
-  ts <- mapM (\(d, mv, (i, o)) -> forkIO $ go d (i, o) `finally` putMVar mv ()) $ zip3 [1..n] mvs connections
+  mvs <- replicateM n newEmptyMVar
+  ts <- mapM (\(d, mv, (f, o)) -> forkIO $ go d o `finally` (f >> putMVar mv ())) $ zip3 [1..n] mvs connections
   -- wait for the threads to complete
   mapM_ takeMVar mvs
   stop <- getCurrentTime
@@ -30,10 +45,10 @@
   return "success"
   where go :: Int -> (Iter BL.ByteString IO (), Onum BL.ByteString IO ()) -> IO ()
         go d (i, o) = do threadDelay (d * 1000000)
-                         let request = defaultHttpReq{reqScheme="https",
+                         let request = defaultHttpReq{reqScheme="http",
                                                       reqMethod="GET",
-                                                      reqPath="/somewhere-on-the-server",
+                                                      reqPath="/",
                                                       reqVers=(1,1)}
                          count <- newIORef 0
                          o |$ inumHttpClient (request, "") (responseHandler count request 0) .| i
   
qnikst ★★★★★ ()
Ответ на: комментарий от fmap

а... runhaskell и ghci бывает выдают смешные проблемы связанные с кривой работой gc и освобождения ресурсов, поэтому логично сразу просить про способ запуска, ну и версию ghc..

qnikst ★★★★★ ()
Ответ на: комментарий от fmap

хотя тут разницы нет.. может ТС хотел побрутфорсить кого или просто пошутить.

qnikst ★★★★★ ()
Ответ на: комментарий от gh0stwizard

Кроме close() иногда надо прямо использовать shutdown(fd, 2)

А в чем разница между вызовом close() и вызовом shutdown() и затем сразу close()?

gv ()
Ответ на: комментарий от gv

А в чем разница между вызовом close() и вызовом shutdown() и затем сразу close()?

close() просто закрывает fd, shutdown() явно посылает в сокет fin. В языках отличных от си/си++ могут быть свои обертки для close().

gh0stwizard ★★★★★ ()
Ответ на: комментарий от gv

А в чем разница между вызовом close() и вызовом shutdown() и затем сразу close()?

close() не запустит shutdown(fd, SHUT_RDWR) если ресурс fd ещё где-то используется - через fdup() или в форкнутом/родительском процессе.

mashina ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.