LINUX.ORG.RU

Контроль за ресурсами в Haskell-программе.

 


0

3

Есть такой вопрос: идёт поток запросов к TCP-серверу. Я этот поток обрабатываю, вытаскиваю данные, делаю разные запросы к другим серверам, результат возвращаю.

По сути, мой код является прокси-сервером, который дружит старое ПО с новым. Написан на кондуитах.

С ним есть проблема, решить которую я не смог: теряются ресурсы в виде файловых дескрипторов. Происходит это из-за подобного подхода:

main :: IO ()
main =
  runTCPServer (serverSettings 4002 hostnameListen) $ \proxy ->
    appSource proxy $= … $$ appSink proxy

…


condCyrusGet :: (MonadIO m, Monad m) => Conduit ByteString m ByteString
condCyrusGet = awaitForever $ \bs -> do
  res <- liftIO $ cyrusSock bs
  yield res

cyrusAppAddr = "/run/cyrusserv/cyrus.socket"

cyrusSock :: ByteString -> IO ByteString
cyrusSock ar = do
  soc <- NS.socket AF_UNIX Stream 0
  NS.connect soc (SockAddrUnix cyrusAppAddr)
  NSB.send soc ar
  msg <- NSB.recv soc 32768
  NS.sClose soc
  return msg

при подключении к сокету, который слушается совсем простым сервером:

hostnameListen = "/run/cyrusserv/cyrus.socket"
cyrusHostname = "127.0.0.1"

main :: IO ()
main = 
  runUnixServer (CNU.serverSettings hostnameListen) $ \proxy ->
    runTCPClient (CN.clientSettings 12345 cyrusHostname) $ \client_cyrus ->
      runConcurrently $
        Concurrently (appSource proxy $$ appSink client_cyrus) *>
          Concurrently (appSource client_cyrus $$ appSink proxy)

Его предназначением является связь каких-то локально запущенных серверов с одним удалённым.

Не закрываются вовремя подключения к Unix-сокету в функции cyrusSock и постепенно количество незакрытых соединений растёт. В итоге, всё заканчивается

socket: resource exhausted (Too many open files)

Полагаю, что проблема решается загоном этой функции cyrusSock и вообще, общения с этим Unix-сокетом через кондуит, примерно так, как сделано в последнем куске кода, но как это сделать — не представляю. Использовать bracket?

Что подскажет уважаемое сообщество?



Последнее исправление: HolyBoy (всего исправлений: 1)

лень думать, но попробуй заменить runConcurrently на race_ (appSource proxy $$ appSink client_cyrus) (appSource client_cyrus $$ appSink proxy)

Ступил, это тут ни при чем.

qnikst ★★★★★
()
Последнее исправление: qnikst (всего исправлений: 1)

Полагаю, что проблема решается загоном этой функции cyrusSock и вообще, общения с этим Unix-сокетом через кондуит, примерно так, как сделано в последнем куске кода, но как это сделать — не представляю. Использовать bracket?

вообще в кондуитах для этого resourceT придумано.

qnikst ★★★★★
()

Его предназначением является связь каких-то локально запущенных серверов с одним удалённым.

Назначение Haskell не в этом. Ты не понял сути Haskell, он работает по-другому.

anonymous
()

При получении exception в NS.connect, NSB.send или NSB.recv у тебя вытекает по сокету. Довольно логично, что в конце концов дескрипторов не остается.

anonymous
()

Что подскажет уважаемое сообщество?

Не использовать академический язык для задач системного программирования.

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

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

Это не системное программирование

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

в данном случае аноним все правильно сказал (про исключения) и даже банального bracket в cyrusSock хватит.

qnikst ★★★★★
()

Довольно странно, что на каждый кусок байтостроки внутри кондуита ты открываешь и закрываешь сокет заново.

Может логичнее сначала открыть сокет, а потом запускать коднуит?


main :: IO ()
main =
    runTCPServer (serverSettings 4002 hostnameListen)
    $ \proxy -> bracket
                mkSock
                NS.sClose
                $ \sock -> appSource sock proxy $= … $$ appSink proxy
  where
    mkSock = do
        s <- NS.socket AF_UNIX Stream 0
        NS.connect s (SockAddrUnix cyrusAppAddr)
        return s


…


condCyrusGet :: (MonadIO m, Monad m)
             => Socket
             -> Conduit ByteString m ByteString
condCyrusGet sock = awaitForever $ \bs -> do
  res <- liftIO $ cyrusSock sock bs
  yield res

cyrusAppAddr = "/run/cyrusserv/cyrus.socket"

cyrusSock :: Socket
          -> ByteString
          -> IO ByteString
cyrusSock sock ar = do
  NSB.send soc ar
  msg <- NSB.recv soc 32768
  return msg

Либо использовать ResourceT вместо bracket. Если лень передавать открытый сокет внутрь функций можешь использовать ReaderT

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

Да, ты прав, мне самому не нравится многократное открытие сокета, однако, я не мог сформулировать нужный вопрос. Как говорится: хороший вопрос — половина ответа. Спасибо.

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