/// <summary> /// Serializes the message passed into a byte array. /// </summary> /// <param name="msg">The message to be serialized.</param> /// <param name="es">The enhanced stream where the output is to be written.</param> /// <returns>The size of the message written in bytes.</returns> /// <remarks> /// The buffer returned includes the preamble, the message type, /// and the payload. /// </remarks> public static int Save(EnhancedStream es, Msg msg) { string typeID; int cbMsg; MsgInfo msgInfo; EnvelopeMsg envelopeMsg; int startPos; // Write the type and payload first to determine the message // size and then go back and write the preamble. Note that // for envelope messages, we're going to get the typeID from the // message instance, rather than looking it up in the type map. envelopeMsg = msg as EnvelopeMsg; if (envelopeMsg != null) { typeID = envelopeMsg.TypeID; } else { lock (syncLock) typeMap.TryGetValue(msg.GetType(), out msgInfo); if (msgInfo == null) { throw new MsgException("Unregistered message class [{0}].", msg.GetType().FullName); } typeID = msgInfo.TypeID; } startPos = (int)es.Position; es.Write(preamble, 0, preamble.Length); // Leave room for the preamble es.WriteString16(typeID); #if DEBUG msg.writeBase = false; // $hack(jeff.lill): Strictly speaking, this isn't threadsafe #endif msg.WriteBase(es); #if DEBUG Assertion.Test(msg.writeBase, "Derived [Msg] classes must call [base.WriteBase()]."); #endif msg.WritePayload(es); cbMsg = (int)es.Position; es.Position = startPos; es.WriteByte(magic); // Magic number es.WriteByte(0); // Message format es.WriteInt32(cbMsg); // Total message length es.Position = startPos + cbMsg; return(cbMsg); }
/// <summary> /// Constructs a <see cref="MsgRequestContext" /> for transactions that are within a session. /// </summary> /// <param name="session">The <see cref="DuplexSession" />.</param> /// <param name="query">The request <see cref="Msg" />.</param> /// <exception cref="ArgumentException">Thrown if the message passed does not have all of the headers necessary to be a request.</exception> internal MsgRequestContext(DuplexSession session, Msg query) { if (session == null) { throw new ArgumentNullException("session"); } if (query._FromEP == null) { throw new ArgumentException("Message cannot be a request: Null [_FromEP] header.", "requestMsg"); } if (query._SessionID == Guid.Empty) { throw new ArgumentException("Message cannot be a request: Empty [_SessionID] header.", "requestMsg"); } this.Header = query._ExtensionHeaders[MsgHeaderID.DuplexSession]; if (this.Header == null) { throw new ArgumentException("Message is not a DuplexSession query.", "requestMsg"); } this.router = session.Router; this.session = session; this.FromEP = query._FromEP.Clone(); this.SessionID = query._SessionID; #if TRACE this.TraceName = query.GetType().Name; #else this.TraceName = "(trace disabled)"; #endif }
private bool closed = false; // True if the transaction has completed /// <summary> /// Constructs a <see cref="MsgRequestContext" /> for transactions that are not within a session. /// </summary> /// <param name="router">The <see cref="MsgRouter" />.</param> /// <param name="requestMsg">The request <see cref="Msg" />.</param> /// <exception cref="ArgumentException">Thrown if the message passed does not have all of the headers necessary to be a request.</exception> internal MsgRequestContext(MsgRouter router, Msg requestMsg) { if (router == null) { throw new ArgumentNullException("router"); } if (requestMsg._FromEP == null) { throw new ArgumentException("Message cannot be a request: Null [_FromEP] header.", "requestMsg"); } if (requestMsg._SessionID == Guid.Empty) { throw new ArgumentException("Message cannot be a request: Empty [_SessionID] header.", "requestMsg"); } this.router = router; this.session = null; this.FromEP = requestMsg._FromEP.Clone(); this.SessionID = requestMsg._SessionID; #if TRACE this.TraceName = requestMsg.GetType().Name; #else this.TraceName = "(trace disabled)"; #endif }
/// <summary> /// Called to dispatch a server side session. /// </summary> /// <param name="msg">The message initiating the session.</param> /// <param name="target">The target object instance.</param> /// <param name="method">The target method information.</param> /// <param name="sessionInfo">The session information associated with the handler.</param> /// <remarks> /// The target and method parameter will specify the message handler /// for the message passed. /// </remarks> public void ServerDispatch(Msg msg, object target, MethodInfo method, SessionHandlerInfo sessionInfo) { ISession session; bool start = false; Assertion.Test((msg._Flags & (MsgFlag.OpenSession & MsgFlag.ServerSession)) == (MsgFlag.OpenSession & MsgFlag.ServerSession)); Assertion.Test(msg._SessionID != Guid.Empty); using (TimedLock.Lock(router.SyncRoot)) { // Create a session with this ID if one doesn't already exist. serverSessions.TryGetValue(msg._SessionID, out session); if (session == null) { if (sessionInfo.SessionType == null) { SysLog.LogError("Session creation failed for received [{0}] message: No session type specified in [MsgSession] tag for handler [{1}.{2}({3})}.", msg.GetType().FullName, target.GetType().FullName, method.Name, method.GetParameters()[0].ParameterType.Name); return; } start = true; session = Helper.CreateInstance <ISession>(sessionInfo.SessionType); session.InitServer(router, this, router.SessionTimeout, msg, target, method, sessionInfo); serverSessions.Add(msg._SessionID, session); } } // Dispatch the message outside of the lock (to avoid deadlocks) if (start) { session.StartServer(); } else { session.OnMsg(msg, sessionInfo); } }
// $todo(jeff.lill): // // I'm not really sure if we need this to be dispached // on a worker thread since MsgRouter.OnProcessMsg() // (who calls MsgDispatcher.Dispatch()) is already // running on a fresh worker thread. We'd probably see // a performance boost by having Dispatch() call the // handler directly. Something to look into when I // have more time. /// <summary> /// Handles the dispatch on a worker thread. /// </summary> /// <param name="state">The DispatchInfo.</param> private void OnDispatch(object state) { DispatchInfo info = (DispatchInfo)state; MsgHandler handler = info.Handler; Msg msg = info.Msg; SessionHandlerInfo sessionInfo = handler == null ? null : handler.SessionInfo; ISessionManager sessionMgr; try { // If there's a router associated with this instance and the message // has a non-empty _SessionID then we'll either initiate a server side // session or simply route the message to the session, depending // on the MsgFlag.OpenSession bit. // // If neither of these conditions are true then route the message // directly to the handler. if (router != null && msg._SessionID != Guid.Empty) { sessionMgr = router.SessionManager; if ((msg._Flags & MsgFlag.OpenSession) != 0) { if (handler == null) { NetTrace.Write(MsgRouter.TraceSubsystem, 1, "Dispatch: Message Discarded", "No message handler for: " + msg.GetType().Name, string.Empty); return; } sessionMgr.ServerDispatch(msg, handler.Target, handler.Method, sessionInfo); } else { sessionMgr.OnMsg(msg, sessionInfo); } } else { handler.Method.Invoke(handler.Target, info.Args); } } catch (Exception e) { NetTrace.Write(MsgRouter.TraceSubsystem, 0, "App Exception", e); SysLog.LogException(e); } }
/// <summary> /// Dispatches the message passed to the appropriate handler in /// the associated target instance. Note that the dispatch call will /// be performed on a worker thread. /// </summary> /// <param name="msg">The message to be dispatched.</param> /// <returns><c>true</c> if the message was successfully dispatched.</returns> /// <remarks> /// <para> /// This method uses two basic implementation techniques depending on whether /// the message's target endpoint is physical or logical. /// </para> /// <para><b><u>Physical Endpoint Implementation</u></b></para> /// <para> /// For logical endpoint handlers, MsgDispatcher maintains a hash table /// mapping each handler method to the method's message parameter type. /// When the router receives a message targeted at a physical endpoint, /// the router calls <see cref="Dispatch" /> which then calls the handler /// method whose parameter type matches that of the message being dispatched. /// <see cref="Dispatch" /> will return true in this case. /// </para> /// <para> /// If no match is found and a method is tagged with <c>[MsgHandler(Default=true)]</c> /// then the message will be dispatched there. If there's no default /// handler, then the message will be dropped and <see cref="Dispatch" /> /// will return false. /// </para> /// <para> /// The message's <see cref="MsgFlag">MsgFlag.Broadcast</see> flag is ignored when dispatching /// messages targeted at physical endpoints. /// </para> /// <para><b><u>Logical Endpoint Implementation</u></b></para> /// <para> /// The logical endpoint implementation is a bit trickier. MsgDispatcher /// includes a LogicalRouteTable instance of the logical endpoints it /// collects from [MsgHandler] attributes. For each distinct logical /// endpoint, the class maintains a hash table mapping the message type /// of each tagged message handler method to the method instance. /// These hash tables are stored in the <see cref="LogicalRoute.Handlers" /> property /// of the routes stored in the LogicalRouteTable. /// </para> /// <para> /// When the router receives a message targeted at a logical endpoint, /// the router call <see cref="Dispatch" />, which will then /// search the logical route table for one or more matching logical routes. /// If any are found, then <see cref="Dispatch" /> will randomly select one and then examine /// the hash table saved in the route's <see cref="LogicalRoute.Handlers" /> property looking /// for a method handler whose parameter type matches the message type /// received, calling the handler if a match is found and <see cref="Dispatch" /> /// will return false. /// </para> /// <para> /// If no match is found and a method is tagged with <c>[MsgHandler(Default=true)]</c> /// then the message will be dispatched there. If there's no default /// handler, then the message will not be dispatched and <see cref="Dispatch" /> /// will return false. /// </para> /// <para> /// <see cref="Dispatch" /> works a bit differently for messages targeted at /// logical endpoints if the <see cref="MsgFlag">MsgFlag.Broadcast</see> flag is set. /// In this situation, instead of randomly selecting a single matching /// route and dispatching the message there, <see cref="Dispatch" /> dispatch the /// message to all matching routes. Note though, that only one message /// handler associated with each route will be called (based on the /// type of the message). /// </para> /// </remarks> public bool Dispatch(Msg msg) { // Look up the approriate handler for this message. MsgHandler handler = null; if (msg._ToEP == null || msg._ToEP.IsPhysical) { // We don't dispatch EnvelopeMsgs. if (msg is EnvelopeMsg) { return(false); } // Get the physical handler using (TimedLock.Lock(syncLock)) { physHandlers.TryGetValue(msg.GetType(), out handler); if (handler == null) { handler = defPhysHandler; } } // If we couldn't find a handler for this message type and the // message isn't associated with a session then discard // the message. Messages with no handler that are associated // with a session will be routed to the session instance // to be handled there. if (handler == null && msg._SessionID == Guid.Empty) { if (router.IsOpen) { NetTrace.Write(MsgRouter.TraceSubsystem, 1, "Dispatch Physical", router.GetType().Name + ": " + msg.GetType().Name + " router=" + router.RouterEP.ToString(), " No handler"); } return(false); } if (router.IsOpen) { NetTrace.Write(MsgRouter.TraceSubsystem, 1, "Dispatch Physical", router.GetType().Name + ": " + msg.GetType().Name + " router=" + router.RouterEP.ToString(), " Target: " + (handler == null ? "(none)" : handler.Target.GetType().Name)); } } else if ((msg._Flags & MsgFlag.Broadcast) != 0) { // A broadcast message is being targeted at a logical endpoint. List <LogicalRoute> routes; LogicalRoute route; using (TimedLock.Lock(syncLock)) { routes = logicalRoutes.GetRoutes(msg._ToEP); if (routes.Count == 0) { return(false); } } for (int i = 0; i < routes.Count; i++) { route = routes[i]; handler = null; if (!route.Handlers.TryGetValue(msg.GetType().FullName, out handler)) { route.Handlers.TryGetValue(DefaultHandler, out handler); } if (handler == null) { continue; } NetTrace.Write(MsgRouter.TraceSubsystem, 1, "Dispatch Logical", msg.GetType().Name + "[" + msg._ToEP + "]", "Target: " + (handler == null ? "(none)" : handler.Target.GetType().Name)); // Verify that the message type is actually compatible with the // handler's message parameter. if (!handler.MsgType.IsAssignableFrom(msg.GetType())) { SysLog.LogWarning("Unable to dispatch message type [{0}] to [{0}.{1}()] because the handler parameter type is not compatible.", msg.GetType(), handler.Target.GetType().FullName, handler.Method.Name); return(false); } if ((msg._Flags & MsgFlag.Priority) != 0) { router.ThreadPool.QueuePriorityTask(onDispatch, new DispatchInfo(msg, handler, new object[] { msg })); } else { router.ThreadPool.QueueTask(onDispatch, new DispatchInfo(msg, handler, new object[] { msg })); } } return(true); } else { // A non-broadcast message is being targeted at a logical endpoint. List <LogicalRoute> routes; LogicalRoute route; Assertion.Test((msg._Flags & MsgFlag.Broadcast) == 0); Assertion.Test(msg._ToEP.IsLogical); using (TimedLock.Lock(syncLock)) { routes = logicalRoutes.GetRoutes(msg._ToEP); if (routes.Count == 0) { return(false); } route = routes[Helper.RandIndex(routes.Count)]; handler = null; if (!route.Handlers.TryGetValue(msg.GetType().FullName, out handler)) { route.Handlers.TryGetValue(DefaultHandler, out handler); } } // If we couldn't find a handler for this message type and the // message isn't associated with a session then discard // the message. Messages with no handler that are associated // with a session will be routed to the session instance // to be handled there. if (handler == null && msg._SessionID == Guid.Empty) { NetTrace.Write(MsgRouter.TraceSubsystem, 1, "Dispatch Logical", msg.GetType().Name, "No handler"); return(false); } NetTrace.Write(MsgRouter.TraceSubsystem, 1, "Dispatch Logical", msg.GetType().Name + "[" + msg._ToEP + "]", "Target: " + (handler == null ? "(none)" : handler.Target.GetType().Name)); } // Verify that the message type is actually compatible with the // handler's message parameter. if (handler != null && !handler.MsgType.IsAssignableFrom(msg.GetType())) { SysLog.LogWarning("Unable to dispatch message type [{0}] to [{0}.{1}()] because the handler parameter type is not compatible.", msg.GetType(), handler.Target.GetType().FullName, handler.Method.Name); return(false); } if ((msg._Flags & MsgFlag.Priority) != 0) { router.ThreadPool.QueuePriorityTask(onDispatch, new DispatchInfo(msg, handler, new object[] { msg })); } else { router.ThreadPool.QueueTask(onDispatch, new DispatchInfo(msg, handler, new object[] { msg })); } return(true); }