2012-01-29 49 views
32

Sembra che le regole sull'accesso simultaneo non siano documentate (sul lato Haskell) e si presupponga semplicemente che lo sviluppatore abbia familiarità con il particolare back-end utilizzato. Per esigenze di produzione, questo è un assunto perfettamente legittimo, ma per la prototipazione e lo sviluppo casuali sarebbe bello se i pacchetti persistenti fossero più autosufficienti.Quali sono le regole sull'accesso simultaneo a un database persistente

Quindi, quali sono le regole che regolano l'accesso concorrente a persistente-sqlite e famiglia? Implicitamente, ci deve essere un certo grado di concorrenza consentito se disponiamo di pool di connessioni, ma la creazione banale di un singolo pool di connessioni e la chiamata di replicateM x $ forkIO (useThePool connectionPool) danno l'errore seguente.

user error (SQLite3 returned ErrorBusy while attempting to perform step.) 

MODIFICA: alcuni esempi di codice sono ora sotto.

Nel seguente codice I fork off 6 thread (un numero arbitrario - la mia applicazione effettiva fa 3 thread). Ogni thread memorizza costantemente e cerca un record (un record univoco da quello a cui si accede dagli altri thread, ma non importa), stampando uno dei campi.

{-# LANGUAGE TemplateHaskell, QuasiQuotes 
      , TypeFamilies, FlexibleContexts, GADTs 
      , OverloadedStrings #-} 
import Control.Concurrent (forkIO, threadDelay) 
import Database.Persist 
import Database.Persist.Sqlite hiding (get) 
import Database.Persist.TH 
import Control.Monad 
import Control.Monad.IO.Class 

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persist| 
SomeData 
    myId Int 
    myData Double 
    MyId myId 
|] 

main = withSqlitePool "TEST" 40 $ \pool -> do 
    runSqlPool (runMigration migrateAll) pool 
    mapM_ forkIO [runSqlPool (dbThread i) pool | i <- [0..5]] 
    threadDelay maxBound 

dbThread :: Int -> SqlPersist IO() 
dbThread i = forever $ do 
    x <- getBy (MyId i) 
    insert (SomeData i (fromIntegral i)) 
    liftIO (print x) 
    liftIO (threadDelay 100000) -- Just to calm down the CPU, 
           -- not needed for demonstrating 
           -- the problem 

NB I valori di 40, TEST, e tutte le registrazioni sono arbitrari per questo esempio. Molti valori, compresi quelli più realistici, provocano lo stesso comportamento.

Si noti inoltre che, sebbene possa essere ovviamente interrotto quando si annulla un'azione non di terminazione (tramite forever) all'interno di una transazione DB (avviata da runSqlPool), questo non è il problema principale. È possibile invertire queste operazioni e rendere le transazioni arbitrariamente piccole, ma finiscono comunque con eccezioni periodiche.

L'uscita è solitamente come:

$ ./so 
Nothing 
so: user error (SQLite3 returned ErrorBusy while attempting to perform step.) 
so: user error (SQLite3 returned ErrorBusy while attempting to perform step.) 
so: user error (SQLite3 returned ErrorBusy while attempting to perform step.) 
so: user error (SQLite3 returned ErrorBusy while attempting to perform step.) 
so: user error (SQLite3 returned ErrorBusy while attempting to perform step.) 
so: user error (SQLite3 returned ErrorConstraint while attempting to perform step.) 
+0

Puoi fornire maggiori dettagli su 'useThePool'? –

+0

@DanBurton Ho dato ulteriori informazioni tramite una modifica. Ora con un codice di esempio che è probabilmente blindingly sbagliato per coloro che sanno qualcosa di persistente. –

+0

@ ThomasM.DuBuisson, hai provato a utilizzare select anziché insert e vedere se riesci a riprodurre l'errore? Se l'errore non si verifica su select, l'errore potrebbe essere un'eccezione di prevenzione deadlock generata da qualche parte, soprattutto se si sta tentando di eseguire inserimenti concomitanti. Non ha senso se hai un pool di thread. – Sal

risposta

16

cosa degna di nota è che SQLite ha problemi con bloccaggio quando memorizzati su NFS come volumi (vboxsf, NFS, SMB, MVFS, ecc) su molti sistemi che causa SQLite per dare quell'errore anche prima che tu abbia aperto correttamente il database. Questi volumi possono implementare i blocchi di lettura/scrittura fcntl() in modo errato. (http://www.sqlite.org/faq.html#q5)

Partendo dal presupposto che non è questo il problema, è anche opportuno ricordare che SQLite in realtà non supporta nativamente simultanee "connessioni" (http://www.sqlite.org/faq.html#q6), in quanto utilizza i blocchi del file system per garantire che due operazioni di scrittura non si verificano allo stesso tempo. (Vedere la sezione 3.0 di http://www.sqlite.org/lockingv3.html)

Supponendo che tutto questo sia noto, è anche possibile verificare quale versione di sqlite3 è disponibile per il proprio ambiente, poiché alcune modifiche al modo in cui vengono acquisiti diversi tipi di blocchi si sono verificati nel la serie 3.x: http://www.sqlite.org/sharedcache.html

Edit: Alcune informazioni aggiuntive dalla libreria persistere-sqlite3 This package includes a thin sqlite3 wrapper based on the direct-sqlite package, as well as the entire C library

wrapper 'sottile' mi ha fatto decidere di dare un'occhiata a questo per vedere quanto sottile che è; guardando il codice non sembra che il wrapper persistente abbia qualche guardia contro una dichiarazione al pool fallendo tranne la guardia richiesta per tradurre/emettere l'errore e interrompere l'esecuzione, anche se devo fornire l'avvertenza che non mi sento a mio agio con Haskell.

Sembra che sarà necessario proteggersi da un'asserzione nel pool che non riesce e ritenterebbe, o che si limita la dimensione del pool all'inizializzazione a 1 (che sembra meno che ideale.)

Problemi correlati