packages feed

acid-state 0.3.3 → 0.4

raw patch · 18 files changed

+602/−244 lines, 18 filesdep +cerealdep +safecopydep +unixdep −binarydep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: cereal, safecopy, unix

Dependencies removed: binary

Dependency ranges changed: base

API changes (from Hackage documentation)

- Data.Acid.Local: instance Binary Checkpoint
+ Data.Acid: createCheckpointAndClose :: SafeCopy st => AcidState st -> IO ()
+ Data.Acid: query' :: (QueryEvent event, MonadIO m) => AcidState (EventState event) -> event -> m (EventResult event)
+ Data.Acid: type EventState ev = MethodState ev
+ Data.Acid: update' :: (UpdateEvent event, MonadIO m) => AcidState (EventState event) -> event -> m (EventResult event)
+ Data.Acid.Core: closeCore' :: Core st -> (st -> IO ()) -> IO ()
+ Data.Acid.Local: createCheckpointAndClose :: SafeCopy st => AcidState st -> IO ()
+ Data.Acid.Local: instance SafeCopy Checkpoint
+ Data.Acid.Local: query' :: (QueryEvent event, MonadIO m) => AcidState (EventState event) -> event -> m (EventResult event)
+ Data.Acid.Local: scheduleUpdate :: UpdateEvent event => AcidState (EventState event) -> event -> IO (MVar (EventResult event))
+ Data.Acid.Local: type EventState ev = MethodState ev
+ Data.Acid.Local: update' :: (UpdateEvent event, MonadIO m) => AcidState (EventState event) -> event -> m (EventResult event)
- Data.Acid: class Binary st => IsAcidic st
+ Data.Acid: class SafeCopy st => IsAcidic st
- Data.Acid: createCheckpoint :: Binary st => AcidState st -> IO ()
+ Data.Acid: createCheckpoint :: SafeCopy st => AcidState st -> IO ()
- Data.Acid.Core: class (Typeable ev, Binary ev, Typeable (MethodResult ev), Binary (MethodResult ev)) => Method ev where { type family MethodResult ev; type family MethodState ev; { methodTag ev = pack (show (typeOf ev)) } }
+ 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: lookupColdMethod :: Core st -> Tagged ByteString -> (State st ByteString)
+ Data.Acid.Core: lookupColdMethod :: Core st -> Tagged ByteString -> State st ByteString
- Data.Acid.Local: class Binary st => IsAcidic st
+ Data.Acid.Local: class SafeCopy st => IsAcidic st
- Data.Acid.Local: createCheckpoint :: Binary st => AcidState st -> IO ()
+ Data.Acid.Local: createCheckpoint :: SafeCopy st => AcidState st -> IO ()

Files

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.3.3+Version:             0.4  -- A short (one-line) description of the package. Synopsis:            Add ACID guarantees to any serializable Haskell data structure.@@ -37,7 +37,7 @@  -- Extra files to be distributed with the package, such as examples or -- a README.-Extra-source-files:  examples/*.hs+Extra-source-files:  examples/*.hs, examples/errors/*.hs  -- Constraint on the version of Cabal needed to build this package. Cabal-version:       >=1.6@@ -54,11 +54,13 @@                        Data.Acid.TemplateHaskell      -- Packages needed in order to build this package.-  Build-depends:       base >= 4 && < 5, binary, bytestring, stm, filepath, directory,-                       mtl, array, containers, template-haskell+  Build-depends:       base >= 4 && < 5, cereal >= 0.3.2.0, safecopy >= 0.5, bytestring, stm,+                       filepath, directory, mtl, array, containers, template-haskell, unix -  Hs-Source-Dirs:         src/-  +  Hs-Source-Dirs:      src/++  GHC-Options:         -fwarn-unused-imports -fwarn-unused-binds+   -- Extra tools (e.g. alex, hsc2hs, ...) needed to build the source.   -- Build-tools:            
examples/HelloDatabase.hs view
@@ -7,14 +7,12 @@ import Control.Monad.Reader                  ( ask ) import Control.Applicative                   ( (<$>) ) import System.Environment                    ( getArgs )-import qualified Data.Binary as Binary+import Data.SafeCopy  type Message = String data Database = Database [Message] -instance Binary.Binary Database where-    get = Database <$> Binary.get-    put (Database msg) = Binary.put msg+$(deriveSafeCopy 0 'base ''Database)  -- Transactions are defined to run in either the 'Update' monad -- or the 'Query' monad.                                                                                                                                    
examples/HelloWorld.hs view
@@ -1,13 +1,12 @@ {-# LANGUAGE DeriveDataTypeable, TypeFamilies, TemplateHaskell #-} module Main (main) where -import Data.Acid.Core import Data.Acid -import qualified Control.Monad.State as State+import Control.Monad.State import Control.Monad.Reader import System.Environment-import Data.Binary+import Data.SafeCopy  import Data.Typeable @@ -17,16 +16,14 @@ data HelloWorldState = HelloWorldState String     deriving (Show, Typeable) -instance Binary HelloWorldState where-    put (HelloWorldState state) = put state-    get = liftM HelloWorldState get+$(deriveSafeCopy 0 'base ''HelloWorldState)  ------------------------------------------------------ -- The transaction we will execute over the state.  writeState :: String -> Update HelloWorldState () writeState newValue-    = State.put (HelloWorldState newValue)+    = put (HelloWorldState newValue)  queryState :: Query HelloWorldState String queryState = do HelloWorldState string <- ask
examples/HelloWorldNoTH.hs view
@@ -4,10 +4,10 @@ import Data.Acid.Core import Data.Acid.Local -import qualified Control.Monad.State as State+import Control.Monad.State import Control.Monad.Reader import System.Environment-import Data.Binary+import Data.SafeCopy  import Data.Typeable @@ -17,16 +17,16 @@ data HelloWorldState = HelloWorldState String     deriving (Show, Typeable) -instance Binary HelloWorldState where-    put (HelloWorldState state) = put state-    get = liftM HelloWorldState get+instance SafeCopy HelloWorldState where+    putCopy (HelloWorldState state) = contain $ safePut state+    getCopy = contain $ liftM HelloWorldState safeGet  ------------------------------------------------------ -- The transaction we will execute over the state.  writeState :: String -> Update HelloWorldState () writeState newValue-    = State.put (HelloWorldState newValue)+    = put (HelloWorldState newValue)  queryState :: Query HelloWorldState String queryState = do HelloWorldState string <- ask@@ -55,18 +55,18 @@   deriving instance Typeable WriteState-instance Binary WriteState where-    put (WriteState st) = put st-    get = liftM WriteState get+instance SafeCopy WriteState where+    putCopy (WriteState st) = contain $ safePut st+    getCopy = contain $ liftM WriteState safeGet instance Method WriteState where     type MethodResult WriteState = ()     type MethodState WriteState = HelloWorldState instance UpdateEvent WriteState  deriving instance Typeable QueryState-instance Binary QueryState where-    put QueryState = return ()-    get = return QueryState+instance SafeCopy QueryState where+    putCopy QueryState = contain $ return ()+    getCopy = contain $ return QueryState instance Method QueryState where     type MethodResult QueryState = String     type MethodState QueryState = HelloWorldState
examples/KeyValue.hs view
@@ -1,15 +1,14 @@ {-# LANGUAGE DeriveDataTypeable, TypeFamilies, TemplateHaskell #-} module Main (main) where -import Data.Acid.Core import Data.Acid -import qualified Control.Monad.State as State+import Control.Monad.State import Control.Monad.Reader import Control.Applicative import System.Environment import System.IO-import Data.Binary+import Data.SafeCopy  import Data.Typeable @@ -24,17 +23,15 @@ data KeyValue = KeyValue !(Map.Map Key Value)     deriving (Typeable) -instance Binary KeyValue where-    put (KeyValue state) = put state-    get = liftM KeyValue get+$(deriveSafeCopy 0 'base ''KeyValue)  ------------------------------------------------------ -- The transaction we will execute over the state.  insertKey :: Key -> Value -> Update KeyValue () insertKey key value-    = do KeyValue m <- State.get-         State.put (KeyValue (Map.insert key value m))+    = do KeyValue m <- get+         put (KeyValue (Map.insert key value m))  lookupKey :: Key -> Query KeyValue (Maybe Value) lookupKey key
examples/KeyValueNoTH.hs view
@@ -9,7 +9,7 @@ import Control.Applicative import System.Environment import System.IO-import Data.Binary+import Data.SafeCopy  import Data.Typeable @@ -24,9 +24,9 @@ data KeyValue = KeyValue !(Map.Map Key Value)     deriving (Typeable) -instance Binary KeyValue where-    put (KeyValue state) = put state-    get = liftM KeyValue get+instance SafeCopy KeyValue where+    putCopy (KeyValue state) = contain $ safePut state+    getCopy = contain $ liftM KeyValue safeGet  ------------------------------------------------------ -- The transaction we will execute over the state.@@ -72,18 +72,18 @@   deriving instance Typeable InsertKey-instance Binary InsertKey where-    put (InsertKey key value) = put key >> put value-    get = InsertKey <$> get <*> get+instance SafeCopy InsertKey where+    putCopy (InsertKey key value) = contain $ safePut key >> safePut value+    getCopy = contain $ InsertKey <$> safeGet <*> safeGet instance Method InsertKey where     type MethodResult InsertKey = ()     type MethodState InsertKey = KeyValue instance UpdateEvent InsertKey  deriving instance Typeable LookupKey-instance Binary LookupKey where-    put (LookupKey key) = put key-    get = LookupKey <$> get+instance SafeCopy LookupKey where+    putCopy (LookupKey key) = contain $ safePut key+    getCopy = contain $ LookupKey <$> safeGet instance Method LookupKey where     type MethodResult LookupKey = Maybe Value     type MethodState LookupKey = KeyValue
+ examples/SlowCheckpoint.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE DeriveDataTypeable, TypeFamilies, TemplateHaskell #-}+module Main (main) where++import Data.Acid++import Control.Monad.State+import Control.Concurrent+import Data.Time+import System.IO+import Data.SafeCopy++------------------------------------------------------+-- The Haskell structure that we want to encapsulate++data SlowCheckpoint = SlowCheckpoint Int Int++$(deriveSafeCopy 0 'base ''SlowCheckpoint)++------------------------------------------------------+-- The transaction we will execute over the state.++-- This transaction adds a very computationally heavy entry+-- into our state. However, since the state is lazy, the+-- chunk will not be forced until we create a checkpoint.+-- Computing 'last [0..100000000]' takes roughly 2 seconds+-- on my machine.       XXX Lemmih, 2011-04-26+setComputationallyHeavyData :: Update SlowCheckpoint ()+setComputationallyHeavyData+    = do SlowCheckpoint _slow tick <- get+         put $ SlowCheckpoint (last [0..100000000]) tick++tick :: Update SlowCheckpoint Int+tick = do SlowCheckpoint slow tick <- get+          put $ SlowCheckpoint slow (tick+1)+          return tick++$(makeAcidic ''SlowCheckpoint ['setComputationallyHeavyData, 'tick])++------------------------------------------------------+-- This is how AcidState is used:++main :: IO ()+main = do acid <- openAcidStateFrom "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 "has gone awry."+          putStrLn ""+          doTick acid+          update acid SetComputationallyHeavyData+          forkIO $ do putStrLn "Seriazing checkpoint..."+                      t <- timeIt $ createCheckpoint acid+                      putStrLn $ "Checkpoint created in: " ++ show t+          replicateM_ 20 $+            do doTick acid+               threadDelay (10^5)++doTick acid+    = do tick <- update acid Tick+         putStrLn $ "Tick: " ++ show tick++timeIt action+    = do t1 <- getCurrentTime+         ret <- action+         t2 <- getCurrentTime+         return (diffUTCTime t2 t1)
examples/StressTest.hs view
@@ -1,14 +1,14 @@ {-# LANGUAGE DeriveDataTypeable, TypeFamilies, TemplateHaskell #-} module Main (main) where -import Data.Acid.Core import Data.Acid+import Data.Acid.Local -import qualified Control.Monad.State as State+import Control.Monad.State import Control.Monad.Reader import System.Environment import System.IO-import Data.Binary+import Data.SafeCopy  import Data.Typeable @@ -16,18 +16,16 @@ -- The Haskell structure that we want to encapsulate  data StressState = StressState !Int-    deriving (Show, Typeable)+    deriving (Typeable) -instance Binary StressState where-    put (StressState state) = put state-    get = liftM StressState get+$(deriveSafeCopy 0 'base ''StressState)  ------------------------------------------------------ -- The transaction we will execute over the state.  pokeState :: Update StressState ()-pokeState = do StressState i <- State.get-               State.put (StressState (i+1))+pokeState = do StressState i <- get+               put (StressState (i+1))  queryState :: Query StressState Int queryState = do StressState i <- ask@@ -39,21 +37,23 @@ -- This is how AcidState is used:  main :: IO ()-main = do acid <- openAcidState (StressState 0)-          args <- getArgs+main = do args <- getArgs           case args of             ["checkpoint"]-              -> createCheckpoint acid+              -> do acid <- openAcidState (StressState 0)+                    createCheckpoint acid             ["query"]-              -> do n <- query acid QueryState+              -> do acid <- openAcidState (StressState 0)+                    n <- query acid QueryState                     putStrLn $ "State value: " ++ show n             ["poke"]-              -> do putStr "Issuing 10k sequential transactions... "+              -> do acid <- openAcidState (StressState 0)+                    putStr "Issuing 100k transactions... "                     hFlush stdout-                    replicateM_ 10000 (update acid PokeState)+                    replicateM_ (100000-1) (scheduleUpdate acid PokeState)+                    update acid PokeState                     putStrLn "Done"             _ -> do putStrLn $ "Commands:"                     putStrLn $ "  query            Prints out the current state."-                    putStrLn $ "  poke             Spawn 10k transactions."+                    putStrLn $ "  poke             Spawn 100k transactions."                     putStrLn $ "  checkpoint       Create a new checkpoint."-          closeAcidState acid
examples/StressTestNoTH.hs view
@@ -4,11 +4,11 @@ import Data.Acid.Core import Data.Acid.Local -import qualified Control.Monad.State as State+import Control.Monad.State import Control.Monad.Reader import System.Environment import System.IO-import Data.Binary+import Data.SafeCopy  import Data.Typeable @@ -16,18 +16,18 @@ -- The Haskell structure that we want to encapsulate  data StressState = StressState !Int-    deriving (Show, Typeable)+    deriving (Typeable) -instance Binary StressState where-    put (StressState state) = put state-    get = liftM StressState get+instance SafeCopy StressState where+    putCopy (StressState state) = contain $ safePut state+    getCopy = contain $ liftM StressState safeGet  ------------------------------------------------------ -- The transaction we will execute over the state.  pokeState :: Update StressState ()-pokeState = do StressState i <- State.get-               State.put (StressState (i+1))+pokeState = do StressState i <- get+               put (StressState (i+1))  queryState :: Query StressState Int queryState = do StressState i <- ask@@ -38,27 +38,28 @@ -- This is how AcidState is used:  main :: IO ()-main = do acid <- openAcidState (StressState 0)-          args <- getArgs+main = do args <- getArgs           case args of             ["checkpoint"]-              -> createCheckpoint acid+              -> do acid <- openAcidState (StressState 0)+                    createCheckpoint acid             ["query"]-              -> do n <- query acid QueryState+              -> do acid <- openAcidState (StressState 0)+                    n <- query acid QueryState                     putStrLn $ "State value: " ++ show n             ["poke"]-              -> do putStr "Issuing 10k sequential transactions... "+              -> do acid <- openAcidState (StressState 0)+                    putStr "Issuing 100k transactions... "                     hFlush stdout-                    replicateM_ 10000 (update acid PokeState)+                    replicateM_ (100000-1) (scheduleUpdate acid PokeState)+                    update acid PokeState                     putStrLn "Done"             _ -> do putStrLn $ "Commands:"                     putStrLn $ "  query            Prints out the current state."-                    putStrLn $ "  poke             Spawn 10k transactions."+                    putStrLn $ "  poke             Spawn 100k transactions."                     putStrLn $ "  checkpoint       Create a new checkpoint."-          closeAcidState acid  - ------------------------------------------------------ -- The gritty details. These things may be done with -- Template Haskell in the future.@@ -68,18 +69,18 @@   deriving instance Typeable PokeState-instance Binary PokeState where-    put PokeState = return ()-    get = return PokeState+instance SafeCopy PokeState where+    putCopy PokeState = contain $ return ()+    getCopy = contain $ return PokeState instance Method PokeState where     type MethodResult PokeState = ()     type MethodState PokeState = StressState instance UpdateEvent PokeState  deriving instance Typeable QueryState-instance Binary QueryState where-    put QueryState = return ()-    get = return QueryState+instance SafeCopy QueryState where+    putCopy QueryState = contain $ return ()+    getCopy = contain $ return QueryState instance Method QueryState where     type MethodResult QueryState = Int     type MethodState QueryState = StressState
+ examples/errors/ChangeState.hs view
@@ -0,0 +1,50 @@+{-# LANGUAGE DeriveDataTypeable, TypeFamilies, TemplateHaskell #-}+module Main (main) where++import Data.Acid++import Control.Monad.State+import System.Environment+import Data.SafeCopy++import Data.Typeable++import Control.Exception+import Prelude hiding (catch)++import qualified Data.Text as Text+++------------------------------------------------------+-- The Haskell structure that we want to encapsulate++data FirstState = FirstState String+    deriving (Show)++data SecondState = SecondState Text.Text+    deriving (Show)++$(deriveSafeCopy 0 'base ''FirstState)+$(deriveSafeCopy 0 'base ''SecondState)++------------------------------------------------------+-- The transaction we will execute over the state.++$(makeAcidic ''FirstState [])+$(makeAcidic ''SecondState [])++------------------------------------------------------+-- This is how AcidState is used:++main :: IO ()+main = do putStrLn "This example simulates what happens when you modify your state type"+          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")+          createCheckpoint firstAcid+          closeAcidState firstAcid++          secondAcid <- openAcidStateFrom "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
@@ -0,0 +1,57 @@+{-# LANGUAGE DeriveDataTypeable, TypeFamilies, TemplateHaskell #-}+module Main (main) where++import Data.Acid++import Control.Monad.State+import System.Environment+import Data.SafeCopy++import Data.Typeable++import Control.Exception+import Prelude hiding (catch)++------------------------------------------------------+-- The Haskell structure that we want to encapsulate++newtype MyState = MyState Integer+    deriving (Show, Typeable)++$(deriveSafeCopy 0 'base ''MyState)++------------------------------------------------------+-- The transaction we will execute over the state.++failEvent :: Update MyState ()+failEvent = fail "fail!"++errorEvent :: Update MyState ()+errorEvent = error "error!"++stateError :: Update MyState ()+stateError = put (error "state error!")++tick :: Update MyState Integer+tick = do MyState n <- get+          put $ MyState (n+1)+          return n++$(makeAcidic ''MyState ['failEvent, 'errorEvent, 'stateError, 'tick])++------------------------------------------------------+-- This is how AcidState is used:++main :: IO ()+main = do acid <- openAcidStateFrom "state/Exceptions" (MyState 0)+          args <- getArgs+          case args of+            ["1"] -> update acid (undefined :: FailEvent)+            ["2"] -> update acid FailEvent+            ["3"] -> update acid ErrorEvent+            ["4"] -> update acid StateError+            _     -> do putStrLn "Call with '1', '2', '3' or '4' to test error scenarios."+                        n <- update acid Tick+                        putStrLn $ "Tick: " ++ show n+           `catch` \e -> do putStrLn $ "Caught exception: " ++ show (e:: SomeException)+                            createCheckpointAndClose acid
+ examples/errors/RemoveEvent.hs view
@@ -0,0 +1,50 @@+{-# LANGUAGE DeriveDataTypeable, TypeFamilies, TemplateHaskell #-}+module Main (main) where++import Data.Acid++import Control.Monad.State+import System.Environment+import Data.SafeCopy++import Data.Typeable++import Control.Exception+import Prelude hiding (catch)++------------------------------------------------------+-- The Haskell structure that we want to encapsulate++data FirstState = FirstState+    deriving (Show)++data SecondState = SecondState+    deriving (Show)++$(deriveSafeCopy 0 'base ''FirstState)+$(deriveSafeCopy 0 'base ''SecondState)++------------------------------------------------------+-- The transaction we will execute over the state.++firstEvent :: Update FirstState ()+firstEvent = return ()++$(makeAcidic ''FirstState ['firstEvent])+$(makeAcidic ''SecondState [])++------------------------------------------------------+-- This is how AcidState is used:++main :: IO ()+main = do putStrLn "This example simulates what happens when you remove an event"+          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+          update firstAcid FirstEvent+          closeAcidState firstAcid++          secondAcid <- openAcidStateFrom "state/RemoveEvent" SecondState+          closeAcidState secondAcid+          putStrLn "If you see this message then something has gone wrong!"
src/Data/Acid.hs view
@@ -19,9 +19,13 @@     , openAcidStateFrom     , closeAcidState     , createCheckpoint+    , createCheckpointAndClose     , update     , query+    , update'+    , query'     , EventResult+    , EventState     , UpdateEvent     , QueryEvent     , Update
src/Data/Acid/Archive.hs view
@@ -17,8 +17,9 @@  import qualified Data.ByteString.Lazy as Lazy import qualified Data.ByteString as Strict-import Data.Binary.Get-import Data.Binary.Builder+import qualified Data.Serialize.Get as Serialize+import Data.Serialize.Get hiding (Result(..))+import Data.Serialize.Builder import Data.Monoid  type Entry = Lazy.ByteString@@ -51,23 +52,25 @@  readEntries :: Lazy.ByteString -> Entries readEntries bs-    | Lazy.null bs-    = Done-    | Lazy.length header < headerSize-    = Fail "Incomplete header."-    | Lazy.length content /= fromIntegral contentLength-    = Fail "Insuficient content."-    | crc16 content /= contentHash-    = Fail "Invalid hash"-    | otherwise-    = Next content (readEntries rest)-    where header        = Lazy.take headerSize bs-          headerSize    = 10-          contentLength = fromIntegral $ runGet getWord64le header-          contentHash   = runGet getWord16le $ Lazy.drop 8 header-          content       = Lazy.take contentLength $ Lazy.drop headerSize bs-          rest          = Lazy.drop (contentLength+headerSize) bs--lazyToStrict :: Lazy.ByteString -> Strict.ByteString-lazyToStrict = Strict.concat . Lazy.toChunks+    = worker (Lazy.toChunks bs)+    where worker [] = Done+          worker (x:xs)+              = check (runGetPartial readEntry x) xs+          check result more+              = case result of+                  Serialize.Done entry rest+                      | Strict.null rest    -> Next entry (worker more)+                      | otherwise           -> Next entry (worker (rest:more))+                  Serialize.Fail msg        -> Fail msg+                  Serialize.Partial cont    -> case more of+                                                 []     -> check (cont Strict.empty) []+                                                 (x:xs) -> check (cont x) xs +readEntry :: Get Entry+readEntry+    = do contentLength <- getWord64le+         contentChecksum <-getWord16le+         content <- getLazyByteString (fromIntegral contentLength)+         if crc16 content /= contentChecksum+           then fail "Invalid hash"+           else return content
src/Data/Acid/Core.hs view
@@ -23,6 +23,7 @@     , Tagged     , mkCore     , closeCore+    , closeCore'     , modifyCoreState     , modifyCoreState_     , withCoreState@@ -32,37 +33,37 @@     , runColdMethod     ) where -import Control.Concurrent-import Control.Monad-import Control.Monad.State (State, runState )+import Control.Concurrent                 ( MVar, newMVar, withMVar+                                          , modifyMVar, modifyMVar_ )+import Control.Monad                      ( liftM )+import Control.Monad.State                ( State, runState ) import qualified Data.Map as Map-import qualified Data.ByteString.Lazy as Lazy-import qualified Data.ByteString.Lazy.Char8 as Lazy.Char8+import Data.ByteString.Lazy as Lazy       ( ByteString )+import Data.ByteString.Lazy.Char8 as Lazy ( pack, unpack ) -import Data.Binary+import Data.Serialize                     ( runPutLazy, runGetLazy )+import Data.SafeCopy                      ( SafeCopy, safeGet, safePut ) -import Data.Typeable-import Unsafe.Coerce (unsafeCoerce)+import Data.Typeable                      ( Typeable, typeOf )+import Unsafe.Coerce                      ( unsafeCoerce )   -- | The basic Method class. Each Method has an indexed result type --   and a unique tag.-class ( Typeable ev, Binary ev-      , Typeable (MethodResult ev), Binary (MethodResult ev)) =>+class ( Typeable ev, SafeCopy ev+      , Typeable (MethodResult ev), SafeCopy (MethodResult ev)) =>       Method ev where     type MethodResult ev     type MethodState ev     methodTag :: ev -> Tag-    methodTag ev = Lazy.Char8.pack (show (typeOf ev))+    methodTag ev = Lazy.pack (show (typeOf ev))  -- | The control structure at the very center of acid-state. --   This module provides access to a mutable state through --   methods. No efforts towards durability, checkpointing or --   sharding happens at this level. --   Important things to keep in mind in this module:--- --     * We don't distinguish between updates and queries.--- --     * We allow direct access to the core state as well --       as through events. data Core st@@ -82,16 +83,23 @@ -- | Mark Core as closed. Any subsequent use will throw an exception. closeCore :: Core st -> IO () closeCore core-    = do swapMVar (coreState core) errorMsg-         return ()+    = closeCore' core (\_st -> return ())++-- | Access the state and then mark the Core as closed. Any subsequent use+--   will throw an exception.+closeCore' :: Core st -> (st -> IO ()) -> IO ()+closeCore' core action+    = modifyMVar_ (coreState core) $ \st ->+      do action st+         return errorMsg     where errorMsg = error "Access failure: Core closed."  -- | Modify the state component. The resulting state is ensured to be in --   WHNF. modifyCoreState :: Core st -> (st -> IO (st, a)) -> IO a modifyCoreState core action-    = modifyMVar (coreState core) $ \st -> do (!st, a) <- action st-                                              return (st, a)+    = modifyMVar (coreState core) $ \st -> do (!st', a) <- action st+                                              return (st', a)  -- | Modify the state component. The resulting state is ensured to be in --   WHNF.@@ -118,13 +126,25 @@          return ( st', a)  -- | Find the state action that corresponds to a tagged and serialized method.-lookupColdMethod :: Core st -> Tagged Lazy.ByteString -> (State st Lazy.ByteString)-lookupColdMethod core (methodTag, methodContent)-    = case Map.lookup methodTag (coreMethods core) of-        Nothing      -> error $ "Method tag doesn't exist: " ++ show methodTag+lookupColdMethod :: Core st -> Tagged Lazy.ByteString -> State st Lazy.ByteString+lookupColdMethod core (storedMethodTag, methodContent)+    = case Map.lookup storedMethodTag (coreMethods core) of+        Nothing      -> missingMethod storedMethodTag         Just (Method method)-          -> liftM encode (method (decode methodContent))-      +          -> liftM (runPutLazy . safePut) (method (lazyDecode methodContent))++lazyDecode :: SafeCopy a => Lazy.ByteString -> a+lazyDecode inp+    = case runGetLazy safeGet inp of+        Left msg  -> error msg+        Right val -> val++missingMethod :: Tag -> a+missingMethod tag+    = error msg+    where msg = "This method is required but not available: " ++ show (Lazy.unpack tag) +++                ". Did you perhaps remove it before creating a checkpoint?"+ -- | Apply an in-memory method to the state. runHotMethod :: Method method => Core (MethodState method) -> method -> IO (MethodResult method) runHotMethod core method@@ -136,7 +156,7 @@ lookupHotMethod :: Method method => Core (MethodState method) -> method -> State (MethodState method) (MethodResult method) lookupHotMethod core method     = case Map.lookup (methodTag method) (coreMethods core) of-        Nothing -> error $ "Method type doesn't exist: " ++ show (typeOf method)+        Nothing -> missingMethod (methodTag method)         Just (Method methodHandler)           -> -- If the methodTag doesn't index the right methodHandler then we're in deep              -- trouble. Luckly, it would take deliberate malevolence for that to happen.
src/Data/Acid/Local.hs view
@@ -1,5 +1,5 @@ {-# LANGUAGE GADTs, OverloadedStrings, DeriveDataTypeable, TypeFamilies,-             MagicHash, GeneralizedNewtypeDeriving #-}+             GeneralizedNewtypeDeriving, BangPatterns #-} ----------------------------------------------------------------------------- -- | -- Module      :  Data.Acid.Local@@ -18,6 +18,7 @@     , AcidState     , Event(..)     , EventResult+    , EventState     , UpdateEvent     , QueryEvent     , Update@@ -26,23 +27,33 @@     , openAcidStateFrom     , closeAcidState     , createCheckpoint+    , createCheckpointAndClose     , update+    , scheduleUpdate     , query+    , update'+    , query'     ) where  import Data.Acid.Log as Log import Data.Acid.Core -import Control.Concurrent-import qualified Control.Monad.State as State-import Control.Monad.Reader-import Control.Applicative-import qualified Data.ByteString.Lazy as Lazy+import Control.Concurrent             ( newEmptyMVar, putMVar, takeMVar, MVar )+--import Control.Exception              ( evaluate )+import Control.Monad.State            ( MonadState, State, get, runState )+import Control.Monad.Reader           ( Reader, runReader, MonadReader )+import Control.Monad.Trans            ( MonadIO(liftIO) )+import Control.Applicative            ( (<$>), (<*>) )+import Data.ByteString.Lazy           ( ByteString )+--import qualified Data.ByteString.Lazy as Lazy ( length ) -import Data.Binary-import Data.Typeable-import System.FilePath +import Data.Serialize                 ( runPutLazy, runGetLazy )+import Data.SafeCopy                  ( SafeCopy(..), safeGet, safePut+                                      , primitive, contain )+import Data.Typeable                  ( Typeable, typeOf )+import System.FilePath                ( (</>) )+ -- | Events return the same thing as Methods. The exact type of 'EventResult' --   depends on the event. type EventResult ev = MethodResult ev@@ -70,7 +81,7 @@ eventsToMethods = map worker     where worker :: Event st -> MethodContainer st           worker (UpdateEvent fn) = Method (unUpdate . fn)-          worker (QueryEvent fn)  = Method (\ev -> do st <- State.get+          worker (QueryEvent fn)  = Method (\ev -> do st <- get                                                       return (runReader (unQuery $ fn ev) st)                                            ) {-| State container offering full ACID (Atomicity, Consistency, Isolation and Durability)@@ -89,13 +100,13 @@ -} data AcidState st     = AcidState { localCore        :: Core st-                , localEvents      :: FileLog (Tagged Lazy.ByteString)+                , localEvents      :: FileLog (Tagged ByteString)                 , localCheckpoints :: FileLog Checkpoint                 }  -- | Context monad for Update events.-newtype Update st a = Update { unUpdate :: State.State st a }-    deriving (Monad, State.MonadState st)+newtype Update st a = Update { unUpdate :: State st a }+    deriving (Monad, MonadState st)  -- | Context monad for Query events. newtype Query st a  = Query { unQuery :: Reader st a }@@ -108,48 +119,98 @@ --   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'.+--+--   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 acidState event     = do mvar <- newEmptyMVar-         let encodedEvent = encode event-         Lazy.length encodedEvent `seq`-           modifyCoreState_ (localCore acidState) $ \st ->-             do let !(result, st') = State.runState hotMethod 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) (methodTag event, encodedEvent) $ putMVar mvar result-                return st'-         takeMVar mvar+         let encoded = runPutLazy (safePut event)+         --evaluate (Lazy.length encoded) -- It would be best to encode the event before we lock the core+                                          -- but it hurts performance /-:+         modifyCoreState_ (localCore acidState) $ \st ->+           do let !(result, !st') = runState hotMethod 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) (methodTag event, encoded) $ putMVar mvar result+              return st'+         return mvar     where hotMethod = lookupHotMethod (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)+ -- | 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-    = runHotMethod (localCore acidState) event+    = do mvar <- newEmptyMVar+         withCoreState (localCore acidState) $ \st ->+           do let (result, _st) = runState hotMethod 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 hotMethod = lookupHotMethod (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)+ -- | 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 :: Binary st => AcidState st -> IO ()+createCheckpoint :: SafeCopy st => AcidState st -> IO () createCheckpoint acidState     = do mvar <- newEmptyMVar          withCoreState (localCore acidState) $ \st ->            do eventId <- askCurrentEntryId (localEvents acidState)-              pushEntry (localCheckpoints acidState) (Checkpoint eventId (encode st)) (putMVar mvar ())+              pushAction (localEvents acidState) $+                do let encoded = runPutLazy (safePut st)+                   pushEntry (localCheckpoints acidState) (Checkpoint eventId encoded) (putMVar mvar ())          takeMVar mvar-          +-- | Save a snapshot to disk and close the AcidState as a single atomic+--   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+    = do mvar <- newEmptyMVar+         closeCore' (localCore acidState) $ \st ->+           do eventId <- askCurrentEntryId (localEvents acidState)+              pushAction (localEvents acidState) $+                pushEntry (localCheckpoints acidState) (Checkpoint eventId (runPutLazy (safePut st))) (putMVar mvar ())+         takeMVar mvar+         closeFileLog (localEvents acidState)+         closeFileLog (localCheckpoints acidState) -data Checkpoint = Checkpoint EntryId Lazy.ByteString -instance Binary Checkpoint where-    put (Checkpoint eventEntryId content)-        = do put eventEntryId-             put content-    get = Checkpoint <$> get <*> get+data Checkpoint = Checkpoint EntryId ByteString -class (Binary st) => IsAcidic st where+instance SafeCopy Checkpoint where+    kind = primitive+    putCopy (Checkpoint eventEntryId content)+        = contain $+          do safePut eventEntryId+             safePut content+    getCopy = contain $ Checkpoint <$> safeGet <*> safeGet++class (SafeCopy st) => IsAcidic st where     acidEvents :: [Event st]       -- ^ List of events capable of updating or querying the state. @@ -184,7 +245,9 @@                 Nothing                   -> return 0                 Just (Checkpoint eventCutOff content)-                  -> do modifyCoreState_ core (\_oldState -> return (decode content))+                  -> do modifyCoreState_ core (\_oldState -> case runGetLazy safeGet content of+                                                               Left msg  -> checkpointRestoreError msg+                                                               Right val -> return val)                         return eventCutOff                    eventsLog <- openFileLog eventsLogKey@@ -195,6 +258,9 @@                           , 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.
src/Data/Acid/Log.hs view
@@ -1,3 +1,4 @@+{-# 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.@@ -9,6 +10,7 @@     , openFileLog     , closeFileLog     , pushEntry+    , pushAction     , readEntriesFrom     , newestEntry     , askCurrentEntryId@@ -18,14 +20,21 @@ import System.Directory import System.FilePath import System.IO+import System.Posix                              ( handleToFd, Fd(..), fdWriteBuf+                                                 , closeFd )+import Foreign.C+import Foreign.Ptr import Control.Monad import Control.Concurrent import Control.Concurrent.STM import qualified Data.ByteString.Lazy as Lazy---import qualified Data.ByteString as Strict+import qualified Data.ByteString as Strict+import qualified Data.ByteString.Unsafe as Strict import Data.List import Data.Maybe-import Data.Binary+import qualified Data.Serialize.Get as Get+import qualified Data.Serialize.Put as Put+import Data.SafeCopy                             ( safePut, safeGet, SafeCopy )  import Text.Printf                               ( printf ) @@ -36,7 +45,7 @@  data FileLog object     = FileLog { logIdentifier  :: LogKey object-              , logCurrent     :: MVar (Handle)+              , logCurrent     :: MVar Fd -- Handle               , logNextEntryId :: TVar EntryId               , logQueue       :: TVar ([Lazy.ByteString], [IO ()])               , logThreads     :: [ThreadId]@@ -76,23 +85,28 @@          queue <- newTVarIO ([], [])          nextEntryRef <- newTVarIO 0          tid2 <- forkIO $ fileWriter currentState queue-         let log = FileLog { logIdentifier  = identifier-                           , logCurrent     = currentState-                           , logNextEntryId = nextEntryRef-                           , logQueue       = queue-                           , logThreads     = [tid2] }+         let fLog = FileLog { logIdentifier  = identifier+                            , logCurrent     = currentState+                            , logNextEntryId = nextEntryRef+                            , logQueue       = queue+                            , logThreads     = [tid2] }          if null logFiles             then do let currentEntryId = 0                     currentHandle <- openBinaryFile (logDirectory identifier </> formatLogFile (logPrefix identifier) currentEntryId) WriteMode-                    putMVar currentState currentHandle+                    fd <- handleToFd currentHandle+                    putMVar currentState fd             else do let (lastFileEntryId, lastFilePath) = maximum logFiles                     entries <- readEntities lastFilePath                     let currentEntryId = lastFileEntryId + length entries                     atomically $ writeTVar nextEntryRef currentEntryId-                    currentHandle <- openFile (logDirectory identifier </> formatLogFile (logPrefix identifier) currentEntryId) WriteMode-                    putMVar currentState currentHandle-         return log+                    currentHandle <- openBinaryFile (logDirectory identifier </> formatLogFile (logPrefix identifier) currentEntryId) WriteMode+                    fd <- handleToFd currentHandle+                    putMVar currentState fd+         return fLog +foreign import ccall "fsync" c_fsync :: CInt -> IO CInt++fileWriter :: MVar Fd -> TVar ([Lazy.ByteString], [IO ()]) -> IO () fileWriter currentState queue     = forever $       do (entries, actions) <- atomically $ do (entries, actions) <- readTVar queue@@ -101,20 +115,39 @@                                                -- We don't actually have to reverse the actions                                                -- but I don't think it hurts performance much.                                                return (reverse entries, reverse actions)-         withMVar currentState $ \handle ->+         withMVar currentState $ \fd ->            do let arch = Archive.packEntries entries-              Lazy.hPutStr handle arch-              hFlush handle-              return ()+              writeToDisk fd (repack arch)          sequence_ actions          yield +-- Repack a lazy bytestring into larger blocks that can be efficiently written to disk.+repack :: Lazy.ByteString -> [Strict.ByteString]+repack = worker+    where worker bs+              | Lazy.null bs = []+              | otherwise    = Strict.concat (Lazy.toChunks (Lazy.take blockSize bs)) : worker (Lazy.drop blockSize bs)+          blockSize = 4*1024 +writeToDisk :: Fd -> [Strict.ByteString] -> IO ()+writeToDisk _ [] = return ()+writeToDisk fd@(Fd c_fd) xs+    = do mapM_ worker xs+         c_fsync c_fd+         return ()+    where worker bs+              = do let len = Strict.length bs+                   count <- Strict.unsafeUseAsCString bs $ \ptr -> fdWriteBuf fd (castPtr ptr) (fromIntegral len)+                   if fromIntegral count < len+                      then worker (Strict.drop (fromIntegral count) bs)+                      else return ()++ closeFileLog :: FileLog object -> IO ()-closeFileLog log-    = modifyMVar_ (logCurrent log) $ \handle ->-      do hClose handle-         forkIO $ forM_ (logThreads log) killThread+closeFileLog fLog+    = modifyMVar_ (logCurrent fLog) $ \fd ->+      do closeFd fd+         _ <- forkIO $ forM_ (logThreads fLog) killThread          return $ error "FileLog has been closed"  readEntities :: FilePath -> IO [Lazy.ByteString]@@ -129,13 +162,13 @@ -- Read all durable entries younger than the given EntryId. -- Note that entries written during or after this call won't -- be included in the returned list.-readEntriesFrom :: Binary object => FileLog object -> EntryId -> IO [object]-readEntriesFrom log youngestEntry+readEntriesFrom :: SafeCopy object => FileLog object -> EntryId -> IO [object]+readEntriesFrom fLog youngestEntry     = do -- Cut the log so we can read written entries without interfering          -- with the writing of new entries.-         entryCap <- cutFileLog log+         entryCap <- cutFileLog fLog          -- We're interested in these entries: youngestEntry <= x < entryCap.-         logFiles <- findLogFiles (logIdentifier log)+         logFiles <- findLogFiles (logIdentifier fLog)          let sorted = sort logFiles              findRelevant [] = []              findRelevant [ logFile ]@@ -156,9 +189,9 @@                               []                     -> 0                               ( logFile : _logFiles) -> rangeStart logFile -         archive <- liftM Lazy.concat $ mapM Lazy.readFile (map snd relevant)+         archive <- liftM Lazy.concat $ mapM (Lazy.readFile . snd) relevant          let entries = entriesToList $ readEntries archive-         return $ map decode+         return $ map decode'                 $ take (entryCap - youngestEntry)             -- Take events under the eventCap.                 $ drop (youngestEntry - firstEntryId) entries -- Drop entries that are too young. @@ -166,39 +199,37 @@   cutFileLog :: FileLog object -> IO EntryId-cutFileLog log+cutFileLog fLog     = do mvar <- newEmptyMVar          let action = do currentEntryId <- atomically $-                                           do (entries, _) <- readTVar (logQueue log)-                                              next <- readTVar (logNextEntryId log)+                                           do (entries, _) <- readTVar (logQueue fLog)+                                              next <- readTVar (logNextEntryId fLog)                                               return (next - length entries)-                         modifyMVar_ (logCurrent log) $ \old ->-                           do hClose old-                              openFile (logDirectory key </> formatLogFile (logPrefix key) currentEntryId) WriteMode+                         modifyMVar_ (logCurrent fLog) $ \old ->+                           do closeFd old+                              handleToFd =<< openBinaryFile (logDirectory key </> formatLogFile (logPrefix key) currentEntryId) WriteMode                          putMVar mvar currentEntryId-         atomically $-           do (entries, actions) <- readTVar (logQueue log)-              writeTVar (logQueue log) (entries, action : actions)+         pushAction fLog action          takeMVar mvar-    where key = logIdentifier log+    where key = logIdentifier fLog  -- Finds the newest entry in the log. Doesn't work on open logs. -- Do not use after the log has been opened. -- Implementation: Search the newest log files first. Once a file --                 containing at least one valid entry is found, --                 return the last entry in that file.-newestEntry :: Binary object => LogKey object -> IO (Maybe object)+newestEntry :: SafeCopy object => LogKey object -> IO (Maybe object) newestEntry identifier     = do logFiles <- findLogFiles identifier          let sorted = reverse $ sort logFiles-             (eventIds, files) = unzip sorted+             (_eventIds, files) = unzip sorted          archives <- mapM Lazy.readFile files          return $ worker archives     where worker [] = Nothing           worker (archive:archives)               = case Archive.readEntries archive of                   Done            -> worker archives-                  Next entry next -> Just (decode (lastEntry entry next))+                  Next entry next -> Just (decode' (lastEntry entry next))                   Fail{}          -> worker archives           lastEntry entry Done   = entry           lastEntry entry Fail{} = entry@@ -207,15 +238,30 @@ -- Schedule a new log entry. This call does not block -- The given IO action runs once the object is durable. The IO action -- blocks the serialization of events so it should be swift.-pushEntry :: Binary object => FileLog object -> object -> IO () -> IO ()-pushEntry log object finally+pushEntry :: SafeCopy object => FileLog object -> object -> IO () -> IO ()+pushEntry fLog object finally     = atomically $-      do tid <- readTVar (logNextEntryId log)-         writeTVar (logNextEntryId log) (tid+1)-         (entries, actions) <- readTVar (logQueue log)-         writeTVar (logQueue log) ( encoded : entries, finally : actions )-    where encoded = encode object+      do tid <- readTVar (logNextEntryId fLog)+         writeTVar (logNextEntryId fLog) (tid+1)+         (entries, actions) <- readTVar (logQueue fLog)+         writeTVar (logQueue fLog) ( encoded : entries, finally : actions )+    where encoded = Lazy.fromChunks [ Strict.copy $ Put.runPut (safePut object) ] +-- The given IO action is executed once all previous entries are durable.+pushAction :: FileLog object -> IO () -> IO ()+pushAction fLog finally+    = atomically $+      do (entries, actions) <- readTVar (logQueue fLog)+         writeTVar (logQueue fLog) (entries, finally : actions)+ askCurrentEntryId :: FileLog object -> IO EntryId-askCurrentEntryId log-    = atomically $ readTVar (logNextEntryId log)+askCurrentEntryId fLog+    = atomically $ readTVar (logNextEntryId fLog)+++-- FIXME: Check for unused input.+decode' :: SafeCopy object => Lazy.ByteString -> object+decode' inp+    = case Get.runGetLazy safeGet inp of+        Left msg  -> error msg+        Right val -> val
src/Data/Acid/TemplateHaskell.hs view
@@ -9,7 +9,7 @@ import Data.Acid.Core import Data.Acid.Local -import Data.Binary+import Data.SafeCopy import Data.Typeable import Data.Char import Control.Applicative@@ -52,12 +52,12 @@                  _ -> error "Unsupported state type. Only 'data' and 'newtype' are supported."            _ -> error "Given state is not a type." -makeEvent :: Name -> Name -> Q [Dec]-makeEvent eventName stateName+makeEvent :: Name -> Q [Dec]+makeEvent eventName     = do eventInfo <- reify eventName          eventType <- getEventType eventName          d <- makeEventDataType eventName eventType-         b <- makeBinaryInstance eventName eventType+         b <- makeSafeCopyInstance eventName eventType          i <- makeMethodInstance eventName eventType          e <- makeEventInstance eventName eventType          return [d,b,i,e]@@ -70,13 +70,13 @@              -> return eventType            _ -> error $ "Events must be functions: " ++ show eventName ---instance (Binary key, Typeable key, Binary val, Typeable val) => IsAcidic State where+--instance (SafeCopy key, Typeable key, SafeCopy val, Typeable val) => IsAcidic State where --  acidEvents = [ UpdateEven (\(MyUpdateEvent arg1 arg2 -> myUpdateEvent arg1 arg2) ] makeIsAcidic eventNames stateName tyvars constructors     = do types <- mapM getEventType eventNames-         let preds = [ ''Binary, ''Typeable ]+         let preds = [ ''SafeCopy, ''Typeable ]              ty = appT (conT ''IsAcidic) stateType-             handlers = map (uncurry makeEventHandler) (zip eventNames types)+             handlers = zipWith makeEventHandler eventNames types          instanceD (mkCxtFromTyVars preds tyvars []) ty                    [ valD (varP 'acidEvents) (normalB (listE handlers)) [] ]     where stateType = foldl appT (conT stateName) [ varT var | PlainTV var <- tyvars ]@@ -88,7 +88,7 @@          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@@ -98,32 +98,33 @@ 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+    where (tyvars, cxt, args, _stateType, _resultType, _isUpdate) = analyseType eventName eventType           eventStructName = mkName (structName (nameBase eventName))           structName [] = []           structName (x:xs) = toUpper x : xs --- instance (Binary key, Binary val) => Binary (MyUpdateEvent key val) where+-- instance (SafeCopy key, SafeCopy val) => SafeCopy (MyUpdateEvent key val) where --    put (MyUpdateEvent a b) = do put a; put b --    get = MyUpdateEvent <$> get <*> get-makeBinaryInstance eventName eventType-    = do let preds = [ ''Binary ]-             ty = AppT (ConT ''Binary) (foldl AppT (ConT eventStructName) [ VarT tyvar | PlainTV tyvar <- tyvars ])+makeSafeCopyInstance eventName eventType+    = do let preds = [ ''SafeCopy ]+             ty = AppT (ConT ''SafeCopy) (foldl AppT (ConT eventStructName) [ VarT tyvar | PlainTV tyvar <- tyvars ])               getBase = appE (varE 'return) (conE eventStructName)-             getArgs = foldl (\a b -> infixE (Just a) (varE '(<*>)) (Just (varE 'get))) getBase args+             getArgs = foldl (\a b -> infixE (Just a) (varE '(<*>)) (Just (varE 'safeGet))) getBase args+             contained val = varE 'contain `appE` val           putVars <- replicateM (length args) (newName "arg")          let putClause = conP eventStructName [varP var | var <- putVars ]-             putExp    = doE $ [ noBindS $ appE (varE 'put) (varE var) | var <- putVars ] +++             putExp    = doE $ [ noBindS $ appE (varE 'safePut) (varE var) | var <- putVars ] ++                                [ noBindS $ appE (varE 'return) (tupE []) ]           instanceD (mkCxtFromTyVars preds tyvars context)                    (return ty)-                   [ funD 'put [clause [putClause] (normalB putExp) []]-                   , valD (varP 'get) (normalB getArgs) []+                   [ funD 'putCopy [clause [putClause] (normalB (contained putExp)) []]+                   , valD (varP 'getCopy) (normalB (contained getArgs)) []                    ]-    where (tyvars, context, args, stateType, resultType, isUpdate) = analyseType eventName eventType+    where (tyvars, context, args, _stateType, _resultType, _isUpdate) = analyseType eventName eventType           eventStructName = mkName (structName (nameBase eventName))           structName [] = []           structName (x:xs) = toUpper x : xs@@ -133,13 +134,13 @@             map return extraContext  {--instance (Binary key, Typeable key-         ,Binary val, Typeable val) => Method (MyUpdateEvent key val) where+instance (SafeCopy key, Typeable key+         ,SafeCopy val, Typeable val) => Method (MyUpdateEvent key val) where   type MethodResult (MyUpdateEvent key val) = Return   type MethodState (MyUpdateEvent key val) = State key val -} makeMethodInstance eventName eventType-    = do let preds = [ ''Binary, ''Typeable ]+    = do let preds = [ ''SafeCopy, ''Typeable ]              ty = AppT (ConT ''Method) (foldl AppT (ConT eventStructName) [ VarT tyvar | PlainTV tyvar <- tyvars ])              structType = foldl appT (conT eventStructName) [ varT tyvar | PlainTV tyvar <- tyvars ]          instanceD (cxt $ [ classP classPred [varT tyvar] | PlainTV tyvar <- tyvars, classPred <- preds ] ++ map return context)@@ -147,22 +148,21 @@                    [ tySynInstD ''MethodResult [structType] (return resultType)                    , tySynInstD ''MethodState  [structType] (return stateType)                    ]-    where (tyvars, context, args, stateType, resultType, isUpdate) = analyseType eventName eventType+    where (tyvars, context, _args, stateType, resultType, _isUpdate) = analyseType eventName eventType           eventStructName = mkName (structName (nameBase eventName))           structName [] = []           structName (x:xs) = toUpper x : xs ---instance (Binary key, Typeable key---         ,Binary val, Typeable val) => UpdateEvent (MyUpdateEvent key val)+--instance (SafeCopy key, Typeable key+--         ,SafeCopy val, Typeable val) => UpdateEvent (MyUpdateEvent key val) makeEventInstance eventName eventType-    = do let preds = [ ''Binary, ''Typeable ]+    = do let preds = [ ''SafeCopy, ''Typeable ]              eventClass = if isUpdate then ''UpdateEvent else ''QueryEvent              ty = AppT (ConT eventClass) (foldl AppT (ConT eventStructName) [ VarT tyvar | PlainTV tyvar <- tyvars ])-             structType = foldl appT (conT eventStructName) [ varT tyvar | PlainTV tyvar <- tyvars ]          instanceD (cxt $ [ classP classPred [varT tyvar] | PlainTV tyvar <- tyvars, classPred <- preds ] ++ map return context)                    (return ty)                    []-    where (tyvars, context, args, stateType, resultType, isUpdate) = analyseType eventName eventType+    where (tyvars, context, _args, _stateType, _resultType, isUpdate) = analyseType eventName eventType           eventStructName = mkName (structName (nameBase eventName))           structName [] = []           structName (x:xs) = toUpper x : xs@@ -172,9 +172,9 @@ analyseType :: Name -> Type -> ([TyVarBndr], Cxt, [Type], Type, Type, Bool) analyseType eventName t     = let (tyvars, cxt, t') = case t of-                                ForallT binds [] t' -> +                                ForallT binds [] t' ->                                   (binds, [], t')-                                ForallT binds cxt t' -> +                                ForallT binds cxt t' ->                                   error $ "Context restrictions on events aren't supported yet: " ++ show eventName                                 _ -> ([], [], t)           args = getArgs t'@@ -193,6 +193,6 @@  makeAcidic' :: [Name] -> Name -> [TyVarBndr] -> [Con] -> Q [Dec] makeAcidic' eventNames stateName tyvars constructors-    = do events <- sequence [ makeEvent eventName stateName | eventName <- eventNames ]+    = do events <- sequence [ makeEvent eventName | eventName <- eventNames ]          acidic <- makeIsAcidic eventNames stateName tyvars constructors          return $ acidic : concat events