Кажется, что правила одновременного доступа недокументированы (на стороне Haskell) и просто предполагают, что разработчик знаком с конкретным используемым бэкэндом. Для производственных нужд это совершенно законное предположение, но для случайного прототипирования и разработки было бы неплохо, если бы пакеты persist-* были немного более автономными.
Итак, каковы правила, регулирующие одновременный доступ к persistence-sqlite и семейству? Неявно должна быть разрешена некоторая степень параллелизма, если у нас есть пулы соединений, но тривиальное создание одного пула соединений и вызов replicateM x $ forkIO (useThePool connectionPool)
дает следующую ошибку.
user error (SQLite3 returned ErrorBusy while attempting to perform step.)
РЕДАКТИРОВАТЬ: Ниже приведен пример кода.
В приведенном ниже коде я отделяю 6 потоков (произвольное число — мое фактическое приложение выполняет 3 потока). Каждый поток постоянно сохраняет и ищет запись (уникальную запись из той, к которой обращаются другие потоки, но это не имеет значения), выводя одно из полей.
{-# 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. Значения 40
, TEST
и всех записей для этого примера произвольны. Многие значения, в том числе более реалистичные, вызывают такое же поведение.
Также обратите внимание, что, хотя это может быть явно нарушено, когда вы вкладываете не завершающее действие (через forever
) в транзакцию БД (запущенную runSqlPool
), это не основная проблема. Вы можете инвертировать эти операции и сделать транзакции сколь угодно маленькими, но все равно получать периодические исключения.
Вывод обычно такой:
$ ./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.)
useThePool
? - person Dan Burton   schedule 30.01.2012