2012-01-29 24 views
32

Parece que las reglas sobre el acceso concurrente no están documentadas (en el lado de Haskell) y simplemente supone que el desarrollador está familiarizado con el backend particular que se utiliza. Para las necesidades de producción, esta es una suposición perfectamente legítima, pero para prototipos y desarrollo casuales sería bueno si los paquetes persistentes - * fueran un poco más autónomos.Cuáles son las reglas sobre el acceso simultáneo a una base de datos persistente

Entonces, ¿cuáles son las reglas que rigen el acceso concurrente a sqlite persistente y la familia? Implícitamente, debe haber cierto grado de concurrencia permitido si tenemos grupos de conexiones, pero la creación trivial de un solo grupo de conexiones y la llamada al replicateM x $ forkIO (useThePool connectionPool) arroja el siguiente error.

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

EDITAR: Algunos ejemplos de código están a continuación.

En el siguiente código, corto 6 hilos (un número arbitrario: mi aplicación real tiene 3 hilos). Cada hilo almacena y busca constantemente un registro (un registro único del que se accede por los otros hilos, pero eso no importa), imprimiendo uno de los campos.

{-# 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 Los valores de 40, TEST, y todos los registros son arbitrarias para este ejemplo. Muchos valores, incluidos los más realistas, causan el mismo comportamiento.

También tenga en cuenta que, si bien puede estar roto cuando anida una acción no finalizada (a través de forever) dentro de una transacción DB (iniciada por runSqlPool), este no es el problema principal. Puede invertir esas operaciones y hacer que las transacciones sean arbitrariamente pequeñas, pero aún así terminar con excepciones periódicas.

La salida es por lo general les gusta:

$ ./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

¿Puedes dar más detalles sobre 'useThePool'? –

+0

@ DanBurton He dado más información a través de una edición. Ahora con un código de ejemplo que probablemente sea deslumbrantemente erróneo para aquellos que saben algo acerca de la persistencia. –

+0

@ ThomasM.DuBuisson, ¿ha intentado usar seleccionar en lugar de insertar y ver si puede reproducir el error? Si el error no ocurre en select, el error puede ser una excepción de prevención de interbloqueo lanzada a alguna parte, especialmente si está tratando de hacer inserciones concurrentes. Sin embargo, no tiene sentido si tienes un grupo de subprocesos. – Sal

Respuesta

16

Algo a destacar es que SQLite tiene problemas con el bloqueo cuando se almacena en NFS-como volúmenes (vboxsf, NFS, SMB, MVFS, etc.), que en muchos sistemas hacer que SQLite dé ese error incluso antes de que hayas abierto la base de datos. Estos volúmenes pueden implementar bloqueos de lectura/escritura fcntl() incorrectamente. (http://www.sqlite.org/faq.html#q5)

Suponiendo que no es el tema, también vale la pena mencionar que SQLite en realidad no soporta de forma nativa "conexiones" concurrentes (http://www.sqlite.org/faq.html#q6), ya que utiliza las cerraduras del sistema de archivos para asegurarse de que dos escrituras no ocurren al mismo hora. (Consulte la sección 3.0 de http://www.sqlite.org/lockingv3.html)

Suponiendo que todo esto se conoce, también puede verificar qué versión de sqlite3 tiene disponible para su entorno, ya que se produjeron algunos cambios en la forma en que se adquirieron los diferentes tipos de bloqueos. la serie 3.x: http://www.sqlite.org/sharedcache.html

Editar: Parte de la información adicional de la biblioteca persistir-sqlite3 This package includes a thin sqlite3 wrapper based on the direct-sqlite package, as well as the entire C library

envoltorio 'fino' me hizo decidir a tomar un vistazo para ver lo delgada que es; mirando el código, no parece que el envoltorio persistente tenga guardias contra un enunciado al fallo del grupo, excepto el guardia requerido para traducir/emitir el error e interrumpir la ejecución, aunque debo proporcionar la advertencia de que no me siento cómodo con Haskell.

Parece que tendrá que protegerse contra una declaración en el grupo que falla y vuelve a intentar, o que limita el tamaño del grupo en la inicialización a 1 (que parece menos que ideal).)

Cuestiones relacionadas