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.
Si può sostituire l'intero db con un file di testo pigro per vedere se è davvero il db? –
Ho effettivamente rimosso l'intera parte del database e ha ancora lo stesso problema. Modificheremo il post per riflettere questo. – nejstastnejsistene
Sostituisci la parte di download con qualcosa di fisso come "let page =" "' –