on master help sign in
WIP per-request timeout
commitdb9742fc1d5478026c2fbf955c5f356236ec7c66 authorAgentM <agentm@themactionfaction.com> dateFri 20 Jan 2017, 16:01 (10 years ago) parent1e82c5c
9 files changed+119−53
project-m36.cabal+2−2 src/bin/ProjectM36/Server/WebSocket.hs+6−2 src/lib/ProjectM36/Client.hs+9−2 src/lib/ProjectM36/Server.hs+28−15 src/lib/ProjectM36/Server/Config.hs+5−2 src/lib/ProjectM36/Server/EntryPoints.hs+7−1 src/lib/ProjectM36/Server/ParseArgs.hs+1−1 src/lib/ProjectM36/Server/RemoteCallTypes.hs+2−3 test/Server/Main.hs+59−25
project-m36.cabal+2−2
@@ -366,7 +366,7 @@ Test-Suite test-server
366366 Default-Extensions: OverloadedStrings
367367 type: exitcode-stdio-1.0
368368 main-is: test/Server/Main.hs
369− Build-Depends: base, HUnit, Cabal, containers, hashable, unordered-containers, mtl, vector, vector-binary-instances, time, hashable-time, bytestring, network-transport-tcp, uuid, stm, deepseq, deepseq-generics, binary, parallel, cassava, attoparsec, gnuplot, directory, temporary, haskeline, megaparsec, text, base64-bytestring, data-interval, filepath, transformers, stm-containers, list-t, project-m36 == 0.1
369+ Build-Depends: base, HUnit, Cabal, containers, hashable, unordered-containers, mtl, vector, vector-binary-instances, time, hashable-time, bytestring, network-transport-tcp, uuid, stm, deepseq, deepseq-generics, binary, parallel, cassava, attoparsec, gnuplot, directory, temporary, haskeline, megaparsec, text, base64-bytestring, data-interval, filepath, transformers, stm-containers, list-t, project-m36 == 0.1, network-transport
370370 HS-Source-Dirs: ., ./src/bin
371371 GHC-Options: -Wall
372372
@@ -399,7 +399,7 @@ Test-Suite test-scripts
399399
400400Executable project-m36-websocket-server
401401 Default-Language: Haskell2010
402− Build-Depends: base, aeson, path-pieces, either, conduit, http-api-data, template-haskell, persistent-template, websockets, aeson, uuid-aeson, optparse-applicative, project-m36 == 0.1, containers, bytestring, text, vector, uuid, megaparsec, haskeline, mtl, directory, base64-bytestring, random, MonadRandom
402+ Build-Depends: base, aeson, path-pieces, either, conduit, http-api-data, template-haskell, persistent-template, websockets, aeson, uuid-aeson, optparse-applicative, project-m36 == 0.1, containers, bytestring, text, vector, uuid, megaparsec, haskeline, mtl, directory, base64-bytestring, random, MonadRandom, time
403403 Main-Is: ProjectM36/Server/WebSocket/websocket-server.hs
404404 Other-Modules: ProjectM36.Client.Json, ProjectM36.Server.RemoteCallTypes.Json, ProjectM36.Server.WebSocket, TutorialD.Interpreter, TutorialD.Interpreter.Base, TutorialD.Interpreter.DatabaseContextExpr, TutorialD.Interpreter.DatabaseContextIOOperator, TutorialD.Interpreter.Export.Base, TutorialD.Interpreter.Export.CSV, TutorialD.Interpreter.Import.Base, TutorialD.Interpreter.Import.BasicExamples, TutorialD.Interpreter.Import.CSV, TutorialD.Interpreter.Import.TutorialD, TutorialD.Interpreter.InformationOperator, TutorialD.Interpreter.RODatabaseContextOperator, TutorialD.Interpreter.RelationalExpr, TutorialD.Interpreter.TransactionGraphOperator, TutorialD.Interpreter.Types, TutorialD.Interpreter.TransGraphRelationalOperator
405405 GHC-Options: -Wall
src/bin/ProjectM36/Server/WebSocket.hs+6−2
@@ -47,8 +47,12 @@ websocketProxyServer port host pending = do
4747 case parseTutorialD tutdString of
4848 Left err -> handleOpResult conn dbconn (DisplayErrorResult ("parse error: " `T.append` T.pack (show err)))
4949 Right parsed -> do
50− result <- evalTutorialD sessionId dbconn SafeEvaluation parsed
51− handleOpResult conn dbconn result
50+ let timeoutFilter = \exc -> if exc == RequestTimeoutException
51+ then Just exc
52+ else Nothing
53+ catchJust timeoutFilter (do
54+ result <- evalTutorialD sessionId dbconn SafeEvaluation parsed
55+ handleOpResult conn dbconn result) (\_ -> handleOpResult conn dbconn (DisplayErrorResult "Request Timed Out."))
5256 _ -> unexpectedMsg
5357 pure ()
5458
src/lib/ProjectM36/Client.hs+9−2
@@ -42,6 +42,7 @@ module ProjectM36.Client
4242 createSessionAtHead,
4343 closeSession,
4444 addClientNode,
45+ callTestTimeout_,
4546 RelationCardinality(..),
4647 TransactionGraphOperator(..),
4748 TransGraphRelationalExpr,
@@ -105,6 +106,8 @@ import GHC.Generics (Generic)
105106import Control.DeepSeq (force)
106107import System.IO
107108
109+import Debug.Trace
110+
108111type Hostname = String
109112
110113type Port = Word16
@@ -143,7 +146,7 @@ data EvaluatedNotification = EvaluatedNotification {
143146 deriving(Binary, Eq, Show, Generic)
144147
145148createNodeId :: Hostname -> Port -> NodeId
146−createNodeId host port = NodeId $ encodeEndPointAddress host (show port) 0
149+createNodeId host port = NodeId $ encodeEndPointAddress host (show port) 1
147150
148151defaultServerPort :: Port
149152defaultServerPort = 6543
@@ -235,7 +238,7 @@ connectProjectM36 (RemoteProcessConnectionInfo databaseName serverNodeId notific
235238 eLocalNode <- commonLocalNode
236239 notificationListenerPid <- startNotificationListener notificationCallback
237240 let dbName = remoteDBLookupName databaseName
238− --putStrLn $ show serverNodeId ++ " " ++ dbName
241+ putStrLn $ "Connecting to " ++ show serverNodeId ++ " " ++ dbName
239242 case eLocalNode of
240243 Left err -> pure (Left err)
241244 Right localNode -> do
@@ -719,3 +722,7 @@ atomTypesAsRelation sessionId (InProcessConnection _ _ sessions _ _) = do
719722 Right rel -> pure (Right rel)
720723atomTypesAsRelation sessionId conn@(RemoteProcessConnection _ _) = remoteCall conn (RetrieveAtomTypesAsRelation sessionId)
721724
725+--used only for testing- we expect this to throw an exception
726+callTestTimeout_ :: SessionId -> Connection -> IO Bool
727+callTestTimeout_ _ (InProcessConnection _ _ _ _ _) = error "bad testing call"
728+callTestTimeout_ sessionId conn@(RemoteProcessConnection _ _) = remoteCall conn (TestTimeout sessionId)
src/lib/ProjectM36/Server.hs+28−15
@@ -8,6 +8,7 @@ import ProjectM36.Server.Config (ServerConfig(..))
88
99import Control.Monad.IO.Class (liftIO)
1010import Network.Transport.TCP (createTransport, defaultTCPParameters)
11+import Network.Transport (EndPointAddress(..), newEndPoint, address)
1112import Control.Distributed.Process.Node (initRemoteTable, runProcess, newLocalNode)
1213import Control.Distributed.Process.Extras.Time (Delay(..))
1314import Control.Distributed.Process (Process, register, RemoteTable, getSelfPid)
@@ -18,8 +19,8 @@ import System.IO (stderr, hPutStrLn)
1819
1920-- the state should be a mapping of remote connection to the disconnected transaction- the graph should be the same, so discon must be removed from the stm tuple
2021--trying to refactor this for less repetition is very challenging because the return type cannot be polymorphic or the distributed-process call gets confused and drops messages
21−serverDefinition :: Timeout -> ProcessDefinition Connection
22−serverDefinition ti = defaultProcess {
22+serverDefinition :: Bool -> Timeout -> ProcessDefinition Connection
23+serverDefinition testBool ti = defaultProcess {
2324 apiHandlers = [
2425 handleCall (\conn (ExecuteHeadName sessionId) -> handleExecuteHeadName ti sessionId conn),
2526 handleCall (\conn (ExecuteRelationalExpr sessionId expr) -> handleExecuteRelationalExpr ti sessionId conn expr),
@@ -40,17 +41,23 @@ serverDefinition ti = defaultProcess {
4041 handleCall (\conn (RetrieveRelationVariableSummary sessionId) -> handleRetrieveRelationVariableSummary ti sessionId conn),
4142 handleCall (\conn (RetrieveCurrentSchemaName sessionId) -> handleRetrieveCurrentSchemaName ti sessionId conn),
4243 handleCall (\conn (ExecuteSchemaExpr sessionId schemaExpr) -> handleExecuteSchemaExpr ti sessionId conn schemaExpr)
43− ],
44+ ] ++ testModeHandlers,
4445 unhandledMessagePolicy = Log
4546 }
46−
47+ where
48+ testModeHandlers = if not testBool then
49+ []
50+ else
51+ [handleCall (\conn (TestTimeout sessionId) -> handleTestTimeout ti sessionId conn)]
52+
4753
48−initServer :: InitHandler (Connection, DatabaseName, Maybe (MVar Port), Port) Connection
49−initServer (conn, dbname, mPortMVar, portNum) = do
54+initServer :: InitHandler (Connection, DatabaseName, Maybe (MVar EndPointAddress), EndPointAddress) Connection
55+initServer (conn, dbname, mAddressMVar, saddress) = do
5056 registerDB dbname
51− case mPortMVar of
57+ case mAddressMVar of
5258 Nothing -> pure ()
53− Just portMVar -> liftIO $ putMVar portMVar portNum
59+ Just addressMVar -> liftIO $ putMVar addressMVar saddress
60+ --traceShowM ("server started on " ++ show saddress)
5461 pure $ InitOk conn Infinity
5562
5663remoteTable :: RemoteTable
@@ -68,8 +75,8 @@ loggingNotificationCallback :: NotificationCallback
6875loggingNotificationCallback notName evaldNot = hPutStrLn stderr $ "Notification received \"" ++ show notName ++ "\": " ++ show evaldNot
6976
7077-- | A synchronous function to start the project-m36 daemon given an appropriate 'ServerConfig'. Note that this function only returns if the server exits. Returns False if the daemon exited due to an error. If the second argument is not Nothing, the port is put after the server is ready to service the port.
71−launchServer :: ServerConfig -> Maybe (MVar Port) -> IO (Bool)
72−launchServer daemonConfig mPortMVar = do
78+launchServer :: ServerConfig -> Maybe (MVar EndPointAddress) -> IO (Bool)
79+launchServer daemonConfig mAddressMVar = do
7380 econn <- connectProjectM36 (InProcessConnectionInfo (persistenceStrategy daemonConfig) loggingNotificationCallback (ghcPkgPaths daemonConfig))
7481 case econn of
7582 Left err -> do
@@ -82,9 +89,15 @@ launchServer daemonConfig mPortMVar = do
8289 case etransport of
8390 Left err -> error (show err)
8491 Right transport -> do
85− localTCPNode <- newLocalNode transport remoteTable
86− runProcess localTCPNode $ do
87− serve (conn, databaseName daemonConfig, mPortMVar, port) initServer (serverDefinition (perRequestTimeout daemonConfig))
88− liftIO $ putStrLn "serve returned"
89− pure True
92+ eEndpoint <- newEndPoint transport
93+ case eEndpoint of
94+ Left err -> hPutStrLn stderr ("Failed to create transport: " ++ show err) >> pure False
95+ Right endpoint -> do
96+ localTCPNode <- newLocalNode transport remoteTable
97+ runProcess localTCPNode $ do
98+ let testBool = testMode daemonConfig
99+ reqTimeout = perRequestTimeout daemonConfig
100+ serve (conn, databaseName daemonConfig, mAddressMVar, (address endpoint)) initServer (serverDefinition testBool reqTimeout)
101+ liftIO $ putStrLn "serve returned"
102+ pure True
90103
src/lib/ProjectM36/Server/Config.hs+5−2
@@ -6,8 +6,10 @@ data ServerConfig = ServerConfig { persistenceStrategy :: PersistenceStrategy,
66 bindHost :: Hostname,
77 bindPort :: Port,
88 ghcPkgPaths :: [String], -- used for AtomFunction dynamic compilation
9− perRequestTimeout :: Int
9+ perRequestTimeout :: Int,
10+ testMode :: Bool -- used exclusively for automated testing of the server, thus not accessible from the command line
1011 }
12+ deriving (Show)
1113
1214defaultServerConfig :: ServerConfig
1315defaultServerConfig = ServerConfig { persistenceStrategy = NoPersistence,
@@ -15,5 +17,6 @@ defaultServerConfig = ServerConfig { persistenceStrategy = NoPersistence,
1517 bindHost = "127.0.0.1",
1618 bindPort = 6543,
1719 ghcPkgPaths = [],
18− perRequestTimeout = 0
20+ perRequestTimeout = 0,
21+ testMode = False
1922 }
src/lib/ProjectM36/Server/EntryPoints.hs+7−1
@@ -27,7 +27,7 @@ type Reply a = Process (ProcessReply (Either ServerError a) Connection)
2727
2828handleExecuteRelationalExpr :: Timeout -> SessionId -> Connection -> RelationalExpr -> Reply (Either RelationalError Relation)
2929handleExecuteRelationalExpr ti sessionId conn expr = do
30− ret <- timeoutOrDie ti (threadDelay 5000 >> executeRelationalExpr sessionId conn expr)
30+ ret <- timeoutOrDie ti (executeRelationalExpr sessionId conn expr)
3131 reply ret conn
3232
3333handleExecuteDatabaseContextExpr :: Timeout -> SessionId -> Connection -> DatabaseContextExpr -> Reply (Maybe RelationalError)
@@ -119,8 +119,14 @@ handleRetrieveCurrentSchemaName :: Timeout -> SessionId -> Connection -> Reply (
119119handleRetrieveCurrentSchemaName ti sessionId conn = do
120120 ret <- timeoutOrDie ti (currentSchemaName sessionId conn)
121121 reply ret conn
122+
122123handleExecuteSchemaExpr :: Timeout -> SessionId -> Connection -> SchemaExpr -> Reply (Maybe RelationalError)
123124handleExecuteSchemaExpr ti sessionId conn schemaExpr = do
124125 ret <- timeoutOrDie ti (executeSchemaExpr sessionId conn schemaExpr)
125126 reply ret conn
126127
128+handleTestTimeout :: Timeout -> SessionId -> Connection -> Reply Bool
129+handleTestTimeout ti _ conn = do
130+ ret <- timeoutOrDie ti (threadDelay 100000 >> pure True)
131+ reply ret conn
132+
src/lib/ProjectM36/Server/ParseArgs.hs+1−1
@@ -6,7 +6,7 @@ import ProjectM36.Server.Config
66import Data.Monoid
77
88parseArgs :: Parser ServerConfig
9−parseArgs = ServerConfig <$> parsePersistenceStrategy <*> parseDatabaseName <*> parseHostname <*> parsePort <*> many parseGhcPkgPaths <*> parseTimeout
9+parseArgs = ServerConfig <$> parsePersistenceStrategy <*> parseDatabaseName <*> parseHostname <*> parsePort <*> many parseGhcPkgPaths <*> parseTimeout <*> pure False
1010
1111parsePersistenceStrategy :: Parser PersistenceStrategy
1212parsePersistenceStrategy = CrashSafePersistence <$> (dbdirOpt <* fsyncOpt) <|>
src/lib/ProjectM36/Server/RemoteCallTypes.hs+2−3
@@ -53,6 +53,5 @@ data RetrieveRelationVariableSummary = RetrieveRelationVariableSummary SessionId
5353 deriving (Binary, Generic)
5454data RetrieveCurrentSchemaName = RetrieveCurrentSchemaName SessionId
5555 deriving (Binary, Generic)
56−
57−
58−
56+data TestTimeout = TestTimeout SessionId
57+ deriving (Binary, Generic)
test/Server/Main.hs+59−25
@@ -4,6 +4,7 @@ test client/server interaction
44import Test.HUnit
55import ProjectM36.Client
66import qualified ProjectM36.Client as C
7+import ProjectM36.Server.EntryPoints
78import ProjectM36.Server
89import ProjectM36.Server.Config
910import ProjectM36.Relation
@@ -13,42 +14,54 @@ import ProjectM36.Base
1314
1415import System.Exit
1516import Control.Concurrent
17+import Network.Transport (EndPointAddress)
18+import Network.Transport.TCP (encodeEndPointAddress, decodeEndPointAddress)
1619import Data.Either (isRight)
1720import Data.Maybe (isJust)
21+import Control.Exception
22+import Control.Monad.IO.Class
1823
1924testList :: SessionId -> Connection -> MVar () -> Test
20−testList sessionId conn notificationTestMVar = TestList $ map (\t -> t sessionId conn) [
21− testRelationalExpr,
22− testSchemaExpr,
23− testTypeForRelationalExpr,
24− testDatabaseContextExpr,
25− testGraphExpr,
26− testPlanForDatabaseContextExpr,
27− testTransactionGraphAsRelation,
28− testHeadTransactionId,
29− testHeadName,
30− testSession,
31− testRelationVariableSummary,
32− testNotification notificationTestMVar
33− ]
25+testList sessionId conn notificationTestMVar = TestList $ serverTests
26+ where
27+ sessionTests = map (\t -> t sessionId conn) [
28+ testRelationalExpr,
29+ testSchemaExpr,
30+ testTypeForRelationalExpr,
31+ testDatabaseContextExpr,
32+ testGraphExpr,
33+ testPlanForDatabaseContextExpr,
34+ testTransactionGraphAsRelation,
35+ testHeadTransactionId,
36+ testHeadName,
37+ testSession,
38+ testRelationVariableSummary,
39+ testNotification notificationTestMVar
40+ ]
41+ serverTests = [testRequestTimeout]
3442
3543main :: IO ()
3644main = do
37− port <- launchTestServer
45+ (serverAddress, _) <- launchTestServer 0
3846 notificationTestMVar <- newEmptyMVar
39− eTestConn <- testConnection port notificationTestMVar
47+ eTestConn <- testConnection serverAddress notificationTestMVar
4048 case eTestConn of
4149 Left err -> putStrLn (show err) >> exitFailure
4250 Right (session, testConn) -> do
4351 tcounts <- runTestTT (testList session testConn notificationTestMVar)
4452 if errors tcounts + failures tcounts > 0 then exitFailure else exitSuccess
4553
54+{-main = do
55+ tcounts <- runTestTT (TestList [testRequestTimeout])
56+ if errors tcounts + failures tcounts > 0 then exitFailure else exitSuccess-}
57+
4658testDatabaseName :: DatabaseName
4759testDatabaseName = "test"
4860
49−testConnection :: Port -> MVar () -> IO (Either ConnectionError (SessionId, Connection))
50−testConnection port mvar = do
51− let connInfo = RemoteProcessConnectionInfo testDatabaseName (createNodeId "127.0.0.1" port) (testNotificationCallback mvar)
61+testConnection :: EndPointAddress -> MVar () -> IO (Either ConnectionError (SessionId, Connection))
62+testConnection serverAddress mvar = do
63+ let connInfo = RemoteProcessConnectionInfo testDatabaseName (NodeId serverAddress) (testNotificationCallback mvar)
64+ putStrLn ("Connecting to " ++ show serverAddress)
5265 eConn <- connectProjectM36 connInfo
5366 case eConn of
5467 Left err -> pure $ Left err
@@ -59,12 +72,18 @@ testConnection port mvar = do
5972 Right sessionId -> pure $ Right (sessionId, conn)
6073
6174-- | A version of 'launchServer' which returns the port on which the server is listening on a secondary thread
62−launchTestServer :: IO (Port)
63−launchTestServer = do
64− let config = defaultServerConfig { databaseName = testDatabaseName }
65− portVar <- newEmptyMVar
66− _ <- forkIO $ launchServer config (Just portVar) >> pure ()
67− takeMVar portVar
75+launchTestServer :: Timeout -> IO (EndPointAddress, ThreadId)
76+launchTestServer ti = do
77+ let config = defaultServerConfig { databaseName = testDatabaseName,
78+ perRequestTimeout = ti,
79+ testMode = True,
80+ bindPort = 0
81+ }
82+ addressMVar <- newEmptyMVar
83+ tid <- forkIO $ launchServer config (Just addressMVar) >> pure ()
84+ endPointAddress <- takeMVar addressMVar
85+ liftIO $ putStrLn ("launched server on " ++ show endPointAddress)
86+ pure (endPointAddress, tid)
6887
6988testRelationalExpr :: SessionId -> Connection -> Test
7089testRelationalExpr sessionId conn = TestCase $ do
@@ -172,3 +191,18 @@ testNotification mvar sess conn = TestCase $ do
172191 check $ executeDatabaseContextExpr sess conn (Assign "x" (ExistingRelation relationFalse))
173192 check $ commit sess conn
174193 takeMVar mvar
194+
195+testRequestTimeout :: Test
196+testRequestTimeout = TestCase $ do
197+ (serverAddress, serverTid) <- launchTestServer 1000
198+ unusedMVar <- newEmptyMVar
199+ let Just (host, service, _) = decodeEndPointAddress serverAddress
200+ serverAddress' = encodeEndPointAddress host service 1
201+ eTestConn <- testConnection serverAddress' unusedMVar
202+ --eTestConn <- testConnection (encodeEndPointAddress "127.0.0.1" "10000" 1) unusedMVar
203+ case eTestConn of
204+ Left err -> putStrLn ("failed to connect: " ++ show err) >> exitFailure
205+ Right (session, testConn) -> do
206+ res <- catchJust (\exc -> if exc == RequestTimeoutException then Just exc else Nothing) (callTestTimeout_ session testConn) (const (pure False))
207+ assertBool "exception was not thrown" (not res)
208+ killThread serverTid