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)

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

ilovewindows ★★★★★
()
Последнее исправление: ilovewindows (всего исправлений: 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

там список импортов, впрочем проблема ТС у меня не воспроизвелась, все fd в /proc закрываются корректно.

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 ★★★★★
()
Вы не можете добавлять комментарии в эту тему: топик перемещен в архив.