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.)
¿Puedes dar más detalles sobre 'useThePool'? –
@ 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. –
@ 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