on master help sign in
WIP notification listener working

-needs test -consider notification for when changeExpr returns error

commita011cd3472349a1f1b3ca3d76ca849c91997da3d authorAgentM <agentm@themactionfaction.com> dateWed 20 Jan 2016, 22:38 (11 years ago) parenta1d63ca
8 files changed+111−56
ProjectM36/Base.hs+5 ProjectM36/Client.hs+54−48 ProjectM36/Error.hs+2 ProjectM36/Notifications.hs+7−2 ProjectM36/RelationalExpression.hs+23−3 ProjectM36/Server.hs+1−1 ProjectM36/Server/EntryPoints.hs+2−2 TutorialD/Interpreter/DatabaseExpr.hs+17
ProjectM36/Base.hs+5
@@ -279,8 +279,13 @@ data DatabaseExpr where
279279 Insert :: RelVarName -> RelationalExpr -> DatabaseExpr
280280 Delete :: RelVarName -> RestrictionPredicateExpr -> DatabaseExpr
281281 Update :: RelVarName -> M.Map AttributeName Atom -> RestrictionPredicateExpr -> DatabaseExpr -- needs restriction support
282+
282283 AddInclusionDependency :: IncDepName -> InclusionDependency -> DatabaseExpr
283284 RemoveInclusionDependency :: IncDepName -> DatabaseExpr
285+
286+ AddNotification :: NotificationName -> RelationalExpr -> RelationalExpr -> DatabaseExpr
287+ RemoveNotification :: NotificationName -> DatabaseExpr
288+
284289 MultipleExpr :: [DatabaseExpr] -> DatabaseExpr
285290 deriving (Show, Eq, Binary, Generic)
286291
ProjectM36/Client.hs+54−48
@@ -55,7 +55,7 @@ import ProjectM36.Persist (DiskSync(..))
5555import ProjectM36.Notifications
5656import ProjectM36.Server.RemoteCallTypes
5757import Network.Transport.TCP (createTransport, defaultTCPParameters, encodeEndPointAddress)
58−import Control.Distributed.Process.Node (newLocalNode, initRemoteTable, runProcess, LocalNode)
58+import Control.Distributed.Process.Node (newLocalNode, initRemoteTable, runProcess, LocalNode, forkProcess)
5959import Control.Distributed.Process.Extras.Internal.Types (whereisRemote)
6060import Control.Distributed.Process.ManagedProcess.Client (call, safeCall)
6161import Control.Distributed.Process (NodeId(..), getSelfPid)
@@ -64,7 +64,7 @@ import Data.UUID (UUID)
6464import Data.UUID.V4 (nextRandom)
6565import Control.Concurrent.STM
6666import Data.Word
67−import Control.Distributed.Process (ProcessId, Process, receiveWait, send, match)
67+import Control.Distributed.Process (ProcessId, Process, receiveWait, send, match, say)
6868import Control.Exception (IOException)
6969import Control.Concurrent.MVar
7070import qualified Data.Map as M
@@ -77,6 +77,7 @@ import ProjectM36.Sessions
7777import ListT
7878import Data.Binary (Binary)
7979import GHC.Generics (Generic)
80+--import Debug.Trace
8081
8182type Hostname = String
8283
@@ -142,17 +143,20 @@ commonLocalNode = do
142143
143144notificationListener :: Process ()
144145notificationListener = do
145− _ <- receiveWait [
146− match (\(NotificationMessage eNots) -> undefined)
147− ]
148− liftIO $ putStrLn $ "LISTENER THREAD EXIT"
146+ pid <- getSelfPid
147+ liftIO $ putStrLn $ "LISTENER THREAD START " ++ show pid
148+ _ <- forever $ do
149+ receiveWait [
150+ match (\(NotificationMessage eNots) -> say $ "NOTIFICATION: " ++ show eNots)
151+ ]
152+ say "LISTENER THREAD EXIT"
149153 pure ()
150154
151155startNotificationListener :: IO (ProcessId)
152156startNotificationListener = do
153157 eLocalNode <- commonLocalNode
154158 case eLocalNode of
155− Left err -> error "Failed to start local notification listener"
159+ Left err -> error ("Failed to start local notification listener: " ++ show err)
156160 Right localNode -> forkProcess localNode notificationListener
157161
158162-- | To create a 'Connection' to a remote or local database, create a connectionInfo and call 'connectProjectM36'.
@@ -168,7 +172,7 @@ connectProjectM36 (InProcessConnectionInfo strat) = do
168172 graphTvar <- newTVarIO freshGraph
169173 clientNodes <- STMSet.newIO
170174 sessions <- STMMap.newIO
171− notProcId <- startNotificationListener
175+ --notificationPid <- startNotificationListener
172176 return $ Right $ InProcessConnection strat clientNodes sessions graphTvar
173177 MinimalPersistence dbdir -> connectPersistentProjectM36 strat NoDiskSync dbdir freshGraph
174178 CrashSafePersistence dbdir -> connectPersistentProjectM36 strat FsyncDiskSync dbdir freshGraph
@@ -176,6 +180,7 @@ connectProjectM36 (InProcessConnectionInfo strat) = do
176180connectProjectM36 (RemoteProcessConnectionInfo databaseName serverNodeId) = do
177181 connStatus <- newEmptyMVar
178182 eLocalNode <- commonLocalNode
183+ notificationListenerPid <- startNotificationListener
179184 let dbName = remoteDBLookupName databaseName
180185 putStrLn $ show serverNodeId ++ " " ++ dbName
181186 case eLocalNode of
@@ -183,11 +188,10 @@ connectProjectM36 (RemoteProcessConnectionInfo databaseName serverNodeId) = do
183188 Right localNode -> do
184189 runProcess localNode $ do
185190 mServerProcessId <- whereisRemote serverNodeId dbName
186− selfPid <- getSelfPid
187191 case mServerProcessId of
188192 Nothing -> liftIO $ putMVar connStatus $ Left (NoSuchDatabaseByNameError databaseName)
189193 Just serverProcessId -> do
190− loginConfirmation <- call serverProcessId (Login selfPid)
194+ loginConfirmation <- call serverProcessId (Login notificationListenerPid)
191195 if not loginConfirmation then
192196 liftIO $ putMVar connStatus (Left LoginError)
193197 else do
@@ -315,9 +319,9 @@ executeDatabaseContextExpr sessionId conn@(RemoteProcessConnection _ _) dbExpr =
315319
316320executeGraphExprSTM_ :: UUID -> SessionId -> Sessions -> TransactionGraphOperator -> TVar TransactionGraph -> STM (Either RelationalError TransactionGraph)
317321executeGraphExprSTM_ freshUUID sessionId sessions graphExpr graphTvar = do
318− graph <- readTVar graphTvar
319− eSession <- sessionForSessionId sessionId sessions
320− case eSession of
322+ graph <- readTVar graphTvar
323+ eSession <- sessionForSessionId sessionId sessions
324+ case eSession of
321325 Left err -> pure $ Left err
322326 Right (Session discon) -> case evalGraphOp freshUUID discon graph graphExpr of
323327 Left err -> pure $ Left err
@@ -327,61 +331,63 @@ executeGraphExprSTM_ freshUUID sessionId sessions graphExpr graphTvar = do
327331 STMMap.insert newSession sessionId sessions
328332 pure $ Right graph'
329333
330−
334+-- process notifications for commits
335+executeCommitExprSTM_ :: DatabaseContext -> DatabaseContext -> ClientNodes -> STM (EvaluatedNotifications, ClientNodes)
336+executeCommitExprSTM_ oldContext newContext nodes = do
337+ let nots = notifications oldContext
338+ fireNots = notificationChanges nots oldContext newContext
339+ evaldNots = M.map mkEvaldNot fireNots
340+ mkEvaldNot notif = EvaluatedNotification { notification = notif, reportRelation = evalState (RE.evalRelationalExpr (reportExpr notif)) oldContext }
341+ pure (evaldNots, nodes)
342+
343+
331344-- | Execute a transaction graph expression in the context of the session and connection. Transaction graph operators modify the transaction graph state.
332345executeGraphExpr :: SessionId -> Connection -> TransactionGraphOperator -> IO (Maybe RelationalError)
333−executeGraphExpr sessionId (InProcessConnection strat _ sessions graphTvar) graphExpr = do
346+executeGraphExpr sessionId (InProcessConnection strat clientNodes sessions graphTvar) graphExpr = do
334347 freshUUID <- nextRandom
335− manip <- atomically $ executeGraphExprSTM_ freshUUID sessionId sessions graphExpr graphTvar
348+ manip <- atomically $ do
349+ eSession <- sessionForSessionId sessionId sessions
350+ oldGraph <- readTVar graphTvar
351+ case eSession of
352+ Left err -> pure (Left err)
353+ Right (Session (DisconnectedTransaction parentUUID currentContext)) -> do
354+ eGraph <- executeGraphExprSTM_ freshUUID sessionId sessions graphExpr graphTvar
355+ case eGraph of
356+ Left err -> pure (Left err)
357+ Right newGraph -> do
358+ if graphExpr == Commit then
359+ case transactionForUUID parentUUID oldGraph of
360+ Left err -> pure $ Left err
361+ Right (Transaction _ _ previousContext) -> do
362+ (evaldNots, nodes) <- executeCommitExprSTM_ previousContext currentContext clientNodes
363+ nodesToNotify <- toList (STMSet.stream nodes)
364+ pure $ Right (evaldNots, nodesToNotify, newGraph)
365+ else
366+ pure $ Right (M.empty, [], newGraph)
336367 case manip of
337368 Left err -> return $ Just err
338− Right newGraph -> do
369+ Right (notsToFire, nodesToNotify, newGraph) -> do
339370 --update filesystem database, if necessary
340371 --this should really grab a lock at the beginning of the method to be threadsafe
341372 processPersistence strat newGraph
373+ sendNotifications nodesToNotify notsToFire
342374 return Nothing
343375executeGraphExpr sessionId conn@(RemoteProcessConnection _ _) graphExpr = remoteCall conn (ExecuteGraphExpr sessionId graphExpr)
344376
377+{-
345378commitSTM_ :: UUID -> SessionId -> Sessions -> TVar TransactionGraph -> STM (Maybe RelationalError)
346379commitSTM_ freshUUID sessionId sessions graph = do
347380 mErr <- executeGraphExprSTM_ freshUUID sessionId sessions Commit graph
348381 pure $ case mErr of
349382 Left err -> Just err
350383 Right _ -> Nothing
384+-}
351385
352386-- | After modifying a session, 'commit' the transaction to the transaction graph at the head which the session is referencing. This will also trigger checks for any notifications which need to be propagated.
353387commit :: SessionId -> Connection -> IO (Maybe RelationalError)
354−commit sessionId (InProcessConnection _ clientNodes sessions graphTvar) = do
355− freshUUID <- nextRandom
356− mNots <- atomically $ do
357− graph <- readTVar graphTvar
358− -- take the notifications of the current context and scan them for changes in the previous -> current contexts
359− eSession <- sessionForSessionId sessionId sessions
360− case eSession of
361− Left err -> pure (Left err)
362− Right (Session (DisconnectedTransaction parentUUID currentContext)) -> do
363− --grab previous context
364− case transactionForUUID parentUUID graph of
365− Left err -> pure $ Left err
366− Right (Transaction _ _ previousContext) -> do
367− let nots = notifications currentContext
368− fireNots = notificationChanges nots previousContext currentContext
369− evaldNots = M.map mkEvaldNot fireNots
370− mkEvaldNot notif = EvaluatedNotification { notification = notif, reportRelation = evalState (RE.evalRelationalExpr (reportExpr notif)) currentContext }
371− mErr <- commitSTM_ freshUUID sessionId sessions graphTvar
372− case mErr of
373− Just err -> pure (Left err)
374− Nothing -> do
375− notifyNodes <- toList (STMSet.stream clientNodes)
376− pure (Right (evaldNots, notifyNodes))
377− case mNots of
378− Left err -> pure (Just err)
379− Right (notsToFire, nodesToNotify) -> do
380− --trigger the notifications on all clients
381− sendNotifications nodesToNotify notsToFire
382− pure Nothing
388+commit sessionId conn@(InProcessConnection _ _ _ _) = executeGraphExpr sessionId conn Commit
383389commit sessionId conn@(RemoteProcessConnection _ _) = remoteCall conn (ExecuteGraphExpr sessionId Commit)
384−
390+
385391sendNotifications :: [ProcessId] -> EvaluatedNotifications -> IO ()
386392sendNotifications pids nots = mapM_ sendNots pids
387393 where
@@ -389,7 +395,7 @@ sendNotifications pids nots = mapM_ sendNots pids
389395 eLocalNode <- commonLocalNode
390396 case eLocalNode of
391397 Left err -> error ("Failed to get local node: " ++ show err)
392− Right localNode -> runProcess localNode $ send remoteClientPid (NotificationMessage nots)
398+ Right localNode -> when (not (M.null nots)) $ runProcess localNode $ send remoteClientPid (NotificationMessage nots)
393399
394400-- | Discard any changes made in the current session. This resets the disconnected transaction to reference the original database context of the parent transaction and is a very cheap operation.
395401rollback :: SessionId -> Connection -> IO (Maybe RelationalError)
ProjectM36/Error.hs+2
@@ -39,6 +39,8 @@ data RelationalError = NoSuchAttributeNamesError (S.Set AttributeName)
3939 | AtomTypeCountError [AtomType] [AtomType]
4040 | AtomFunctionTypeError AtomFunctionName Int AtomType AtomType --arg number
4141 | RelationValuedAttributesNotSupportedError [AttributeName]
42+ | NotificationNameInUseError NotificationName
43+ | NotificationNameNotInUseError NotificationName
4244 | ImportError T.Text -- really? This should be broken out into some other error type- this has nothing to do with relational algebra
4345 | ExportError T.Text
4446 deriving (Show,Eq,Generic,Binary,Typeable)
ProjectM36/Notifications.hs+7−2
@@ -3,10 +3,15 @@ import ProjectM36.Base
33import ProjectM36.RelationalExpression
44import Control.Monad.State
55import qualified Data.Map as M
6+import Data.Either (isRight)
67
78-- | Returns the notifications which should be triggered based on the transition from the first 'DatabaseContext' to the second 'DatabaseContext'.
89notificationChanges :: Notifications -> DatabaseContext -> DatabaseContext -> Notifications
910notificationChanges nots context1 context2 = M.filter notificationFilter nots
1011 where
11− notificationFilter (Notification chExpr _) = evalChangeExpr chExpr context1 /= evalChangeExpr chExpr context2
12− evalChangeExpr chExpr = evalState (evalRelationalExpr chExpr)
12+ notificationFilter (Notification chExpr _) = let oldChangeEval = evalChangeExpr chExpr context1
13+ newChangeEval = evalChangeExpr chExpr context2 in
14+ oldChangeEval /= newChangeEval && isRight oldChangeEval
15+ evalChangeExpr chExpr = evalState (evalRelationalExpr chExpr)
16+
17+
ProjectM36/RelationalExpression.hs+23−3
@@ -303,6 +303,29 @@ evalContextExpr (RemoveInclusionDependency depName) = do
303303 else do
304304 put $ currContext {inclusionDependencies = newDeps }
305305 return Nothing
306+
307+-- | Add a notification which will send the resultExpr when triggerExpr changes between commits.
308+evalContextExpr (AddNotification notName triggerExpr resultExpr) = do
309+ currentContext <- get
310+ let nots = notifications currentContext
311+ if M.member notName nots then
312+ return $ Just (NotificationNameInUseError notName)
313+ else do
314+ let newNotifications = M.insert notName newNotification nots
315+ newNotification = Notification { changeExpr = triggerExpr,
316+ reportExpr = resultExpr }
317+ put $ currentContext { notifications = newNotifications }
318+ return Nothing
319+
320+evalContextExpr (RemoveNotification notName) = do
321+ currentContext <- get
322+ let nots = notifications currentContext
323+ if M.notMember notName nots then
324+ return $ Just (NotificationNameNotInUseError notName)
325+ else do
326+ let newNotifications = M.delete notName nots
327+ put $ currentContext { notifications = newNotifications }
328+ return Nothing
306329
307330evalContextExpr (MultipleExpr exprs) = do
308331 --the multiple expressions must pass the same context around- not the old unmodified context
@@ -312,9 +335,6 @@ evalContextExpr (MultipleExpr exprs) = do
312335 [] -> return $ Nothing
313336 err:_ -> return $ Just err
314337
315−-- restrict relvar to get affected tuples, update tuples, delete restriction from relvar, relvar = relvar union updated tuples
316−--evalRelVarExpr (Update relVarName updateMap) = do
317−
318338--run verification on all constraints
319339checkConstraints :: DatabaseContext -> Maybe RelationalError
320340checkConstraints context = case failures of
ProjectM36/Server.hs+1−1
@@ -29,7 +29,7 @@ serverDefinition = defaultProcess {
2929 handleCall (\conn (RetrievePlanForDatabaseContextExpr sessionId dbExpr) -> handleRetrievePlanForDatabaseContextExpr sessionId conn dbExpr),
3030 handleCall (\conn (RetrieveHeadTransactionUUID sessionId) -> handleRetrieveHeadTransactionUUID sessionId conn),
3131 handleCall (\conn (RetrieveTransactionGraph sessionId) -> handleRetrieveTransactionGraph sessionId conn),
32− handleCall (\conn Login -> handleLogin conn),
32+ handleCall (\conn (Login procId) -> handleLogin conn procId),
3333 handleCall (\conn (CreateSessionAtHead headn) -> handleCreateSessionAtHead headn conn),
3434 handleCall (\conn (CreateSessionAtCommit commitId) -> handleCreateSessionAtCommit commitId conn),
3535 handleCall (\conn (CloseSession sessionId) -> handleCloseSession sessionId conn)
ProjectM36/Server/EntryPoints.hs+2−2
@@ -2,7 +2,7 @@ module ProjectM36.Server.EntryPoints where
22import ProjectM36.Base hiding (inclusionDependencies)
33import ProjectM36.Client
44import ProjectM36.Error
5−import Control.Distributed.Process (Process)
5+import Control.Distributed.Process (Process, ProcessId)
66import Control.Distributed.Process.ManagedProcess (ProcessReply)
77import Control.Distributed.Process.ManagedProcess.Server (reply)
88import Control.Monad.IO.Class (liftIO)
@@ -26,7 +26,7 @@ handleExecuteHeadName sessionId conn = do
2626
2727handleLogin :: Connection -> ProcessId -> Process (ProcessReply Bool Connection)
2828handleLogin conn newClientProcessId = do
29− liftIO (addClientNode newClientProcessId)
29+ liftIO (addClientNode conn newClientProcessId)
3030 reply True conn
3131
3232handleExecuteGraphExpr :: SessionId -> Connection -> TransactionGraphOperator -> Process (ProcessReply (Maybe RelationalError) Connection)
TutorialD/Interpreter/DatabaseExpr.hs+17
@@ -24,6 +24,8 @@ databaseExprP = insertP
2424 <|> defineP
2525 <|> undefineP
2626 <|> assignP
27+ <|> addNotificationP
28+ <|> removeNotificationP
2729
2830assignP :: Parser DatabaseExpr
2931assignP = do
@@ -107,6 +109,20 @@ attributeAssignmentP = do
107109 reservedOp ":="
108110 atom <- stringAtomP <|> intAtomP
109111 return $ (attrName, atom)
112+
113+addNotificationP :: Parser DatabaseExpr
114+addNotificationP = do
115+ reserved "notify"
116+ notName <- identifier
117+ triggerExpr <- relExprP
118+ resultExpr <- relExprP
119+ return $ AddNotification (T.pack notName) triggerExpr resultExpr
120+
121+removeNotificationP :: Parser DatabaseExpr
122+removeNotificationP = do
123+ reserved "unnotify"
124+ notName <- identifier
125+ return $ RemoveNotification (T.pack notName)
110126
111127databaseExprOpP :: Parser DatabaseExpr
112128databaseExprOpP = multipleDatabaseExprP
@@ -131,3 +147,4 @@ interpretNO context tutdstring = case parseString tutdstring of
131147 Left err -> (Just err, context)
132148 Right parsed -> runState (evalContextExpr parsed) context
133149-}
150+