Демон веб-парсинга Haskell http-проводник аварийно завершает работу с ошибкой нехватки памяти

Я написал на Haskell демона, который считывает информацию с веб-страницы каждые 5 минут.

Первоначально демон работал нормально около 50 минут, но затем неожиданно умер с out of memory (requested 1048576 bytes). Каждый раз, когда я запускал его, он умирал через такое же количество времени. Установив его в спящий режим всего на 30 секунд, он умер через 8 минут.

Я понял, что код для очистки веб-сайта был невероятно неэффективным с памятью (примерно с 30 МБ во время сна до 250 МБ при анализе 9 МБ HTML), поэтому я переписал его так, что теперь он использует только около 15 МБ дополнительных при разборе. Думая, что проблема решена, я запустил демон на ночь, и когда я проснулся, он фактически использовал меньше памяти, чем той ночью. Я думал, что закончил, но примерно через 20 часов после запуска он вылетел с той же ошибкой.

Я начал изучать профилирование ghc, но не смог заставить его работать. Затем я начал возиться с параметрами rts , и я попытался установить -H64m, чтобы размер кучи по умолчанию был больше, чем использовалась моей программой, а также использовал -Ksize, чтобы уменьшить максимальный размер стека, чтобы увидеть, приведет ли это к более раннему сбою.

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

В исходном коде я определил сбой где-то в парсинге html, но я не сделал того же для версии с более эффективным использованием памяти, потому что 20 часов занимает очень много времени. Я не знаю, будет ли это полезно знать, потому что не похоже, что какая-то конкретная часть программы сломана, потому что она успешно выполнялась в течение десятков итераций до сбоя.

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

(Изменить: код переписан и перемещен в конец сообщения)

Я новичок в Haskell, поэтому надеюсь, что это какая-то причуда ленивого оценивания или что-то еще, что можно быстро исправить. В противном случае, у меня только что закончились идеи.

Я использую GHC версии 7.4.2 на FreeBsd 9.1

Редактировать:

Замена загрузки статическим html избавила от проблемы, поэтому я сузил ее до того, как я использую http-pipeline. Я отредактировал приведенный выше код, включив в него свой сетевой код. В хакерских документах упоминается о совместном использовании менеджера, так что я это сделал. В нем также говорится, что для http вы должны явно закрывать соединения, но я не думаю, что мне нужно делать это для httpLbs.

Вот мой код.

import Control.Monad.IO.Class (liftIO)
import qualified Data.Text as T
import qualified Data.ByteString.Lazy as BL
import Text.Regex.PCRE
import Network.HTTP.Conduit

main :: IO ()
main = do
    manager <- newManager def
    daemonLoop manager

daemonLoop :: Manager -> IO ()
daemonLoop manager = do
    rows <- scrapeWebpage manager
    putStrLn $ "number of rows parsed: " ++ (show $ length rows)
    doSleep
    daemonLoop manager

scrapeWebpage :: Manager -> IO [[BL.ByteString]]
scrapeWebpage manager = do
    putStrLn "before makeRequest"
    html <- makeRequest manager
    -- Force evaluation of html.
    putStrLn $ "html length: " ++ (show $ BL.length html)
    putStrLn "after makeRequest"
    -- Breaks ~10M html table into 2d list of bytestrings.
    -- Max memory usage is about 45M, which is about 15M more than when sleeping.
    return $ map tail $ html =~ pattern
    where
        pattern :: BL.ByteString
        pattern = BL.concat $ replicate 12 "<td[^>]*>([^<]+)</td>\\s*"

makeRequest :: Manager -> IO BL.ByteString
makeRequest manager = runResourceT $ do
    defReq <- parseUrl url
    let request = urlEncodedBody params $ defReq
                    -- Don't throw errors for bad statuses.
                    { checkStatus = \_ _ -> Nothing
                    -- 1 minute.
                    , responseTimeout = Just 60000000
                    }
    response <- httpLbs request manager
    return $ responseBody response

и его вывод:

before makeRequest
html length: 1555212
after makeRequest
number of rows parsed: 3608
...
before makeRequest
html length: 1555212
after makeRequest
bannerstalkerd: out of memory (requested 2097152 bytes)

Избавление от вычислений регулярных выражений устранило проблему, но кажется, что ошибка возникает после подключения к сети и во время регулярного выражения, предположительно из-за того, что я делаю неправильно с http-проводником. Любые идеи?

Кроме того, когда я пытаюсь скомпилировать с включенным профилированием, я получаю эту ошибку:

Could not find module `Network.HTTP.Conduit'
Perhaps you haven't installed the profiling libraries for package `http-conduit-1.8.9'?

Действительно, я не установил библиотеки профилирования для http-conduit и не знаю, как это сделать.


person nejstastnejsistene    schedule 25.02.2013    source источник
comment
Можете ли вы заменить всю базу данных ленивым текстовым файлом, чтобы проверить, действительно ли это база данных?   -  person Gert Cuykens    schedule 25.02.2013
comment
Я фактически удалил всю часть базы данных, и у нее все еще та же проблема. Я отредактирую сообщение, чтобы отразить это.   -  person nejstastnejsistene    schedule 25.02.2013
comment
Замените часть загрузки чем-то исправленным, например let page = "<html></html>"   -  person Gert Cuykens    schedule 25.02.2013
comment
Возможно, у вас проблема с праздником.   -  person Alexei Averchenko    schedule 25.02.2013
comment
Профилируйте программу   -  person n. 1.8e9-where's-my-share m.    schedule 25.02.2013
comment
Я сузил проблему до способа использования http-conduit. В своей редакции я объяснил, почему я не могу скомпилировать программу с профилированием.   -  person nejstastnejsistene    schedule 25.02.2013
comment
попробуйте import qualified Data.ByteString.Char8 (строгий)   -  person Gert Cuykens    schedule 26.02.2013
comment
также попробуйте html = pattern   -  person Gert Cuykens    schedule 26.02.2013


Ответы (2)


Итак, вы обнаружили утечку. Обманывая параметры компилятора и настройки памяти, вы можете только отложить момент сбоя вашей программы, но вы не можете устранить источник проблемы, поэтому независимо от того, что вы там установили, в конечном итоге у вас все равно закончится нехватка памяти.

Я рекомендую вам внимательно изучить весь нечистый код и в первую очередь часть, работающую с ресурсами. Проверьте, все ли ресурсы высвобождаются правильно. Проверьте, есть ли у вас состояние накопления, например, растущий неограниченный канал. И, конечно же, как мудро предложил нм, профиль.

У меня есть парсер, который анализирует страницы без пауз и скачивает файлы, и делает все это одновременно. Я никогда не видел, чтобы он использовал больше памяти, чем ~ 60 МБ. Я компилировал его с помощью GHC 7.4.2, GHC 7.6.1 и GHC 7.6.2, и ни с одним из них не было проблем.

Следует отметить, что корень вашей проблемы также может быть в библиотеках, которые вы используете. В моем скребке я использую http-conduit, http-conduit-browser, HandsomeSoup и HXT.

person Nikita Volkov    schedule 25.02.2013
comment
Похоже, вы на правильном пути. Я использую http-conduit и regex-pcre для веб-сканирования, и я редактировал весь код, который использую. Моя программа никогда не использует больше 45M, но по какой-то причине все равно умирает. Мой http-conduit код довольно прост, и я не понимаю, где я могу неправильно обращаться с ресурсами. - person nejstastnejsistene; 25.02.2013
comment
@Nikita Можно ли поделиться своим кодом? Я также занимаюсь парсингом веб-страниц с помощью Haskell и хотел бы поучиться на вашем коде. - person McBear Holden; 25.04.2014
comment
@osager Извини. Это частный проект. Однако существует множество руководств. Например, this и это. - person Nikita Volkov; 25.04.2014

В итоге я решил свою проблему. Похоже, это ошибка GHC во FreeBSD. Я отправил отчет об ошибке и переключился на Linux, и теперь он работает безупречно последние несколько дней.

person nejstastnejsistene    schedule 03.03.2013