2013-02-25 14 views
8

Ho scritto un demone in Haskell che cancella le informazioni da una pagina Web ogni 5 minuti.Il demone web-scraping http-conduit di Haskell si arresta in modo anomalo con errore di memoria insufficiente

Il daemon in origine funzionava bene per circa 50 minuti, ma poi è morto improvvisamente con out of memory (requested 1048576 bytes). Ogni volta che l'ho eseguito è morto dopo la stessa quantità di tempo. Impostandolo per dormire solo 30 secondi, è morto dopo 8 minuti.

Mi sono reso conto che il codice per raschiare il sito Web era incredibilmente inefficiente (passando da circa 30 M mentre si dormiva a 250 M mentre analizzava 9 M di html), quindi l'ho riscritto in modo che ora utilizzi solo circa 15 M extra durante l'analisi. Pensando che il problema è stato risolto, ho eseguito il demone durante la notte e quando mi sono svegliato, in realtà utilizzavo meno memoria di quella notte. Pensavo di aver finito, ma circa 20 ore dopo che era iniziato, si era schiantato con lo stesso errore.

Ho iniziato a esaminare il profilo di ghc ma non sono riuscito a farlo funzionare. Successivamente ho iniziato a scherzare con rts options, e ho provato a impostare -H64m per impostare la dimensione dell'heap predefinita in modo che fosse maggiore del mio programma e usare -Ksize per ridurre la dimensione massima dello stack per vedere se si sarebbe arrestato prima.

Nonostante ogni modifica apportata, il daemon sembra ancora bloccarsi dopo un numero costante di iterazioni. Rendere l'analisi più efficiente della memoria ha reso questo valore più alto, ma si blocca ancora. Questo non ha senso per me perché nessuno di questi ha funzionato si è persino avvicinato ad usare tutta la mia memoria, molto meno lo spazio di swap. La dimensione dell'heap dovrebbe essere illimitata per impostazione predefinita, la riduzione della dimensione dello stack non ha fatto la differenza e tutti i miei ulimits sono illimitati o significativamente più alti di quello che sta usando il demone.

Nel codice originale ho individuato l'arresto anomalo in qualche parte dell'analisi HTML, ma non ho fatto lo stesso per la versione più efficiente della memoria perché 20 ore richiedono così tanto tempo per essere eseguito. Non so se questo sarebbe anche utile sapere perché non sembra che una parte specifica del programma sia interrotta perché viene eseguita correttamente per dozzine di iterazioni prima di bloccarsi.

Fuori dalle idee, ho anche guardato attraverso il ghc source code per questo errore, e sembra che sia una chiamata fallita a mmap, che non è stata molto utile per me perché presumo che non sia la radice del problema.

(Edit: il codice riscritto e spostato alla fine del post)

Sono abbastanza nuovo a Haskell, quindi spero questo è qualche bizzarria di valutazione pigra o qualcos'altro che ha una soluzione rapida. Altrimenti, sono fresco di idee.

sto usando GHC versione 7.4.2 su FreeBSD 9.1

Edit:

Sostituzione del scaricano con html statico sbarazzati del problema, così ho ristretto la scelta a come sto utilizzando http-conduit. Ho modificato il codice sopra per includere il mio codice di rete. I documenti di hackage menzionano di condividere un manager così l'ho fatto. E dice anche che per http devi chiudere esplicitamente le connessioni, ma non credo di doverlo fare per httpLbs.

Ecco il mio codice.

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 

ed è uscita:

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) 

Come liberarsi dei calcoli regex risolto il problema, ma sembra che l'errore accade dopo la messa in rete e durante la regex, presumibilmente a causa di qualcosa che mi sbagliare con http-conduit. Qualche idea?

Inoltre, quando provo a compilare con profiling abilitato ottengo questo errore:

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

librerie profilazione In effetti, non ho installato per http-conduit e io non so come.

+0

Si può sostituire l'intero db con un file di testo pigro per vedere se è davvero il db? –

+0

Ho effettivamente rimosso l'intera parte del database e ha ancora lo stesso problema. Modificheremo il post per riflettere questo. – nejstastnejsistene

+2

Sostituisci la parte di download con qualcosa di fisso come "let page =" "' –

risposta

3

Ho finito per risolvere il mio problema. Sembra essere un bug di GHC su FreeBSD. Ho inviato una segnalazione di bug e sono passato a Linux, e ora funziona senza problemi negli ultimi giorni.

4

Quindi ti sei trovato una perdita. Trascinando con le opzioni del compilatore e le impostazioni della memoria è possibile solo posticipare il momento in cui il programma si blocca, ma non è possibile eliminare la fonte del problema, quindi, indipendentemente da ciò che si imposta lì, alla fine si esaurirà ancora la memoria.

Vi consiglio di percorrere con attenzione tutto il codice non puro e in primo luogo la parte che lavora con le risorse. Controlla se tutte le risorse vengono rilasciate correttamente. Controlla se hai uno stato di accumulo, come un canale in espansione. E, naturalmente, come saggiamente suggerito da n. profile it.

Ho un raschietto che analizza le pagine senza interrompere e scarica i file e fa tutto contemporaneamente. Non l'ho mai visto usare più memoria di ~ 60M. L'ho compilato con GHC 7.4.2, GHC 7.6.1 e GHC 7.6.2 e non ho avuto problemi con nessuno dei due.

Si noti che la radice del problema potrebbe trovarsi anche nelle librerie che si stanno utilizzando. Nel mio raschietto io uso http-conduit, http-conduit-browser, HandsomeSoup e HXT.

+0

Sembra che tu sia sulla strada giusta. Sto usando 'http-conduit' e' regex-pcre' per il webscraping, e ho modificato tutto il codice che sto usando. Il mio programma non usa mai più di circa 45M, ma muore comunque per qualche motivo. Il mio codice 'http-conduit' è piuttosto semplice, e non vedo dove potrei essere maltrattare le risorse. – nejstastnejsistene

+0

@Nikita È possibile condividere il tuo codice? Sto anche facendo scraping web con Haskell e vorrei imparare dal tuo codice. – osager

+0

@osager Spiacente. È un progetto privato. Tuttavia ci sono un sacco di tutorial là fuori. Ad esempio, [this] (http://egonschiele.github.io/HandsomeSoup/) e [this] (http://adit.io/posts/2012-03-10-building_a_concurrent_web_scraper_with_haskell.html). –

Problemi correlati