on master help sign in
implement crash safe transaction commiting via fdatasync and transaction directory fsync

-requires ordered metadata journaling file system

commit57142beca1eaa9ca7b1f8487c2bafaf06a3944a3 authorAgentM <agentm@themactionfaction.com> dateMon 26 Oct 2015, 22:24 (11 years ago) parentec1003a
9 files changed+179−67
ProjectM36/Base.hs+2−2 ProjectM36/Client.hs+23−12 ProjectM36/DirectoryFsync.c+44 ProjectM36/Persist.hs+47 ProjectM36/Transaction/Persist.hs+22−21 ProjectM36/TransactionGraph/Persist.hs+19−19 TutorialD/tutd.hs+12−10 project-m36.cabal+6 test/TransactionGraph/Persist.hs+4−3
ProjectM36/Base.hs+2−2
@@ -353,8 +353,8 @@ instance Binary AttributeNames
353353
354354--used for global dbms configuration- per-transaction flag is elsewhere
355355data PersistenceStrategy = NoPersistence | --no filesystem persistence/memory-only database
356− MinimalPersistence FilePath --fsync off
357− --CrashSafePersistence FilePath --full fsync- not yet implemented
356+ MinimalPersistence FilePath | --fsync off
357+ CrashSafePersistence FilePath --full fsync- not yet implemented
358358 deriving (Show, Read)
359359
360360
ProjectM36/Client.hs+23−12
@@ -33,6 +33,7 @@ import ProjectM36.RelationalExpression
3333import ProjectM36.TransactionGraph
3434import ProjectM36.TransactionGraph.Persist
3535import ProjectM36.Attribute
36+import ProjectM36.Persist (DiskSync(..))
3637import Data.UUID.V4 (nextRandom)
3738import Control.Concurrent.STM
3839import Data.Word
@@ -65,17 +66,26 @@ connectProjectM36 (InProcessConnectionInfo strat) = do
6566 NoPersistence -> do
6667 tvar <- newTVarIO (freshDiscon, freshGraph)
6768 return $ Right $ InProcessConnection strat tvar
68− MinimalPersistence dbdir -> do
69− err <- setupDatabaseDir dbdir freshGraph
70− case err of
71− Just err' -> return $ Left (SetupDatabaseDirectoryError err')
72− Nothing -> do
73− graph <- transactionGraphLoad dbdir emptyTransactionGraph
74− case graph of
75− Left err' -> return $ Left (SetupDatabaseDirectoryError err')
76− Right graph' -> do
77− tvar <- newTVarIO (freshDiscon, graph')
78− return $ Right $ InProcessConnection strat tvar
69+ MinimalPersistence dbdir -> connectPersistentProjectM36 strat NoDiskSync dbdir freshDiscon freshGraph
70+ CrashSafePersistence dbdir -> connectPersistentProjectM36 strat FsyncDiskSync dbdir freshDiscon freshGraph
71+
72+connectPersistentProjectM36 :: PersistenceStrategy ->
73+ DiskSync ->
74+ FilePath ->
75+ DisconnectedTransaction ->
76+ TransactionGraph ->
77+ IO (Either ConnectionError Connection)
78+connectPersistentProjectM36 strat sync dbdir freshDiscon freshGraph = do
79+ err <- setupDatabaseDir sync dbdir freshGraph
80+ case err of
81+ Just err' -> return $ Left (SetupDatabaseDirectoryError err')
82+ Nothing -> do
83+ graph <- transactionGraphLoad dbdir emptyTransactionGraph
84+ case graph of
85+ Left err' -> return $ Left (SetupDatabaseDirectoryError err')
86+ Right graph' -> do
87+ tvar <- newTVarIO (freshDiscon, graph')
88+ return $ Right $ InProcessConnection strat tvar
7989
8090disconnectedTransaction :: Connection -> IO (DisconnectedTransaction)
8191disconnectedTransaction (InProcessConnection _ tvar) = liftM fst (readTVarIO tvar)
@@ -127,4 +137,5 @@ rollback conn = executeGraphExpr conn Rollback
127137
128138processPersistence :: PersistenceStrategy -> TransactionGraph -> IO ()
129139processPersistence NoPersistence _ = return ()
130−processPersistence (MinimalPersistence dbdir) graph = transactionGraphPersist dbdir graph
140+processPersistence (MinimalPersistence dbdir) graph = transactionGraphPersist NoDiskSync dbdir graph
141+processPersistence (CrashSafePersistence dbdir) graph = transactionGraphPersist FsyncDiskSync dbdir graph
ProjectM36/DirectoryFsync.cnew file+44
@@ -0,0 +1,44 @@
1+#include <sys/types.h>
2+#include <sys/stat.h>
3+#include <unistd.h>
4+#include <strings.h>
5+#include <errno.h>
6+#include <fcntl.h>
7+
8+#ifndef _DIRECTORY_FSYNC_
9+#define _DIRECTORY_FSYNC_
10+/* open a directory for reading and fsync the resultant fd */
11+int cDirectoryFsync(char *path)
12+{
13+ int fd = 0;
14+ int ret = 0;
15+ struct stat fdstat = {0};
16+
17+ fd = open(path, O_RDONLY);
18+ if(fd < 0)
19+ {
20+ return errno;
21+ }
22+
23+ /* ensure that opened fd is a directory fd. Otherwise, fsync will fail (on a read-only file descriptor */
24+ ret = fstat(fd,&fdstat);
25+ if(ret < 0)
26+ {
27+ return errno;
28+ }
29+
30+ if(!S_ISDIR(fdstat.st_mode))
31+ {
32+ return ENOTDIR;
33+ }
34+
35+ /* execute the fsync */
36+ ret = fsync(fd);
37+ if(ret < 0)
38+ {
39+ return errno;
40+ }
41+
42+ return 0;
43+}
44+#endif
ProjectM36/Persist.hsnew file+47
@@ -0,0 +1,47 @@
1+{-# LANGUAGE ForeignFunctionInterface #-}
2+--this module is related to persisting Project:M36 structures to disk and not related to the persistent library
3+module ProjectM36.Persist (writeFileSync,
4+ writeBSFileSync,
5+ renameSync,
6+ DiskSync(..)) where
7+--import ProjectM36.DirectoryFsync (directoryFsync)
8+import System.Posix.Files (rename)
9+import System.Posix.Unistd (fileSynchroniseDataOnly)
10+import System.Posix.IO (handleToFd)
11+import System.IO (withFile, IOMode(WriteMode), hPutStr, Handle)
12+import qualified Data.ByteString.Lazy as BS
13+import Foreign.C
14+
15+foreign import ccall unsafe "cDirectoryFsync" cHSDirectoryFsync :: CString -> IO (CInt)
16+
17+data DiskSync = NoDiskSync | FsyncDiskSync
18+
19+writeFileSync :: DiskSync -> FilePath -> String -> IO()
20+writeFileSync sync path strOut = withFile path WriteMode handler
21+ where
22+ handler handle = do
23+ hPutStr handle strOut
24+ syncHandle sync handle
25+
26+renameSync :: DiskSync -> FilePath -> FilePath -> IO ()
27+renameSync sync srcPath dstPath = do
28+ rename srcPath dstPath
29+ syncDirectory sync dstPath
30+
31+syncHandle :: DiskSync -> Handle -> IO ()
32+syncHandle FsyncDiskSync handle = handleToFd handle >>= fileSynchroniseDataOnly
33+syncHandle NoDiskSync _ = pure ()
34+
35+syncDirectory :: DiskSync -> FilePath -> IO ()
36+syncDirectory FsyncDiskSync path = directoryFsync path
37+syncDirectory NoDiskSync _ = pure ()
38+
39+writeBSFileSync :: DiskSync -> FilePath -> BS.ByteString -> IO ()
40+writeBSFileSync sync path bstring = do
41+ withFile path WriteMode $ \handle -> do
42+ BS.hPut handle bstring
43+ syncHandle sync handle
44+
45+directoryFsync :: FilePath -> IO ()
46+directoryFsync path = throwErrnoIfMinus1Retry_ "directoryFsync" (withCString path $ \cpath -> cHSDirectoryFsync cpath)
47+
ProjectM36/Transaction/Persist.hs+22−21
@@ -1,6 +1,7 @@
11module ProjectM36.Transaction.Persist where
22import ProjectM36.Base
33import ProjectM36.Error
4+import ProjectM36.Persist (writeBSFileSync, DiskSync, renameSync)
45import qualified Data.Map as M
56import qualified Data.HashSet as HS
67import qualified Data.UUID as U
@@ -53,8 +54,8 @@ readTransaction dbdir transUUID = do
5354
5455 return $ Right $ Transaction transUUID transInfo newContext
5556
56−writeTransaction :: FilePath -> Transaction -> IO ()
57−writeTransaction dbdir trans = do
57+writeTransaction :: DiskSync -> FilePath -> Transaction -> IO ()
58+writeTransaction sync dbdir trans = do
5859 let tempTransDir = tempTransactionDir dbdir (transactionUUID trans)
5960 finalTransDir = transactionDir dbdir (transactionUUID trans)
6061 context = transactionContext trans
@@ -62,22 +63,22 @@ writeTransaction dbdir trans = do
6263 if not transDirExists then do
6364 --create sub directories
6465 mapM_ createDirectory [tempTransDir, relvarsDir tempTransDir, incDepsDir tempTransDir, atomFuncsDir tempTransDir]
65− writeRelVars tempTransDir (relationVariables context)
66− writeIncDeps tempTransDir (inclusionDependencies context)
67− writeAtomFuncs tempTransDir (atomFunctions context)
66+ writeRelVars sync tempTransDir (relationVariables context)
67+ writeIncDeps sync tempTransDir (inclusionDependencies context)
68+ writeAtomFuncs sync tempTransDir (atomFunctions context)
6869 BS.writeFile (transactionInfoPath tempTransDir) (B.encode $ transactionInfo trans)
6970 --move the temp directory to final location
70− renameDirectory tempTransDir finalTransDir
71+ renameSync sync tempTransDir finalTransDir
7172 else
7273 return ()
7374
74−writeRelVar :: FilePath -> (RelVarName, Relation) -> IO ()
75−writeRelVar transDir (relvarName, rel) = do
75+writeRelVar :: DiskSync -> FilePath -> (RelVarName, Relation) -> IO ()
76+writeRelVar sync transDir (relvarName, rel) = do
7677 let relvarPath = relvarsDir transDir </> T.unpack relvarName
77− BS.writeFile relvarPath (B.encode rel)
78+ writeBSFileSync sync relvarPath (B.encode rel)
7879
79−writeRelVars :: FilePath -> (M.Map RelVarName Relation) -> IO ()
80−writeRelVars transDir relvars = mapM_ (writeRelVar transDir) $ M.toList relvars
80+writeRelVars :: DiskSync -> FilePath -> (M.Map RelVarName Relation) -> IO ()
81+writeRelVars sync transDir relvars = mapM_ (writeRelVar sync transDir) $ M.toList relvars
8182
8283readRelVars :: FilePath -> IO (M.Map RelVarName Relation)
8384readRelVars transDir = do
@@ -88,8 +89,8 @@ readRelVars transDir = do
8889 return (T.pack name, rel)) relvarNames
8990 return $ M.fromList relvars
9091
91−writeAtomFuncs :: FilePath -> AtomFunctions -> IO ()
92−writeAtomFuncs transDir funcs = mapM_ (writeAtomFunc transDir) $ HS.toList funcs
92+writeAtomFuncs :: DiskSync -> FilePath -> AtomFunctions -> IO ()
93+writeAtomFuncs sync transDir funcs = mapM_ (writeAtomFunc sync transDir) $ HS.toList funcs
9394
9495--all the atom functions are in one file (???)
9596readAtomFuncs :: FilePath -> IO (AtomFunctions)
@@ -99,10 +100,10 @@ readAtomFuncs transDir = do
99100 return $ HS.fromList funcs
100101
101102--to write the atom functions, we really some bytecode to write (GHCi bytecode?)
102−writeAtomFunc :: FilePath -> AtomFunction -> IO ()
103−writeAtomFunc transDir func = do
103+writeAtomFunc :: DiskSync -> FilePath -> AtomFunction -> IO ()
104+writeAtomFunc sync transDir func = do
104105 let atomFuncPath = atomFuncsDir transDir </> T.unpack (atomFuncName func)
105− BS.writeFile atomFuncPath BS.empty
106+ writeBSFileSync sync atomFuncPath BS.empty
106107
107108readAtomFunc :: FilePath -> AtomFunctionName -> IO (AtomFunction)
108109readAtomFunc transDir funcName = do
@@ -110,12 +111,12 @@ readAtomFunc transDir funcName = do
110111 _ <- BS.readFile atomFuncPath
111112 return undefined
112113
113−writeIncDep :: FilePath -> (IncDepName, InclusionDependency) -> IO ()
114−writeIncDep transDir (incDepName, incDep) = do
115− BS.writeFile (incDepsDir transDir </> T.unpack incDepName) $ B.encode incDep
114+writeIncDep :: DiskSync -> FilePath -> (IncDepName, InclusionDependency) -> IO ()
115+writeIncDep sync transDir (incDepName, incDep) = do
116+ writeBSFileSync sync (incDepsDir transDir </> T.unpack incDepName) $ B.encode incDep
116117
117−writeIncDeps :: FilePath -> M.Map IncDepName InclusionDependency -> IO ()
118−writeIncDeps transDir incdeps = mapM_ (writeIncDep transDir) $ M.toList incdeps
118+writeIncDeps :: DiskSync -> FilePath -> M.Map IncDepName InclusionDependency -> IO ()
119+writeIncDeps sync transDir incdeps = mapM_ (writeIncDep sync transDir) $ M.toList incdeps
119120
120121readIncDep :: FilePath -> IncDepName -> IO (IncDepName, InclusionDependency)
121122readIncDep transDir incdepName = do
ProjectM36/TransactionGraph/Persist.hs+19−19
@@ -5,8 +5,8 @@ import ProjectM36.TransactionGraph
55import ProjectM36.Transaction
66import ProjectM36.Transaction.Persist
77import ProjectM36.Base
8+import ProjectM36.Persist (writeFileSync, renameSync, DiskSync)
89import System.Directory
9−import System.Posix.Files
1010import System.FilePath
1111import System.IO.Temp
1212import Data.List
@@ -18,11 +18,11 @@ import Control.Monad (foldM)
1818import Data.Either (isRight)
1919import Data.Maybe (catMaybes)
2020
21−
2221{-
2322The "m36v1" file at the top-level of the destination directory contains the the transaction graph as a set of UUIDs referencing their parents (1 or more)
2423Each Transaction is written to it own directory named by its UUID. Partially written transactions UUIDs are prefixed with a "." to indicate incompleteness in the graph.
2524
25+Persistence requires a POSIX-compliant, journaled-metadata filesystem.
2626-}
2727
2828transactionLogPath :: FilePath -> FilePath
@@ -36,14 +36,14 @@ verify that the database directory is valid or bootstrap it
3636-note: checking for the existence of every transaction may be prohibitively expensive
3737-}
3838
39−setupDatabaseDir :: FilePath -> TransactionGraph -> IO (Maybe PersistenceError)
40−setupDatabaseDir dbdir bootstrapGraph = do
39+setupDatabaseDir :: DiskSync -> FilePath -> TransactionGraph -> IO (Maybe PersistenceError)
40+setupDatabaseDir sync dbdir bootstrapGraph = do
4141 dbdirExists <- doesDirectoryExist dbdir
4242 m36exists <- doesFileExist (transactionLogPath dbdir)
4343 if dbdirExists && m36exists then
4444 return Nothing
4545 else if not m36exists then do
46− bootstrapDatabaseDir dbdir bootstrapGraph
46+ bootstrapDatabaseDir sync dbdir bootstrapGraph
4747 return Nothing
4848 else
4949 return $ Just (InvalidDirectoryError dbdir)
@@ -51,10 +51,10 @@ setupDatabaseDir dbdir bootstrapGraph = do
5151{-
5252initialize a database directory with the graph from which to bootstrap
5353-}
54−bootstrapDatabaseDir :: FilePath -> TransactionGraph -> IO ()
55−bootstrapDatabaseDir dbdir bootstrapGraph = do
54+bootstrapDatabaseDir :: DiskSync -> FilePath -> TransactionGraph -> IO ()
55+bootstrapDatabaseDir sync dbdir bootstrapGraph = do
5656 createDirectory dbdir
57− transactionGraphPersist dbdir bootstrapGraph
57+ transactionGraphPersist sync dbdir bootstrapGraph
5858 putStrLn "Bootstrapped DB."
5959
6060
@@ -65,26 +65,26 @@ incrementally updates an existing database directory
6565-assume that all non-head transactions have already been written because this is an incremental (and concurrent!) write method
6666--store the head names with a symlink to the transaction under "heads"
6767-}
68−transactionGraphPersist :: FilePath -> TransactionGraph -> IO ()
69−transactionGraphPersist destDirectory graph = do
70− mapM_ (writeTransaction destDirectory) $ M.elems (transactionHeadsForGraph graph)
71− writeGraphUUIDFile destDirectory graph
72− transactionGraphHeadsPersist destDirectory graph
68+transactionGraphPersist :: DiskSync -> FilePath -> TransactionGraph -> IO ()
69+transactionGraphPersist sync destDirectory graph = do
70+ mapM_ (writeTransaction sync destDirectory) $ M.elems (transactionHeadsForGraph graph)
71+ writeGraphUUIDFile sync destDirectory graph
72+ transactionGraphHeadsPersist sync destDirectory graph
7373 return ()
7474
7575{-
7676write graph heads to a file which can be atomically swapped
7777-}
7878--writing the heads in a directory is a synchronization nightmare, so just write the binary to a file and swap atomically
79−transactionGraphHeadsPersist :: FilePath -> TransactionGraph -> IO ()
80−transactionGraphHeadsPersist dbdir graph = do
79+transactionGraphHeadsPersist :: DiskSync -> FilePath -> TransactionGraph -> IO ()
80+transactionGraphHeadsPersist sync dbdir graph = do
8181 let headFileStr :: (HeadName, Transaction) -> String
8282 headFileStr (headName, trans) = T.unpack headName ++ " " ++ U.toString (transactionUUID trans)
8383 withTempDirectory dbdir ".heads.tmp" $ \tempHeadsDir -> do
8484 let tempHeadsPath = tempHeadsDir </> "heads"
8585 headsStrLines = map headFileStr $ M.toList (transactionHeadsForGraph graph)
86− writeFile tempHeadsPath $ intercalate "\n" headsStrLines
87− rename tempHeadsPath (headsPath dbdir)
86+ writeFileSync sync tempHeadsPath $ intercalate "\n" headsStrLines
87+ renameSync sync tempHeadsPath (headsPath dbdir)
8888
8989transactionGraphHeadsLoad :: FilePath -> IO [(HeadName,U.UUID)]
9090transactionGraphHeadsLoad dbdir = do
@@ -133,8 +133,8 @@ readTransactionIfNecessary dbdir transUUID graphIn = do
133133 Left err -> return $ Left err
134134 Right trans' -> return $ Right $ TransactionGraph (transactionHeadsForGraph graphIn) (S.insert trans' (transactionsForGraph graphIn))
135135
136−writeGraphUUIDFile :: FilePath -> TransactionGraph -> IO ()
137−writeGraphUUIDFile destDirectory (TransactionGraph _ transSet) = writeFile graphFile uuidInfo
136+writeGraphUUIDFile :: DiskSync -> FilePath -> TransactionGraph -> IO ()
137+writeGraphUUIDFile sync destDirectory (TransactionGraph _ transSet) = writeFileSync sync graphFile uuidInfo
138138 where
139139 graphFile = destDirectory </> "m36v1"
140140 uuidInfo = intercalate "\n" graphLines
TutorialD/tutd.hs+12−10
@@ -17,17 +17,19 @@ parseArgs :: Parser InterpreterConfig
1717parseArgs = InterpreterConfig <$> parsePersistenceStrategy
1818
1919parsePersistenceStrategy :: Parser PersistenceStrategy
20−parsePersistenceStrategy = do
21− MinimalPersistence <$> strOption (short 'd' <>
22− long "database-directory" <>
23− metavar "DIRECTORY" <>
24− showDefaultWith show
25− )
26− <|> pure NoPersistence
20+parsePersistenceStrategy = CrashSafePersistence <$> (dbdirOpt <* fsyncOpt) <|>
21+ MinimalPersistence <$> dbdirOpt <|>
22+ pure NoPersistence
23+ where
24+ dbdirOpt = strOption (short 'd' <>
25+ long "database-directory" <>
26+ metavar "DIRECTORY" <>
27+ showDefaultWith show
28+ )
29+ fsyncOpt = switch (short 'f' <>
30+ long "fsync" <>
31+ help "Fsync all new transactions.")
2732
28−
29−
30−
3133opts :: ParserInfo InterpreterConfig
3234opts = info parseArgs idm
3335
project-m36.cabal+6
@@ -70,8 +70,10 @@ Library
7070 ProjectM36.Relation.Show.Gnuplot,
7171 ProjectM36.Relation.Parse.CSV,
7272 ProjectM36.Client,
73+ ProjectM36.Persist,
7374 Database.Persist.ProjectM36
7475 GHC-Options: -Wall -rtsopts
76+ C-sources: ProjectM36/DirectoryFsync.c
7577
7678Executable tutd
7779 Build-Depends: base
@@ -88,6 +90,7 @@ Executable tutd
8890 TutorialD.Interpreter.DataTypes.DateTime
8991 main-is: TutorialD/tutd.hs
9092 GHC-Options: -Wall
93+ C-sources: ProjectM36/DirectoryFsync.c
9194
9295Executable bigrel
9396 Build-Depends: base,deepseq,optparse-applicative
@@ -105,6 +108,7 @@ Test-Suite test-tutoriald
105108 main-is: test/TutorialD/Interpreter.hs
106109 Build-Depends: base,HUnit,Cabal
107110 GHC-Options: -Wall
111+ C-sources: ProjectM36/DirectoryFsync.c
108112
109113Test-Suite test-relation
110114 type: exitcode-stdio-1.0
@@ -123,6 +127,7 @@ Test-Suite test-transactiongraph-persist
123127 main-is: test/TransactionGraph/Persist.hs
124128 Build-Depends: base,HUnit,Cabal
125129 GHC-Options: -Wall
130+ C-sources: ProjectM36/DirectoryFsync.c
126131
127132Test-Suite test-relation-import-csv
128133 type: exitcode-stdio-1.0
@@ -147,6 +152,7 @@ Test-Suite test-persistent-driver
147152 main-is: test/persistent-driver/test.hs
148153 Build-Depends: base,HUnit,Cabal,template-haskell
149154 GHC-Options: -Wall
155+ C-sources: ProjectM36/DirectoryFsync.c
150156
151157benchmark bench
152158 type: exitcode-stdio-1.0
test/TransactionGraph/Persist.hs+4−3
@@ -1,6 +1,7 @@
11{-# LANGUAGE OverloadedStrings,LambdaCase #-}
22import Test.HUnit
33import ProjectM36.Base
4+import ProjectM36.Persist (DiskSync(NoDiskSync))
45import ProjectM36.TransactionGraph.Persist
56import ProjectM36.TransactionGraph
67import ProjectM36.RelationalExpression
@@ -25,7 +26,7 @@ testBootstrapDB :: Test
2526testBootstrapDB = TestCase $ withSystemTempDirectory "m36testdb" $ \tempdir -> do
2627 let dbdir = tempdir </> "dbdir"
2728 freshUUID <- nextRandom
28− _ <- bootstrapDatabaseDir dbdir (bootstrapTransactionGraph freshUUID dateExamples)
29+ _ <- bootstrapDatabaseDir NoDiskSync dbdir (bootstrapTransactionGraph freshUUID dateExamples)
2930 loadedGraph <- transactionGraphLoad dbdir emptyTransactionGraph
3031 assertBool "transactionGraphLoad" $ isRight loadedGraph
3132
@@ -35,7 +36,7 @@ testDBSimplePersistence = TestCase $ withSystemTempDirectory "m36testdb" $ \temp
3536 let dbdir = tempdir </> "dbdir"
3637 freshUUID <- nextRandom
3738 let graph = bootstrapTransactionGraph freshUUID dateExamples
38− bootstrapDatabaseDir dbdir graph
39+ bootstrapDatabaseDir NoDiskSync dbdir graph
3940 case transactionForHead "master" graph of
4041 Nothing -> assertFailure "Failed to retrieve head transaction for master branch."
4142 Just headTrans -> do
@@ -50,7 +51,7 @@ testDBSimplePersistence = TestCase $ withSystemTempDirectory "m36testdb" $ \temp
5051 Left err -> assertFailure (show err)
5152 Right (_, graph') -> do
5253 --persist the new graph
53− transactionGraphPersist dbdir graph'
54+ transactionGraphPersist NoDiskSync dbdir graph'
5455 --reload the graph from the filesystem and confirm that the transaction is present
5556 graphErr <- transactionGraphLoad dbdir emptyTransactionGraph
5657 case graphErr of