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.)
Puoi fornire maggiori dettagli su 'useThePool'? –
@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. –
@ 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