acid-state 0.5.2 → 0.6.0
raw patch · 28 files changed
+817/−205 lines, 28 filesdep +networkdep ~basedep ~safecopyPVP ok
version bump matches the API change (PVP)
Dependencies added: network
Dependency ranges changed: base, safecopy
API changes (from Hackage documentation)
- Data.Acid: createArchive :: AcidState st -> IO ()
- Data.Acid: createCheckpointAndClose :: SafeCopy st => AcidState st -> IO ()
- Data.Acid: openAcidState :: (Typeable st, IsAcidic st) => st -> IO (AcidState st)
- Data.Acid: openAcidStateFrom :: IsAcidic st => FilePath -> st -> IO (AcidState st)
- Data.Acid: query' :: (QueryEvent event, MonadIO m) => AcidState (EventState event) -> event -> m (EventResult event)
- Data.Acid: update' :: (UpdateEvent event, MonadIO m) => AcidState (EventState event) -> event -> m (EventResult event)
- Data.Acid.Core: Method :: (method -> State (MethodState method) (MethodResult method)) -> MethodContainer (MethodState method)
- Data.Acid.Core: class (Typeable ev, SafeCopy ev, Typeable (MethodResult ev), SafeCopy (MethodResult ev)) => Method ev where { type family MethodResult ev; type family MethodState ev; { methodTag ev = pack (show (typeOf ev)) } }
- Data.Acid.Core: closeCore :: Core st -> IO ()
- Data.Acid.Core: closeCore' :: Core st -> (st -> IO ()) -> IO ()
- Data.Acid.Core: data Core st
- Data.Acid.Core: data MethodContainer st
- Data.Acid.Core: lookupColdMethod :: Core st -> Tagged ByteString -> State st ByteString
- Data.Acid.Core: lookupHotMethod :: Method method => MethodMap (MethodState method) -> method -> State (MethodState method) (MethodResult method)
- Data.Acid.Core: methodTag :: Method ev => ev -> Tag
- Data.Acid.Core: mkCore :: [MethodContainer st] -> st -> IO (Core st)
- Data.Acid.Core: mkMethodMap :: [MethodContainer st] -> MethodMap st
- Data.Acid.Core: modifyCoreState :: Core st -> (st -> IO (st, a)) -> IO a
- Data.Acid.Core: modifyCoreState_ :: Core st -> (st -> IO st) -> IO ()
- Data.Acid.Core: runColdMethod :: Core st -> Tagged ByteString -> IO ByteString
- Data.Acid.Core: runHotMethod :: Method method => Core (MethodState method) -> method -> IO (MethodResult method)
- Data.Acid.Core: type MethodMap st = Map Tag (MethodContainer st)
- Data.Acid.Core: type Tagged a = (Tag, a)
- Data.Acid.Core: withCoreState :: Core st -> (st -> IO a) -> IO a
- Data.Acid.Local: QueryEvent :: (ev -> Query (EventState ev) (EventResult ev)) -> Event (EventState ev)
- Data.Acid.Local: UpdateEvent :: (ev -> Update (EventState ev) (EventResult ev)) -> Event (EventState ev)
- Data.Acid.Local: acidEvents :: IsAcidic st => [Event st]
- Data.Acid.Local: class SafeCopy st => IsAcidic st
- Data.Acid.Local: class Method ev => QueryEvent ev
- Data.Acid.Local: class Method ev => UpdateEvent ev
- Data.Acid.Local: closeAcidState :: AcidState st -> IO ()
- Data.Acid.Local: createCheckpoint :: SafeCopy st => AcidState st -> IO ()
- Data.Acid.Local: data AcidState st
- Data.Acid.Local: data Event st
- Data.Acid.Local: data Query st a
- Data.Acid.Local: data Update st a
- Data.Acid.Local: openAcidState :: (Typeable st, IsAcidic st) => st -> IO (AcidState st)
- Data.Acid.Local: openAcidStateFrom :: IsAcidic st => FilePath -> st -> IO (AcidState st)
- Data.Acid.Local: query :: QueryEvent event => AcidState (EventState event) -> event -> IO (EventResult event)
- Data.Acid.Local: query' :: (QueryEvent event, MonadIO m) => AcidState (EventState event) -> event -> m (EventResult event)
- Data.Acid.Local: runQuery :: Query st a -> Update st a
- Data.Acid.Local: scheduleUpdate :: UpdateEvent event => AcidState (EventState event) -> event -> IO (MVar (EventResult event))
- Data.Acid.Local: type EventResult ev = MethodResult ev
- Data.Acid.Local: type EventState ev = MethodState ev
- Data.Acid.Local: update :: UpdateEvent event => AcidState (EventState event) -> event -> IO (EventResult event)
- Data.Acid.Local: update' :: (UpdateEvent event, MonadIO m) => AcidState (EventState event) -> event -> m (EventResult event)
- Data.Acid.Memory: QueryEvent :: (ev -> Query (EventState ev) (EventResult ev)) -> Event (EventState ev)
- Data.Acid.Memory: UpdateEvent :: (ev -> Update (EventState ev) (EventResult ev)) -> Event (EventState ev)
- Data.Acid.Memory: acidEvents :: IsAcidic st => [Event st]
- Data.Acid.Memory: class SafeCopy st => IsAcidic st
- Data.Acid.Memory: class Method ev => QueryEvent ev
- Data.Acid.Memory: class Method ev => UpdateEvent ev
- Data.Acid.Memory: closeAcidState :: AcidState st -> IO ()
- Data.Acid.Memory: createCheckpoint :: SafeCopy st => AcidState st -> IO ()
- Data.Acid.Memory: createCheckpointAndClose :: SafeCopy st => AcidState st -> IO ()
- Data.Acid.Memory: data AcidState st
- Data.Acid.Memory: data Event st
- Data.Acid.Memory: data Query st a
- Data.Acid.Memory: data Update st a
- Data.Acid.Memory: openAcidState :: IsAcidic st => st -> IO (AcidState st)
- Data.Acid.Memory: query :: QueryEvent event => AcidState (EventState event) -> event -> IO (EventResult event)
- Data.Acid.Memory: query' :: (QueryEvent event, MonadIO m) => AcidState (EventState event) -> event -> m (EventResult event)
- Data.Acid.Memory: runQuery :: Query st a -> Update st a
- Data.Acid.Memory: scheduleUpdate :: UpdateEvent event => AcidState (EventState event) -> event -> IO (MVar (EventResult event))
- Data.Acid.Memory: type EventResult ev = MethodResult ev
- Data.Acid.Memory: type EventState ev = MethodState ev
- Data.Acid.Memory: update :: UpdateEvent event => AcidState (EventState event) -> event -> IO (EventResult event)
- Data.Acid.Memory: update' :: (UpdateEvent event, MonadIO m) => AcidState (EventState event) -> event -> m (EventResult event)
+ Data.Acid: openLocalState :: (Typeable st, IsAcidic st) => st -> IO (AcidState st)
+ Data.Acid: openLocalStateFrom :: IsAcidic st => FilePath -> st -> IO (AcidState st)
+ Data.Acid.Advanced: QueryEvent :: (ev -> Query (EventState ev) (EventResult ev)) -> Event (EventState ev)
+ Data.Acid.Advanced: UpdateEvent :: (ev -> Update (EventState ev) (EventResult ev)) -> Event (EventState ev)
+ Data.Acid.Advanced: acidEvents :: IsAcidic st => [Event st]
+ Data.Acid.Advanced: class SafeCopy st => IsAcidic st
+ Data.Acid.Advanced: class (Typeable ev, SafeCopy ev, Typeable (MethodResult ev), SafeCopy (MethodResult ev)) => Method ev where { type family MethodResult ev; type family MethodState ev; { methodTag ev = pack (show (typeOf ev)) } }
+ Data.Acid.Advanced: data Event st
+ Data.Acid.Advanced: methodTag :: Method ev => ev -> Tag
+ Data.Acid.Advanced: query' :: (QueryEvent event, MonadIO m) => AcidState (EventState event) -> event -> m (EventResult event)
+ Data.Acid.Advanced: scheduleUpdate :: UpdateEvent event => AcidState (EventState event) -> event -> IO (MVar (EventResult event))
+ Data.Acid.Advanced: update' :: (UpdateEvent event, MonadIO m) => AcidState (EventState event) -> event -> m (EventResult event)
+ Data.Acid.Local: instance Typeable1 LocalState
+ Data.Acid.Local: openLocalState :: (Typeable st, IsAcidic st) => st -> IO (AcidState st)
+ Data.Acid.Local: openLocalStateFrom :: IsAcidic st => FilePath -> st -> IO (AcidState st)
+ Data.Acid.Memory: instance Typeable1 MemoryState
+ Data.Acid.Memory: openMemoryState :: IsAcidic st => st -> IO (AcidState st)
+ Data.Acid.Remote: acidServer :: SafeCopy st => AcidState st -> PortID -> IO ()
+ Data.Acid.Remote: instance Serialize Command
+ Data.Acid.Remote: instance Serialize Response
+ Data.Acid.Remote: instance Typeable1 RemoteState
+ Data.Acid.Remote: openRemoteState :: IsAcidic st => HostName -> PortID -> IO (AcidState st)
- Data.Acid: createCheckpoint :: SafeCopy st => AcidState st -> IO ()
+ Data.Acid: createCheckpoint :: AcidState st -> IO ()
- Data.Acid.Memory.Pure: openAcidState :: IsAcidic st => st -> (AcidState st)
+ Data.Acid.Memory.Pure: openAcidState :: IsAcidic st => st -> AcidState st
Files
- acid-state.cabal +8/−6
- examples/HelloDatabase.hs +1/−1
- examples/HelloWorld.hs +1/−1
- examples/HelloWorldNoTH.hs +3/−3
- examples/KeyValue.hs +7/−4
- examples/KeyValueNoTH.hs +3/−3
- examples/Proxy.hs +80/−0
- examples/QuickCheck.hs +6/−0
- examples/RemoteClient.hs +48/−0
- examples/RemoteCommon.hs +36/−0
- examples/RemoteServer.hs +21/−0
- examples/SlowCheckpoint.hs +2/−2
- examples/StressTest.hs +7/−10
- examples/StressTestNoTH.hs +6/−8
- examples/errors/ChangeState.hs +2/−2
- examples/errors/Exceptions.hs +3/−1
- examples/errors/RemoveEvent.hs +2/−2
- src/Data/Acid.hs +4/−6
- src/Data/Acid/Abstract.hs +102/−0
- src/Data/Acid/Advanced.hs +24/−0
- src/Data/Acid/Common.hs +9/−2
- src/Data/Acid/Core.hs +3/−4
- src/Data/Acid/Local.hs +67/−65
- src/Data/Acid/Log.hs +5/−10
- src/Data/Acid/Memory.hs +51/−63
- src/Data/Acid/Memory/Pure.hs +3/−4
- src/Data/Acid/Remote.hs +186/−0
- src/Data/Acid/TemplateHaskell.hs +127/−8
acid-state.cabal view
@@ -7,7 +7,7 @@ -- The package version. See the Haskell package versioning policy -- (http://www.haskell.org/haskellwiki/Package_versioning_policy) for -- standards guiding when and how versions should be incremented.-Version: 0.5.2+Version: 0.6.0 -- A short (one-line) description of the package. Synopsis: Add ACID guarantees to any serializable Haskell data structure.@@ -45,18 +45,20 @@ Library -- Modules exported by the library.- Exposed-Modules: Data.Acid, Data.Acid.Core,+ Exposed-Modules: Data.Acid, Data.Acid.Local, Data.Acid.Memory,- Data.Acid.Memory.Pure+ Data.Acid.Memory.Pure, Data.Acid.Remote,+ Data.Acid.Advanced -- Modules not exported by this package. Other-modules: Data.Acid.Log, Data.Acid.Archive, Data.Acid.CRC, Paths_acid_state,- Data.Acid.TemplateHaskell, Data.Acid.Common, FileIO+ Data.Acid.TemplateHaskell, Data.Acid.Common, FileIO,+ Data.Acid.Abstract, Data.Acid.Core -- Packages needed in order to build this package.- Build-depends: base >= 4 && < 5, cereal >= 0.3.2.0, safecopy == 0.5.* , bytestring, stm,- filepath, directory, mtl, array, containers, template-haskell+ Build-depends: base >= 4 && < 5, cereal >= 0.3.2.0, safecopy >= 0.6, bytestring, stm,+ filepath, directory, mtl, array, containers, template-haskell, network if os(windows) Build-depends: Win32
examples/HelloDatabase.hs view
@@ -31,7 +31,7 @@ main :: IO () main = do args <- getArgs- database <- openAcidStateFrom "myDatabase/" (Database ["Welcome to the acid-state database."])+ database <- openLocalStateFrom "myDatabase/" (Database ["Welcome to the acid-state database."]) if null args then do messages <- query database (ViewMessages 10) putStrLn "Last 10 messages:"
examples/HelloWorld.hs view
@@ -35,7 +35,7 @@ -- This is how AcidState is used: main :: IO ()-main = do acid <- openAcidState (HelloWorldState "Hello world")+main = do acid <- openLocalState (HelloWorldState "Hello world") args <- getArgs if null args then do string <- query acid QueryState
examples/HelloWorldNoTH.hs view
@@ -1,8 +1,8 @@ {-# LANGUAGE DeriveDataTypeable, TypeFamilies, StandaloneDeriving #-} module Main (main) where -import Data.Acid.Core-import Data.Acid.Local+import Data.Acid+import Data.Acid.Advanced import Control.Monad.State import Control.Monad.Reader@@ -37,7 +37,7 @@ -- This is how AcidState is used: main :: IO ()-main = do acid <- openAcidState (HelloWorldState "Hello world")+main = do acid <- openLocalState (HelloWorldState "Hello world") args <- getArgs if null args then do string <- query acid QueryState
examples/KeyValue.hs view
@@ -2,12 +2,15 @@ module Main (main) where import Data.Acid+import Data.Acid.Remote import Control.Monad.State import Control.Monad.Reader import Control.Applicative import System.Environment import System.IO+import System.Exit+import Network import Data.SafeCopy import Data.Typeable@@ -44,8 +47,8 @@ -- This is how AcidState is used: main :: IO ()-main = do acid <- openAcidState (KeyValue Map.empty)- args <- getArgs+main = do args <- getArgs+ acid <- openLocalState (KeyValue Map.empty) case args of [key] -> do mbKey <- query acid (LookupKey key)@@ -56,6 +59,6 @@ -> do update acid (InsertKey key val) putStrLn "Done." _ -> do putStrLn "Usage:"- putStrLn " key Lookup the value of 'key'."- putStrLn " key value Set the value of 'key' to 'value'."+ putStrLn " key Lookup the value of 'key'."+ putStrLn " key value Set the value of 'key' to 'value'." closeAcidState acid
examples/KeyValueNoTH.hs view
@@ -1,8 +1,8 @@ {-# LANGUAGE DeriveDataTypeable, TypeFamilies, StandaloneDeriving #-} module Main (main) where -import Data.Acid.Core-import Data.Acid.Local+import Data.Acid+import Data.Acid.Advanced import qualified Control.Monad.State as State import Control.Monad.Reader@@ -45,7 +45,7 @@ -- This is how AcidState is used: main :: IO ()-main = do acid <- openAcidState (KeyValue Map.empty)+main = do acid <- openLocalState (KeyValue Map.empty) args <- getArgs case args of [key]
+ examples/Proxy.hs view
@@ -0,0 +1,80 @@+{-# LANGUAGE TemplateHaskell, DeriveDataTypeable, TypeFamilies #-}+module Main (main) where++import Data.Acid+import Data.Acid.Remote+import Data.Acid.Advanced ( scheduleUpdate )++import Control.Monad.State+import Control.Monad.Reader+import System.Environment+import System.IO+import Network+import Data.SafeCopy++import Data.Typeable++------------------------------------------------------+-- The Haskell structure that we want to encapsulate++data ProxyStressState = StressState !Int+ deriving (Typeable)++$(deriveSafeCopy 0 'base ''ProxyStressState)++------------------------------------------------------+-- The transaction we will execute over the state.++pokeState :: Update ProxyStressState ()+pokeState = do StressState i <- get+ put (StressState (i+1))++queryState :: Query ProxyStressState Int+queryState = do StressState i <- ask+ return i++clearState :: Update ProxyStressState ()+clearState = put $ StressState 0++$(makeAcidic ''ProxyStressState ['pokeState, 'queryState, 'clearState])++openLocal :: IO (AcidState ProxyStressState)+openLocal = openLocalState (StressState 0)++openRemote :: String -> IO (AcidState ProxyStressState)+openRemote socket = openRemoteState "localhost" (UnixSocket socket)++main :: IO ()+main = do args <- getArgs+ case args of+ ["server", socket]+ -> do acid <- openLocal+ acidServer acid (UnixSocket socket)+ ["proxy", from, to] + -> do acid <- openRemote from+ acidServer acid (UnixSocket to)+ ["query", socket]+ -> do acid <- openRemote socket+ n <- query acid QueryState+ putStrLn $ "State value: " ++ show n+ ["poke", socket]+ -> do acid <- openRemote socket+ putStr "Issuing 100k transactions... "+ hFlush stdout+ replicateM_ (100000-1) (scheduleUpdate acid PokeState)+ update acid PokeState+ putStrLn "Done"+ ["clear", socket]+ -> do acid <- openRemote socket+ update acid ClearState+ createCheckpoint acid+ ["checkpoint", socket]+ -> do acid <- openRemote socket+ createCheckpoint acid+ _ -> do putStrLn $ "Commands:"+ putStrLn $ " server socket Start a new server instance."+ putStrLn $ " proxy from to Pipe events between 'from' and 'to'."+ putStrLn $ " query socket Prints out the current state."+ putStrLn $ " poke socket Spawn 100k transactions."+ putStrLn $ " clear socket Reset the state and write a checkpoint."+ putStrLn $ " checkpoint socket Create a new checkpoint."
+ examples/QuickCheck.hs view
@@ -0,0 +1,6 @@+module Main (main) where++import Test.QuickCheck++import Data.Acid+
+ examples/RemoteClient.hs view
@@ -0,0 +1,48 @@+{-# LANGUAGE DeriveDataTypeable, TypeFamilies, TemplateHaskell #-}+module Main (main) where++import Data.Acid+import Data.Acid.Advanced+import Data.Acid.Remote++import Control.Monad.State+import Control.Monad.Reader+import System.Environment+import System.IO+import Data.SafeCopy+import Data.Typeable+import Network++import RemoteCommon++------------------------------------------------------+-- This is how AcidState is used:++open :: IO (AcidState StressState)+open = openRemoteState "localhost" (PortNumber 8080)++main :: IO ()+main = do args <- getArgs+ case args of+ ["checkpoint"]+ -> do acid <- open + createCheckpoint acid+ ["query"]+ -> do acid <- open+ n <- query acid QueryState+ putStrLn $ "State value: " ++ show n+ ["poke"]+ -> do acid <- open+ putStr "Issuing 100k transactions... "+ hFlush stdout+ replicateM_ (100000-1) (scheduleUpdate acid PokeState)+ update acid PokeState+ putStrLn "Done"+ ["clear"]+ -> do acid <- open+ update acid ClearState+ createCheckpoint acid+ _ -> do putStrLn $ "Commands:"+ putStrLn $ " query Prints out the current state."+ putStrLn $ " poke Spawn 100k transactions."+ putStrLn $ " checkpoint Create a new checkpoint."
+ examples/RemoteCommon.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE TemplateHaskell, DeriveDataTypeable, TypeFamilies #-}+module RemoteCommon where++import Data.Acid++import Control.Monad.State+import Control.Monad.Reader+import System.Environment+import System.IO+import Data.SafeCopy++import Data.Typeable++------------------------------------------------------+-- The Haskell structure that we want to encapsulate++data StressState = StressState !Int+ deriving (Typeable)++$(deriveSafeCopy 0 'base ''StressState)++------------------------------------------------------+-- The transaction we will execute over the state.++pokeState :: Update StressState ()+pokeState = do StressState i <- get+ put (StressState (i+1))++queryState :: Query StressState Int+queryState = do StressState i <- ask+ return i++clearState :: Update StressState ()+clearState = put $ StressState 0++$(makeAcidic ''StressState ['pokeState, 'queryState, 'clearState])
+ examples/RemoteServer.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE DeriveDataTypeable, TypeFamilies, TemplateHaskell #-}+module Main (main) where++import Data.Acid+import Data.Acid.Remote (acidServer)++import Control.Exception (bracket)+import Data.Typeable++import Network++import RemoteCommon++-- open a server on port 8080++main :: IO ()+main =+ bracket+ (openLocalState $ StressState 0)+ closeAcidState+ (\s -> acidServer s (PortNumber 8080))
examples/SlowCheckpoint.hs view
@@ -40,11 +40,11 @@ -- This is how AcidState is used: main :: IO ()-main = do acid <- openAcidStateFrom "state/SlowCheckpoint" (SlowCheckpoint 0 0)+main = do acid <- openLocalStateFrom "state/SlowCheckpoint" (SlowCheckpoint 0 0) putStrLn "This example illustrates that the state is still accessible while" putStrLn "a checkpoint is being serialized. This is an important property when" putStrLn "the size of a checkpoint reaches several hundred megabytes."- putStrLn "If you don't see any ticks while the creating is being created, something"+ putStrLn "If you don't see any ticks while the checkpoint is being created, something" putStrLn "has gone awry." putStrLn "" doTick acid
examples/StressTest.hs view
@@ -1,8 +1,8 @@ {-# LANGUAGE DeriveDataTypeable, TypeFamilies, TemplateHaskell #-} module Main (main) where -import Data.Acid (makeAcidic)-import Data.Acid.Local+import Data.Acid+import Data.Acid.Advanced (scheduleUpdate) import Control.Monad.State import Control.Monad.Reader@@ -41,24 +41,21 @@ main :: IO () main = do args <- getArgs+ acid <- openLocalState (StressState 0) case args of ["checkpoint"]- -> do acid <- openAcidState (StressState 0)- createCheckpoint acid+ -> createCheckpoint acid ["query"]- -> do acid <- openAcidState (StressState 0)- n <- query acid QueryState+ -> do n <- query acid QueryState putStrLn $ "State value: " ++ show n ["poke"]- -> do acid <- openAcidState (StressState 0)- putStr "Issuing 100k transactions... "+ -> do putStr "Issuing 100k transactions... " hFlush stdout replicateM_ (100000-1) (scheduleUpdate acid PokeState) update acid PokeState putStrLn "Done" ["clear"]- -> do acid <- openAcidState (StressState 0)- update acid ClearState+ -> do update acid ClearState createCheckpoint acid _ -> do putStrLn $ "Commands:" putStrLn $ " query Prints out the current state."
examples/StressTestNoTH.hs view
@@ -1,8 +1,8 @@ {-# LANGUAGE DeriveDataTypeable, TypeFamilies, StandaloneDeriving #-} module Main (main) where -import Data.Acid.Core-import Data.Acid.Local+import Data.Acid+import Data.Acid.Advanced import Control.Monad.State import Control.Monad.Reader@@ -39,17 +39,15 @@ main :: IO () main = do args <- getArgs+ acid <- openLocalState (StressState 0) case args of ["checkpoint"]- -> do acid <- openAcidState (StressState 0)- createCheckpoint acid+ -> createCheckpoint acid ["query"]- -> do acid <- openAcidState (StressState 0)- n <- query acid QueryState+ -> do n <- query acid QueryState putStrLn $ "State value: " ++ show n ["poke"]- -> do acid <- openAcidState (StressState 0)- putStr "Issuing 100k transactions... "+ -> do putStr "Issuing 100k transactions... " hFlush stdout replicateM_ (100000-1) (scheduleUpdate acid PokeState) update acid PokeState
examples/errors/ChangeState.hs view
@@ -41,10 +41,10 @@ putStrLn "without telling AcidState how to migrate from the old version to the new." putStrLn "Hopefully this program will fail with a readable error message." putStrLn ""- firstAcid <- openAcidStateFrom "state/ChangeState" (FirstState "first state")+ firstAcid <- openLocalStateFrom "state/ChangeState" (FirstState "first state") createCheckpoint firstAcid closeAcidState firstAcid - secondAcid <- openAcidStateFrom "state/ChangeState" (SecondState (Text.pack "This initial value shouldn't be used"))+ secondAcid <- openLocalStateFrom "state/ChangeState" (SecondState (Text.pack "This initial value shouldn't be used")) closeAcidState secondAcid putStrLn "If you see this message then something has gone wrong!"
examples/errors/Exceptions.hs view
@@ -2,6 +2,7 @@ module Main (main) where import Data.Acid+import Data.Acid.Local ( createCheckpointAndClose ) import Control.Monad.State import System.Environment@@ -43,7 +44,7 @@ -- This is how AcidState is used: main :: IO ()-main = do acid <- openAcidStateFrom "state/Exceptions" (MyState 0)+main = do acid <- openLocalStateFrom "state/Exceptions" (MyState 0) args <- getArgs case args of ["1"] -> update acid (undefined :: FailEvent)@@ -51,6 +52,7 @@ ["3"] -> update acid ErrorEvent ["4"] -> update acid StateError _ -> do putStrLn "Call with '1', '2', '3' or '4' to test error scenarios."+ putStrLn "If the tick doesn't get stuck, everything is fine." n <- update acid Tick putStrLn $ "Tick: " ++ show n `catch` \e -> do putStrLn $ "Caught exception: " ++ show (e:: SomeException)
examples/errors/RemoveEvent.hs view
@@ -41,10 +41,10 @@ putStrLn "that is required to replay the journal." putStrLn "Hopefully this program will fail with a readable error message." putStrLn ""- firstAcid <- openAcidStateFrom "state/RemoveEvent" FirstState+ firstAcid <- openLocalStateFrom "state/RemoveEvent" FirstState update firstAcid FirstEvent closeAcidState firstAcid - secondAcid <- openAcidStateFrom "state/RemoveEvent" SecondState+ secondAcid <- openLocalStateFrom "state/RemoveEvent" SecondState closeAcidState secondAcid putStrLn "If you see this message then something has gone wrong!"
src/Data/Acid.hs view
@@ -15,16 +15,12 @@ module Data.Acid ( AcidState- , openAcidState- , openAcidStateFrom+ , openLocalState+ , openLocalStateFrom , closeAcidState , createCheckpoint- , createCheckpointAndClose- , createArchive , update , query- , update'- , query' , EventResult , EventState , UpdateEvent@@ -37,4 +33,6 @@ ) where import Data.Acid.Local+import Data.Acid.Common+import Data.Acid.Abstract import Data.Acid.TemplateHaskell
+ src/Data/Acid/Abstract.hs view
@@ -0,0 +1,102 @@+{-# LANGUAGE RankNTypes, TypeFamilies, GADTs #-}+module Data.Acid.Abstract+ ( AcidState(..)+ , scheduleUpdate+ , update+ , update'+ , query+ , query'+ , mkAnyState+ , downcast+ ) where++import Data.Acid.Common+import Data.Acid.Core++import Control.Concurrent ( MVar, takeMVar )+import Data.ByteString.Lazy ( ByteString )+import Control.Monad.Trans ( MonadIO(liftIO) )+import Data.Typeable ( Typeable1, gcast1, typeOf1 )++data AnyState st where+ AnyState :: Typeable1 sub_st => sub_st st -> AnyState st++-- Haddock doesn't get the types right on its own.+{-| State container offering full ACID (Atomicity, Consistency, Isolation and Durability)+ guarantees.++ [@Atomicity@] State changes are all-or-nothing. This is what you'd expect of any state+ variable in Haskell and AcidState doesn't change that.++ [@Consistency@] No event or set of events will break your data invariants.++ [@Isolation@] Transactions cannot interfere with each other even when issued in parallel.++ [@Durability@] Successful transaction are guaranteed to survive unexpected system shutdowns+ (both those caused by hardware and software).+-}+data AcidState st+ = AcidState { + _scheduleUpdate :: forall event. (UpdateEvent event, EventState event ~ st) => event -> IO (MVar (EventResult event))+ , scheduleColdUpdate :: Tagged ByteString -> IO (MVar ByteString)+ , _query :: (QueryEvent event, EventState event ~ st) => event -> IO (EventResult event)+ , queryCold :: Tagged ByteString -> IO ByteString+ , +-- | Take a snapshot of the state and save it to disk. Creating checkpoints+-- makes it faster to resume AcidStates and you're free to create them as+-- often or seldom as fits your needs. Transactions can run concurrently+-- with this call.+--+-- This call will not return until the operation has succeeded.+ createCheckpoint :: IO ()+ , +-- | Close an AcidState and associated resources.+-- Any subsequent usage of the AcidState will throw an exception.+ closeAcidState :: IO ()+ , acidSubState :: AnyState st+ }++-- | Issue an Update event and return immediately. The event is not durable+-- before the MVar has been filled but the order of events is honored.+-- The behavior in case of exceptions is exactly the same as for 'update'.+--+-- If EventA is scheduled before EventB, EventA /will/ be executed before EventB:+--+-- @+--do scheduleUpdate acid EventA+-- scheduleUpdate acid EventB+-- @+scheduleUpdate :: UpdateEvent event => AcidState (EventState event) -> event -> IO (MVar (EventResult event))+scheduleUpdate = _scheduleUpdate -- Redirection to make Haddock happy.++-- | Issue an Update event and wait for its result. Once this call returns, you are+-- guaranteed that the changes to the state are durable. Events may be issued in+-- parallel.+--+-- It's a run-time error to issue events that aren't supported by the AcidState.+update :: UpdateEvent event => AcidState (EventState event) -> event -> IO (EventResult event)+update acidState event = takeMVar =<< scheduleUpdate acidState event++-- | Same as 'update' but lifted into any monad capable of doing IO.+update' :: (UpdateEvent event, MonadIO m) => AcidState (EventState event) -> event -> m (EventResult event)+update' acidState event = liftIO (update acidState event)++-- | Issue a Query event and wait for its result. Events may be issued in parallel.+query :: QueryEvent event => AcidState (EventState event) -> event -> IO (EventResult event)+query = _query -- Redirection to make Haddock happy.++-- | Same as 'query' but lifted into any monad capable of doing IO.+query' :: (QueryEvent event, MonadIO m) => AcidState (EventState event) -> event -> m (EventResult event)+query' acidState event = liftIO (query acidState event)++mkAnyState :: Typeable1 sub_st => sub_st st -> AnyState st+mkAnyState = AnyState++downcast :: Typeable1 sub => AcidState st -> sub st+downcast AcidState{acidSubState = AnyState sub}+ = case gcast1 (Just sub) of+ Just (Just typed_sub_struct) -> typed_sub_struct `asTypeOf` result+ Nothing -> error $ "Data.Acid: Invalid subtype cast: " ++ show tag ++ " -> " ++ show (typeOf1 result)+ where result = undefined+ tag = show (typeOf1 sub)+
+ src/Data/Acid/Advanced.hs view
@@ -0,0 +1,24 @@+-----------------------------------------------------------------------------+{- |+ Module : Data.Acid.Advanced+ Copyright : PublicDomain++ Maintainer : lemmih@gmail.com+ Portability : non-portable (uses GHC extensions)++ Home of the more specialized functions.++-}+module Data.Acid.Advanced+ ( scheduleUpdate+ , update'+ , query'+ , Method(..)+ , IsAcidic(..)+ , Event(..)+ ) where++import Data.Acid.Abstract+import Data.Acid.Core+import Data.Acid.Common+
src/Data/Acid/Common.hs view
@@ -1,5 +1,3 @@-{- LANGUAGE GADTs, OverloadedStrings, DeriveDataTypeable, TypeFamilies,- FlexibleContexts, BangPatterns, CPP -} {-# LANGUAGE CPP, GeneralizedNewtypeDeriving, GADTs #-} ----------------------------------------------------------------------------- -- |@@ -18,8 +16,16 @@ import Control.Monad.State import Control.Monad.Reader import Data.SafeCopy+import Data.Serialize (runGet, runGetLazy) import Control.Applicative+import qualified Data.ByteString as Strict +-- Silly fix for bug in cereal-0.3.3.0's version of runGetLazy.+runGetLazyFix getter inp + = case runGet getter Strict.empty of+ Left msg -> runGetLazy getter inp+ Right val -> Right val+ class (SafeCopy st) => IsAcidic st where acidEvents :: [Event st] -- ^ List of events capable of updating or querying the state.@@ -76,3 +82,4 @@ worker (QueryEvent fn) = Method (\ev -> do st <- get return (runReader (unQuery $ fn ev) st) )+
src/Data/Acid/Core.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE GADTs, OverloadedStrings, DeriveDataTypeable, TypeFamilies,+{-# LANGUAGE GADTs, DeriveDataTypeable, TypeFamilies, FlexibleContexts, BangPatterns #-} ----------------------------------------------------------------------------- -- |@@ -112,8 +112,7 @@ -- | Access the state component. withCoreState :: Core st -> (st -> IO a) -> IO a-withCoreState core action- = withMVar (coreState core) action+withCoreState core = withMVar (coreState core) -- | Execute a method as given by a type identifier and an encoded string. -- The exact format of the encoded string depends on the type identifier.@@ -165,7 +164,7 @@ -- trouble. Luckly, it would take deliberate malevolence for that to happen. unsafeCoerce methodHandler method --- | Method tags must be unique and are most commenly generated automatically.+-- | Method tags must be unique and are most commonly generated automatically. type Tag = Lazy.ByteString type Tagged a = (Tag, a)
src/Data/Acid/Local.hs view
@@ -1,5 +1,4 @@-{-# LANGUAGE GADTs, OverloadedStrings, DeriveDataTypeable, TypeFamilies,- GeneralizedNewtypeDeriving, BangPatterns, CPP #-}+{-# LANGUAGE DeriveDataTypeable, BangPatterns #-} ----------------------------------------------------------------------------- -- | -- Module : Data.Acid.Local@@ -14,37 +13,20 @@ -- module Data.Acid.Local- ( IsAcidic(..)- , AcidState- , Event(..)- , EventResult- , EventState- , UpdateEvent- , QueryEvent- , Update- , Query- , openAcidState- , openAcidStateFrom- , closeAcidState- , createCheckpoint- , createCheckpointAndClose+ ( openLocalState+ , openLocalStateFrom , createArchive- , update- , scheduleUpdate- , query- , update'- , query'- , runQuery- ) where+ , createCheckpointAndClose+ ) where import Data.Acid.Log as Log import Data.Acid.Core import Data.Acid.Common+import Data.Acid.Abstract import Control.Concurrent ( newEmptyMVar, putMVar, takeMVar, MVar ) --import Control.Exception ( evaluate ) import Control.Monad.State ( runState )-import Control.Monad.Trans ( MonadIO(liftIO) ) import Control.Applicative ( (<$>), (<*>) ) import Data.ByteString.Lazy ( ByteString ) --import qualified Data.ByteString.Lazy as Lazy ( length )@@ -70,22 +52,13 @@ [@Durability@] Successful transaction are guaranteed to survive system failure (both hardware and software). -}-data AcidState st- = AcidState { localCore :: Core st- , localEvents :: FileLog (Tagged ByteString)- , localCheckpoints :: FileLog Checkpoint- }+data LocalState st+ = LocalState { localCore :: Core st+ , localEvents :: FileLog (Tagged ByteString)+ , localCheckpoints :: FileLog Checkpoint+ } deriving (Typeable) --- | Issue an Update event and wait for its result. Once this call returns, you are--- guaranteed that the changes to the state are durable. Events may be issued in--- parallel.------ It's a run-time error to issue events that aren't supported by the AcidState.-update :: UpdateEvent event => AcidState (EventState event) -> event -> IO (EventResult event)-update acidState event- = takeMVar =<< scheduleUpdate acidState event- -- | Issue an Update event and return immediately. The event is not durable -- before the MVar has been filled but the order of events is honored. -- The behavior in case of exceptions is exactly the same as for 'update'.@@ -96,8 +69,8 @@ --do scheduleUpdate acid EventA -- scheduleUpdate acid EventB -- @-scheduleUpdate :: UpdateEvent event => AcidState (EventState event) -> event -> IO (MVar (EventResult event))-scheduleUpdate acidState event+scheduleLocalUpdate :: UpdateEvent event => LocalState (EventState event) -> event -> IO (MVar (EventResult event))+scheduleLocalUpdate acidState event = do mvar <- newEmptyMVar let encoded = runPutLazy (safePut event) --evaluate (Lazy.length encoded) -- It would be best to encode the event before we lock the core@@ -111,14 +84,21 @@ return mvar where hotMethod = lookupHotMethod (coreMethods (localCore acidState)) event --- | Same as 'update' but lifted into any monad capable of doing IO.-update' :: (UpdateEvent event, MonadIO m) => AcidState (EventState event) -> event -> m (EventResult event)-update' acidState event- = liftIO (update acidState event)+scheduleLocalColdUpdate :: LocalState st -> Tagged ByteString -> IO (MVar ByteString)+scheduleLocalColdUpdate acidState event+ = do mvar <- newEmptyMVar+ modifyCoreState_ (localCore acidState) $ \st ->+ do let !(result, !st') = runState coldMethod st+ -- Schedule the log entry. Very important that it happens when 'localCore' is locked+ -- to ensure that events are logged in the same order that they are executed.+ pushEntry (localEvents acidState) event $ putMVar mvar result+ return st'+ return mvar+ where coldMethod = lookupColdMethod (localCore acidState) event -- | Issue a Query event and wait for its result. Events may be issued in parallel.-query :: QueryEvent event => AcidState (EventState event) -> event -> IO (EventResult event)-query acidState event+localQuery :: QueryEvent event => LocalState (EventState event) -> event -> IO (EventResult event)+localQuery acidState event = do mvar <- newEmptyMVar withCoreState (localCore acidState) $ \st -> do let (result, _st) = runState hotMethod st@@ -129,10 +109,18 @@ takeMVar mvar where hotMethod = lookupHotMethod (coreMethods (localCore acidState)) event --- | Same as 'query' but lifted into any monad capable of doing IO.-query' :: (QueryEvent event, MonadIO m) => AcidState (EventState event) -> event -> m (EventResult event)-query' acidState event- = liftIO (query acidState event)+-- Whoa, a buttload of refactoring is needed here. 2011-11-02+localQueryCold :: LocalState st -> Tagged ByteString -> IO ByteString+localQueryCold acidState event+ = do mvar <- newEmptyMVar+ withCoreState (localCore acidState) $ \st ->+ do let (result, _st) = runState coldMethod st+ -- Make sure that we do not return the result before the event log has+ -- been flushed to disk.+ pushAction (localEvents acidState) $+ putMVar mvar result+ takeMVar mvar+ where coldMethod = lookupColdMethod (localCore acidState) event -- | Take a snapshot of the state and save it to disk. Creating checkpoints -- makes it faster to resume AcidStates and you're free to create them as@@ -140,8 +128,8 @@ -- with this call. -- -- This call will not return until the operation has succeeded.-createCheckpoint :: SafeCopy st => AcidState st -> IO ()-createCheckpoint acidState+createLocalCheckpoint :: SafeCopy st => LocalState st -> IO ()+createLocalCheckpoint acidState = do mvar <- newEmptyMVar withCoreState (localCore acidState) $ \st -> do eventId <- askCurrentEntryId (localEvents acidState)@@ -154,7 +142,7 @@ -- action. This is useful when you want to make sure that no events -- are saved to disk after a checkpoint. createCheckpointAndClose :: SafeCopy st => AcidState st -> IO ()-createCheckpointAndClose acidState+createCheckpointAndClose abstract_state = do mvar <- newEmptyMVar closeCore' (localCore acidState) $ \st -> do eventId <- askCurrentEntryId (localEvents acidState)@@ -163,6 +151,7 @@ takeMVar mvar closeFileLog (localEvents acidState) closeFileLog (localCheckpoints acidState)+ where acidState = downcast abstract_state data Checkpoint = Checkpoint EntryId ByteString@@ -179,24 +168,24 @@ -- | Create an AcidState given an initial value. -- -- This will create or resume a log found in the \"state\/[typeOf state]\/\" directory.-openAcidState :: (Typeable st, IsAcidic st)+openLocalState :: (Typeable st, IsAcidic st) => st -- ^ Initial state value. This value is only used if no checkpoint is -- found. -> IO (AcidState st)-openAcidState initialState- = openAcidStateFrom ("state" </> show (typeOf initialState)) initialState+openLocalState initialState+ = openLocalStateFrom ("state" </> show (typeOf initialState)) initialState -- | Create an AcidState given a log directory and an initial value. -- -- This will create or resume a log found in @directory@. -- Running two AcidState's from the same directory is an error -- but will not result in dataloss.-openAcidStateFrom :: (IsAcidic st)+openLocalStateFrom :: (IsAcidic st) => FilePath -- ^ Location of the checkpoint and transaction files. -> st -- ^ Initial state value. This value is only used if no checkpoint is -- found. -> IO (AcidState st)-openAcidStateFrom directory initialState+openLocalStateFrom directory initialState = do core <- mkCore (eventsToMethods acidEvents) initialState let eventsLogKey = LogKey { logDirectory = directory , logPrefix = "events" }@@ -216,18 +205,18 @@ events <- readEntriesFrom eventsLog n mapM_ (runColdMethod core) events checkpointsLog <- openFileLog checkpointsLogKey- return AcidState { localCore = core- , localEvents = eventsLog- , localCheckpoints = checkpointsLog- }+ return $ toAcidState LocalState { localCore = core+ , localEvents = eventsLog+ , localCheckpoints = checkpointsLog+ } checkpointRestoreError msg = error $ "Could not parse saved checkpoint due to the following error: " ++ msg -- | Close an AcidState and associated logs. -- Any subsequent usage of the AcidState will throw an exception.-closeAcidState :: AcidState st -> IO ()-closeAcidState acidState+closeLocalState :: LocalState st -> IO ()+closeLocalState acidState = do closeCore (localCore acidState) closeFileLog (localEvents acidState) closeFileLog (localCheckpoints acidState)@@ -240,7 +229,7 @@ -- -- This method is idempotent and does not block the normal operation of the AcidState. createArchive :: AcidState st -> IO ()-createArchive state+createArchive abstract_state = do -- We need to look at the last checkpoint saved to disk. Since checkpoints can be written -- in parallel with this call, we can't guarantee that the checkpoint we get really is the -- last one but that's alright.@@ -258,3 +247,16 @@ -- In the same style as above, we archive all log files that came before the log file -- which contains our checkpoint. archiveFileLog (localCheckpoints state) durableCheckpointId+ where state = downcast abstract_state++toAcidState :: IsAcidic st => LocalState st -> AcidState st+toAcidState local+ = AcidState { _scheduleUpdate = scheduleLocalUpdate local+ , scheduleColdUpdate = scheduleLocalColdUpdate local+ , _query = localQuery local+ , queryCold = localQueryCold local+ , createCheckpoint = createLocalCheckpoint local+ , closeAcidState = closeLocalState local+ , acidSubState = mkAnyState local+ }+
src/Data/Acid/Log.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE ForeignFunctionInterface #-} -- A log is a stack of entries that supports efficient pushing of -- new entries and fetching of old. It can be considered an -- extendible array of entries.@@ -58,8 +57,7 @@ } formatLogFile :: String -> EntryId -> String-formatLogFile tag n- = printf "%s-%010d.log" tag n+formatLogFile = printf "%s-%010d.log" findLogFiles :: LogKey object -> IO [(EntryId, FilePath)] findLogFiles identifier@@ -130,13 +128,11 @@ writeToDisk handle xs = do mapM_ worker xs flush handle- return () where worker bs = do let len = Strict.length bs count <- Strict.unsafeUseAsCString bs $ \ptr -> write handle (castPtr ptr) (fromIntegral len)- if fromIntegral count < len- then worker (Strict.drop (fromIntegral count) bs)- else return ()+ when (fromIntegral count < len) $+ worker (Strict.drop (fromIntegral count) bs) closeFileLog :: FileLog object -> IO ()@@ -170,7 +166,6 @@ firstEntryId = case relevant of [] -> 0 ( logFile : _logFiles) -> rangeStart logFile- archive <- liftM Lazy.concat $ mapM (Lazy.readFile . snd) relevant let entries = entriesToList $ readEntries archive return $ map decode'@@ -198,9 +193,9 @@ | otherwise -- If 'left' starts after our maxEntryId then we're done. = [] ltMinEntryId = case minEntryIdMb of Nothing -> const False- Just minEntryId -> \entryId -> entryId < minEntryId+ Just minEntryId -> (< minEntryId) ltMaxEntryId = case maxEntryIdMb of Nothing -> const True- Just maxEntryId -> \entryId -> entryId < maxEntryId+ Just maxEntryId -> (< maxEntryId) rangeStart (firstEntryId, _path) = firstEntryId -- Move all log files that do not contain entries equal or higher than the given entryId
src/Data/Acid/Memory.hs view
@@ -1,5 +1,4 @@-{-# LANGUAGE GADTs, OverloadedStrings, DeriveDataTypeable, TypeFamilies,- GeneralizedNewtypeDeriving, BangPatterns, CPP #-}+{-# LANGUAGE DeriveDataTypeable, BangPatterns #-} ----------------------------------------------------------------------------- -- | -- Module : Data.Acid.Memory@@ -12,34 +11,17 @@ -- module Data.Acid.Memory- ( IsAcidic(..)- , AcidState- , Event(..)- , EventResult- , EventState- , UpdateEvent- , QueryEvent- , Update- , Query- , openAcidState- , closeAcidState- , createCheckpoint- , createCheckpointAndClose- , update- , scheduleUpdate- , query- , update'- , query'- , runQuery+ ( openMemoryState ) where import Data.Acid.Core import Data.Acid.Common+import Data.Acid.Abstract import Control.Concurrent ( newEmptyMVar, putMVar, takeMVar, MVar ) import Control.Monad.State ( runState )-import Control.Monad.Trans ( MonadIO(liftIO) )-+import Data.ByteString.Lazy ( ByteString )+import Data.Typeable ( Typeable ) import Data.SafeCopy ( SafeCopy(..) ) @@ -57,19 +39,19 @@ [@Durability@] Successful transaction are guaranteed to survive system failure (both hardware and software). -}-data AcidState st- = AcidState { localCore :: Core st- }+data MemoryState st+ = MemoryState { localCore :: Core st+ } deriving (Typeable) --- | Issue an Update event and wait for its result. Once this call returns, you are--- guaranteed that the changes to the state are durable. Events may be issued in--- parallel.------ It's a run-time error to issue events that aren't supported by the AcidState.-update :: UpdateEvent event => AcidState (EventState event) -> event -> IO (EventResult event)-update acidState event- = takeMVar =<< scheduleUpdate acidState event+-- | Create an AcidState given an initial value.+openMemoryState :: (IsAcidic st)+ => st -- ^ Initial state value.+ -> IO (AcidState st)+openMemoryState initialState+ = do core <- mkCore (eventsToMethods acidEvents) initialState+ return $ toAcidState MemoryState { localCore = core } + -- | Issue an Update event and return immediately. The event is not durable -- before the MVar has been filled but the order of events is honored. -- The behavior in case of exceptions is exactly the same as for 'update'.@@ -80,8 +62,8 @@ --do scheduleUpdate acid EventA -- scheduleUpdate acid EventB -- @-scheduleUpdate :: UpdateEvent event => AcidState (EventState event) -> event -> IO (MVar (EventResult event))-scheduleUpdate acidState event+scheduleMemoryUpdate :: UpdateEvent event => MemoryState (EventState event) -> event -> IO (MVar (EventResult event))+scheduleMemoryUpdate acidState event = do mvar <- newEmptyMVar modifyCoreState_ (localCore acidState) $ \st -> do let !(result, !st') = runState hotMethod st@@ -90,14 +72,19 @@ return mvar where hotMethod = lookupHotMethod (coreMethods (localCore acidState)) event --- | Same as 'update' but lifted into any monad capable of doing IO.-update' :: (UpdateEvent event, MonadIO m) => AcidState (EventState event) -> event -> m (EventResult event)-update' acidState event- = liftIO (update acidState event)+scheduleMemoryColdUpdate :: MemoryState st -> Tagged ByteString -> IO (MVar ByteString)+scheduleMemoryColdUpdate acidState event+ = do mvar <- newEmptyMVar+ modifyCoreState_ (localCore acidState) $ \st ->+ do let !(result, !st') = runState coldMethod st+ putMVar mvar result+ return st'+ return mvar+ where coldMethod = lookupColdMethod (localCore acidState) event -- | Issue a Query event and wait for its result. Events may be issued in parallel.-query :: QueryEvent event => AcidState (EventState event) -> event -> IO (EventResult event)-query acidState event+memoryQuery :: QueryEvent event => MemoryState (EventState event) -> event -> IO (EventResult event)+memoryQuery acidState event = do mvar <- newEmptyMVar withCoreState (localCore acidState) $ \st -> do let (result, _st) = runState hotMethod st@@ -105,32 +92,33 @@ takeMVar mvar where hotMethod = lookupHotMethod (coreMethods (localCore acidState)) event --- | Same as 'query' but lifted into any monad capable of doing IO.-query' :: (QueryEvent event, MonadIO m) => AcidState (EventState event) -> event -> m (EventResult event)-query' acidState event- = liftIO (query acidState event)+memoryQueryCold :: MemoryState st -> Tagged ByteString -> IO ByteString+memoryQueryCold acidState event+ = do mvar <- newEmptyMVar+ withCoreState (localCore acidState) $ \st ->+ do let (result, _st) = runState coldMethod st+ putMVar mvar result+ takeMVar mvar+ where coldMethod = lookupColdMethod (localCore acidState) event -- | This is a nop with the memory backend.-createCheckpoint :: SafeCopy st => AcidState st -> IO ()-createCheckpoint acidState+createMemoryCheckpoint :: SafeCopy st => MemoryState st -> IO ()+createMemoryCheckpoint acidState = return () --- | This is an alias for 'closeAcidState' when using the memory backend.-createCheckpointAndClose :: SafeCopy st => AcidState st -> IO ()-createCheckpointAndClose = closeAcidState----- | Create an AcidState given an initial value.-openAcidState :: (IsAcidic st)- => st -- ^ Initial state value. - -> IO (AcidState st)-openAcidState initialState- = do core <- mkCore (eventsToMethods acidEvents) initialState- return AcidState { localCore = core }- -- | Close an AcidState and associated logs. -- Any subsequent usage of the AcidState will throw an exception.-closeAcidState :: AcidState st -> IO ()-closeAcidState acidState+closeMemoryState :: MemoryState st -> IO ()+closeMemoryState acidState = closeCore (localCore acidState) +toAcidState :: IsAcidic st => MemoryState st -> AcidState st+toAcidState memory+ = AcidState { _scheduleUpdate = scheduleMemoryUpdate memory+ , scheduleColdUpdate = scheduleMemoryColdUpdate memory+ , _query = memoryQuery memory+ , queryCold = memoryQueryCold memory+ , createCheckpoint = createMemoryCheckpoint memory+ , closeAcidState = closeMemoryState memory+ , acidSubState = mkAnyState memory+ }
src/Data/Acid/Memory/Pure.hs view
@@ -1,5 +1,4 @@-{-# LANGUAGE GADTs, OverloadedStrings, DeriveDataTypeable, TypeFamilies,- GeneralizedNewtypeDeriving, BangPatterns, CPP #-}+{-# LANGUAGE DeriveDataTypeable, BangPatterns #-} ----------------------------------------------------------------------------- -- | -- Module : Data.Acid.Memory.Pure@@ -78,9 +77,9 @@ where hotMethod = lookupHotMethod (localMethods acidState) event -- | Create an AcidState given an initial value.-openAcidState :: (IsAcidic st)+openAcidState :: IsAcidic st => st -- ^ Initial state value. - -> (AcidState st)+ -> AcidState st openAcidState initialState = AcidState { localMethods = mkMethodMap (eventsToMethods acidEvents) , localState = initialState }
+ src/Data/Acid/Remote.hs view
@@ -0,0 +1,186 @@+{-# LANGUAGE DeriveDataTypeable #-}+-----------------------------------------------------------------------------+{- |+ Module : Data.Acid.Remote+ Copyright : PublicDomain++ Maintainer : lemmih@gmail.com+ Portability : non-portable (uses GHC extensions)++ Network backend.++-}+module Data.Acid.Remote+ ( acidServer+ , openRemoteState+ ) where+++import Data.Acid.Abstract+import Data.Acid.Core+import Data.Acid.Common++import Network+import qualified Data.ByteString as Strict+import qualified Data.ByteString.Lazy as Lazy+import Control.Exception ( throwIO, ErrorCall(..), finally )+import Control.Monad ( forever, liftM, join )+import Control.Concurrent+import Data.IORef ( newIORef, readIORef, writeIORef )+import Data.Serialize+import Data.SafeCopy ( SafeCopy, safeGet, safePut )+import System.IO ( Handle, hFlush, hClose )+import qualified Data.Sequence as Seq+import Data.Typeable ( Typeable )++{- | Accept connections on @port@ and serve requests using the given 'AcidState'.+ This call doesn't return.+ -}+acidServer :: SafeCopy st => AcidState st -> PortID -> IO ()+acidServer acidState port+ = do socket <- listenOn port+ forever $ do (handle, _host, _port) <- accept socket+ forkIO (process acidState handle)+ `finally` sClose socket++data Command = RunQuery (Tagged Lazy.ByteString)+ | RunUpdate (Tagged Lazy.ByteString)+ | CreateCheckpoint++instance Serialize Command where+ put cmd = case cmd of+ RunQuery query -> do putWord8 0; put query+ RunUpdate update -> do putWord8 1; put update+ CreateCheckpoint -> putWord8 2+ get = do tag <- getWord8+ case tag of+ 0 -> liftM RunQuery get+ 1 -> liftM RunUpdate get+ 2 -> return CreateCheckpoint++data Response = Result Lazy.ByteString | Acknowledgement++instance Serialize Response where+ put resp = case resp of+ Result result -> do putWord8 0; put result+ Acknowledgement -> putWord8 1+ get = do tag <- getWord8+ case tag of+ 0 -> liftM Result get+ 1 -> return Acknowledgement++process :: SafeCopy st => AcidState st -> Handle -> IO ()+process acidState handle+ = do chan <- newChan+ forkIO $ forever $ do response <- join (readChan chan)+ Strict.hPut handle (encode response)+ hFlush handle+ worker chan (runGetPartial get Strict.empty)+ where worker chan inp+ = case inp of+ Fail msg -> return () -- error msg+ Partial cont -> do inp <- Strict.hGetSome handle 1024+ worker chan (cont inp)+ Done cmd rest -> do processCommand chan cmd; worker chan (runGetPartial get rest)+ processCommand chan cmd =+ case cmd of+ RunQuery query -> do result <- queryCold acidState query+ writeChan chan (return $ Result result)+ RunUpdate update -> do result <- scheduleColdUpdate acidState update+ writeChan chan (liftM Result $ takeMVar result)+ CreateCheckpoint -> do createCheckpoint acidState+ writeChan chan (return Acknowledgement)+++data RemoteState st = RemoteState (Command -> IO (MVar Response)) (IO ())+ deriving (Typeable)++{- | Connect to a remotely running 'AcidState'. -}+openRemoteState :: IsAcidic st => HostName -> PortID -> IO (AcidState st)+openRemoteState host port+ = do handle <- connectTo host port+ writeLock <- newMVar ()+ -- callbacks are added to the right and read from the left+ callbacks <- newMVar (Seq.empty :: Seq.Seq (Response -> IO ()))+ isClosed <- newIORef False+ let getCallback =+ modifyMVar callbacks $ \s -> return $+ case Seq.viewl s of+ Seq.EmptyL -> noCallback+ (cb Seq.:< s') -> (s', cb)+ noCallback = error "openRemote: Internal error: Missing callback."+ newCallback cb = modifyMVar_ callbacks (\s -> return (s Seq.|> cb))+ + listener inp+ = case inp of+ Fail msg -> error msg+ Partial cont -> do inp <- Strict.hGetSome handle 1024+ listener (cont inp)+ Done resp rest -> do callback <- getCallback+ callback (resp :: Response)+ listener (runGetPartial get rest)+ actor cmd = do readIORef isClosed >>= closedError+ ref <- newEmptyMVar+ withMVar writeLock $ \() -> do+ newCallback (putMVar ref)+ Strict.hPut handle (encode cmd) >> hFlush handle+ return ref++ closedError False = return ()+ closedError True = throwIO $ ErrorCall "The AcidState has been closed"++ shutdown = do writeIORef isClosed True+ hClose handle+ forkIO (listener (runGetPartial get Strict.empty))+ return (toAcidState $ RemoteState actor shutdown)++remoteQuery :: QueryEvent event => RemoteState (EventState event) -> event -> IO (EventResult event)+remoteQuery acidState event+ = do let encoded = runPutLazy (safePut event)+ resp <- remoteQueryCold acidState (methodTag event, encoded)+ return (case runGetLazyFix safeGet resp of+ Left msg -> error msg+ Right result -> result)++remoteQueryCold :: RemoteState st -> Tagged Lazy.ByteString -> IO Lazy.ByteString+remoteQueryCold (RemoteState fn _shutdown) event+ = do Result resp <- takeMVar =<< fn (RunQuery event)+ return resp++scheduleRemoteUpdate :: UpdateEvent event => RemoteState (EventState event) -> event -> IO (MVar (EventResult event))+scheduleRemoteUpdate (RemoteState fn _shutdown) event+ = do let encoded = runPutLazy (safePut event)+ parsed <- newEmptyMVar+ respRef <- fn (RunUpdate (methodTag event, encoded))+ forkIO $ do Result resp <- takeMVar respRef+ putMVar parsed (case runGetLazyFix safeGet resp of+ Left msg -> error msg+ Right result -> result)+ return parsed++scheduleRemoteColdUpdate :: RemoteState st -> Tagged Lazy.ByteString -> IO (MVar Lazy.ByteString)+scheduleRemoteColdUpdate (RemoteState fn _shutdown) event+ = do parsed <- newEmptyMVar+ respRef <- fn (RunUpdate event)+ forkIO $ do Result resp <- takeMVar respRef+ putMVar parsed resp+ return parsed++closeRemoteState :: RemoteState st -> IO ()+closeRemoteState (RemoteState _fn shutdown) = shutdown++createRemoteCheckpoint :: RemoteState st -> IO ()+createRemoteCheckpoint (RemoteState fn _shutdown)+ = do Acknowledgement <- takeMVar =<< fn CreateCheckpoint+ return ()++toAcidState :: IsAcidic st => RemoteState st -> AcidState st+toAcidState remote+ = AcidState { _scheduleUpdate = scheduleRemoteUpdate remote+ , scheduleColdUpdate = scheduleRemoteColdUpdate remote+ , _query = remoteQuery remote+ , queryCold = remoteQueryCold remote+ , createCheckpoint = createRemoteCheckpoint remote+ , closeAcidState = closeRemoteState remote+ , acidSubState = mkAnyState remote+ }
src/Data/Acid/TemplateHaskell.hs view
@@ -5,10 +5,12 @@ ) where import Language.Haskell.TH+import Language.Haskell.TH.Ppr import Data.Acid.Core-import Data.Acid.Local+import Data.Acid.Common +import Data.List ((\\), nub) import Data.SafeCopy import Data.Typeable import Data.Char@@ -78,34 +80,137 @@ _ -> error $ "Events must be functions: " ++ show eventName --instance (SafeCopy key, Typeable key, SafeCopy val, Typeable val) => IsAcidic State where--- acidEvents = [ UpdateEven (\(MyUpdateEvent arg1 arg2 -> myUpdateEvent arg1 arg2) ]+-- acidEvents = [ UpdateEvent (\(MyUpdateEvent arg1 arg2 -> myUpdateEvent arg1 arg2) ] makeIsAcidic eventNames stateName tyvars constructors = do types <- mapM getEventType eventNames+ stateType' <- stateType let preds = [ ''SafeCopy, ''Typeable ] ty = appT (conT ''IsAcidic) stateType handlers = zipWith makeEventHandler eventNames types- instanceD (mkCxtFromTyVars preds tyvars []) ty+ cxtFromEvents = nub $ concat $ zipWith (eventCxts stateType' tyvars) eventNames types+ cxts' <- mkCxtFromTyVars preds tyvars cxtFromEvents+ instanceD (return cxts') ty [ valD (varP 'acidEvents) (normalB (listE handlers)) [] ] where stateType = foldl appT (conT stateName) [ varT var | PlainTV var <- tyvars ] +-- | This function analyses an event function and extracts any+-- additional class contexts which need to be added to the IsAcidic+-- instance.+--+-- For example, if we have:+--+-- > data State a = ...+--+-- > setState :: (Ord a) => a -> UpdateEvent (State a) ()+--+-- Then we need to generate an IsAcidic instance like:+--+-- > instance (SafeCopy a, Typeable a, Ord a) => IsAcidic (State a)+--+-- Note that we can only add constraints for type variables which+-- appear in the State type. If we tried to do this:+--+-- > setState :: (Ord a, Ord b) => a -> b -> UpdateEvent (State a) ()+--+-- We will get an ambigious type variable when trying to create the+-- 'IsAcidic' instance, because there is no way to figure out what+-- type 'b' should be.+-- +-- The tricky part of this code is that we need to unify the type+-- variables.+--+-- Let's say the user writes their code using 'b' instead of 'a':+--+-- > setState :: (Ord b) => b -> UpdateEvent (State b) ()+--+-- In the 'IsAcidic' instance, we are still going to use 'a'. So we+-- need to rename the variables in the context to match.+--+-- The contexts returned by this function will have the variables renamed.+eventCxts :: Type -- ^ State type (used for error messages)+ -> [TyVarBndr] -- ^ type variables that will be used for the State type in the IsAcidic instance+ -> Name -- ^ 'Name' of the event+ -> Type -- ^ 'Type' of the event+ -> [Pred] -- ^ extra context to add to 'IsAcidic' instance+eventCxts targetStateType targetTyVars eventName eventType =+ let (_tyvars, cxt, _args, stateType, _resultType, _isUpdate) + = analyseType eventName eventType+ eventTyVars = findTyVars stateType -- find the type variable names that this event is using for the State type+ table = zip eventTyVars (map tyVarBndrName targetTyVars) -- create a lookup table+ in map (unify table) cxt -- rename the type variables+ where+ -- | rename the type variables in a Pred+ unify :: [(Name, Name)] -> Pred -> Pred+ unify table p@(ClassP n tys) = ClassP n (map (rename p table) tys)+ unify table p@(EqualP a b) = EqualP (rename p table a) (rename p table b)++ -- | rename the type variables in a Type+ rename :: Pred -> [(Name, Name)] -> Type -> Type+ rename pred table t@(ForallT tyvarbndrs cxt typ) = -- this is probably wrong? I don't think acid-state can really handle this type anyway..+ ForallT (map renameTyVar tyvarbndrs) (map (unify table) cxt) (rename pred table typ)+ where+ renameTyVar (PlainTV name) = PlainTV (renameName pred table name)+ renameTyVar (KindedTV name k) = KindedTV (renameName pred table name) k+ rename pred table (VarT n) = VarT $ renameName pred table n+ rename pred table (AppT a b) = AppT (rename pred table a) (rename pred table b)+ rename pred table (SigT a k) = SigT (rename pred table a) k+ rename _ _ typ = typ++ -- | rename a 'Name'+ renameName :: Pred -> [(Name, Name)] -> Name -> Name+ renameName pred table n = + case lookup n table of+ Nothing -> error $ unlines [ show $ ppr_sig eventName eventType+ , ""+ , "can not be used as an UpdateEvent because the class context: "+ , ""+ , pprint pred+ , ""+ , "contains a type variable which is not found in the state type: " + , ""+ , pprint targetStateType+ , ""+ , "You may be able to fix this by providing a type signature that fixes these type variable(s)"+ ]+ (Just n') -> n'+ -- UpdateEvent (\(MyUpdateEvent arg1 arg2) -> myUpdateEvent arg1 arg2) makeEventHandler :: Name -> Type -> ExpQ makeEventHandler eventName eventType- = do vars <- replicateM (length args) (newName "arg")+ = do assertTyVarsOk+ vars <- replicateM (length args) (newName "arg") let lamClause = conP eventStructName [varP var | var <- vars ] conE constr `appE` lamE [lamClause] (foldl appE (varE eventName) (map varE vars)) where constr = if isUpdate then 'UpdateEvent else 'QueryEvent- (_tyvars, _cxt, args, _stateType, _resultType, isUpdate) = analyseType eventName eventType+ (tyvars, _cxt, args, stateType, _resultType, isUpdate) = analyseType eventName eventType eventStructName = mkName (structName (nameBase eventName)) structName [] = [] structName (x:xs) = toUpper x : xs+ stateTypeTyVars = findTyVars stateType+ tyVarNames = map tyVarBndrName tyvars+ assertTyVarsOk =+ case tyVarNames \\ stateTypeTyVars of+ [] -> return ()+ ns -> error $ unlines+ [show $ ppr_sig eventName eventType+ , ""+ , "can not be used as an UpdateEvent because it contains the type variables: "+ , ""+ , pprint ns+ , ""+ , "which do not appear in the state type:"+ , ""+ , pprint stateType+ ]+ + --data MyUpdateEvent = MyUpdateEvent Arg1 Arg2 -- deriving (Typeable) makeEventDataType eventName eventType = do let con = normalC eventStructName [ strictType notStrict (return arg) | arg <- args ]- dataD (return cxt) eventStructName tyvars [con] [''Typeable]- where (tyvars, cxt, args, _stateType, _resultType, _isUpdate) = analyseType eventName eventType+ dataD (return []) eventStructName tyvars [con] [''Typeable]+ where (tyvars, _cxt, args, _stateType, _resultType, _isUpdate) = analyseType eventName eventType eventStructName = mkName (structName (nameBase eventName)) structName [] = [] structName (x:xs) = toUpper x : xs@@ -182,7 +287,7 @@ ForallT binds [] t' -> (binds, [], t') ForallT binds cxt t' ->- error $ "Context restrictions on events aren't supported yet: " ++ show eventName+ (binds, cxt, t') _ -> ([], [], t) args = getArgs t' (stateType, resultType, isUpdate) = findMonad t'@@ -197,3 +302,17 @@ | con == ''Update = (state, result, True) | con == ''Query = (state, result, False) findMonad _ = error $ "Event has an invalid type signature: Not an Update or a Query: " ++ show eventName++-- | find the type variables+-- | e.g. State a b ==> [a,b]+findTyVars :: Type -> [Name]+findTyVars (ForallT _ _ a) = findTyVars a+findTyVars (VarT n) = [n]+findTyVars (AppT a b) = findTyVars a ++ findTyVars b+findTyVars (SigT a _) = findTyVars a+findTyVars _ = []++-- | extract the 'Name' from a 'TyVarBndr'+tyVarBndrName :: TyVarBndr -> Name+tyVarBndrName (PlainTV n) = n+tyVarBndrName (KindedTV n _) = n