/// <summary> /// Called by the router whenever it receives a message with a non-empty /// _SessionID. This method dispatches the message to the associated session /// (if any). /// </summary> /// <param name="msg">The message.</param> /// <param name="sessionInfo">The session information associated with the handler.</param> public void OnMsg(Msg msg, SessionHandlerInfo sessionInfo) { ISession session; Assertion.Test(msg._SessionID != Guid.Empty); using (TimedLock.Lock(router.SyncRoot)) { if ((msg._Flags & MsgFlag.ServerSession) != 0) { serverSessions.TryGetValue(msg._SessionID, out session); } else { clientSessions.TryGetValue(msg._SessionID, out session); } } if (session != null) { msg._Trace(router, 2, "SessionManager: Dispached", null); session.OnMsg(msg, sessionInfo); } else { msg._Trace(router, 2, "SessionManager: No session", null); } }
/// <summary> /// Initializes a server side session. /// </summary> /// <param name="router">The associated message router.</param> /// <param name="sessionMgr">The associated session manager.</param> /// <param name="ttl">Session time-to-live.</param> /// <param name="msg">The message that triggered this session.</param> /// <param name="target">The dispatch target instance.</param> /// <param name="method">The dispatch method.</param> /// <param name="sessionInfo"> /// The session information associated with the handler or <c>null</c> /// to use session defaults. /// </param> public virtual void InitServer(MsgRouter router, ISessionManager sessionMgr, TimeSpan ttl, Msg msg, object target, MethodInfo method, SessionHandlerInfo sessionInfo) { if (sessionInfo == null) { sessionInfo = SessionHandlerInfo.Default; } this.isClient = false; this.router = router; this.sessionMgr = sessionMgr; this.sessionID = msg._SessionID; this.isAsync = sessionInfo.IsAsync; this.isRunning = true; this.startTime = SysTime.Now; this.finishTime = DateTime.MaxValue; this.ttd = startTime + ttl; this.ttl = ttl; this.serverInitMsg = msg; this.clientEP = msg._FromEP; this.target = target; this.method = method; this.sessionInfo = sessionInfo != null ? sessionInfo : defSessionInfo; this.cachedReply = null; msg._Session = (ISession)this; }
/// <summary> /// Static constructor. /// </summary> static SessionBase() { MsgSessionAttribute attr; attr = new MsgSessionAttribute(); attr.Type = SessionTypeID.Unknown; defSessionInfo = new SessionHandlerInfo(attr); }
//--------------------------------------------------------------------- // Common implementation members /// <summary> /// Handles any received messages (not including the first message directed to the server) /// associated with this session. /// </summary> /// <param name="msg">The message.</param> /// <param name="sessionInfo">The session information associated with the handler.</param> /// <remarks> /// <note> /// The first message sent to server side session will result in a /// call to <see cref="StartServer" /> rather than a call to this method. /// </note> /// </remarks> public override void OnMsg(Msg msg, SessionHandlerInfo sessionInfo) { if (base.IsClient) { OnClientMsg(msg); } else { OnServerMsg(msg); } }
/// <summary> /// Constructs a MsgHandler instance mapped to a reflected /// message handler method. /// </summary> /// <param name="target">The target object instance.</param> /// <param name="method">Information about the instance method that will actually handle the message.</param> /// <param name="msgType">The message type accepted by the handler.</param> /// <param name="handlerAttr">The [MsgHandler] instance for the handler (or <c>null</c>).</param> /// <param name="sessionInfo">The session information for the handler (or <c>null</c>).</param> public MsgHandler(object target, MethodInfo method, System.Type msgType, MsgHandlerAttribute handlerAttr, SessionHandlerInfo sessionInfo) { if (sessionInfo == null) { sessionInfo = SessionHandlerInfo.Default; } this.Target = target; this.Method = method; this.MsgType = msgType; this.DynamicScope = handlerAttr == null ? null : handlerAttr.DynamicScope; this.SessionInfo = sessionInfo; }
/// <summary> /// Constructs a MsgHandler instance mapped to a reflected /// message handler method. /// </summary> /// <param name="target">The target object instance.</param> /// <param name="method">Information about the instance method that will actually handle the message.</param> /// <param name="msgType">The message type accepted by the handler.</param> /// <param name="handlerAttr">The [MsgHandler] instance for the handler (or <c>null</c>).</param> /// <param name="sessionAttr">The [MsgSession] instance for the handler (or <c>null</c>).</param> public MsgHandler(object target, MethodInfo method, System.Type msgType, MsgHandlerAttribute handlerAttr, MsgSessionAttribute sessionAttr) { this.Target = target; this.Method = method; this.MsgType = msgType; this.DynamicScope = handlerAttr == null ? null : handlerAttr.DynamicScope; if (sessionAttr == null) { this.SessionInfo = SessionHandlerInfo.Default; } else { this.SessionInfo = new SessionHandlerInfo(sessionAttr); } }
// $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> /// Initializes a client side session. /// </summary> /// <param name="router">The associated message router.</param> /// <param name="sessionMgr">The associated session manager.</param> /// <param name="ttl">Session time-to-live.</param> /// <param name="sessionID">The session ID to assign to this session</param> public virtual void InitClient(MsgRouter router, ISessionManager sessionMgr, TimeSpan ttl, Guid sessionID) { this.isClient = true; this.router = router; this.sessionMgr = sessionMgr; this.sessionID = sessionID; this.startTime = SysTime.Now; this.finishTime = DateTime.MaxValue; this.ttd = startTime + ttl; this.ttl = ttl; this.serverInitMsg = null; this.clientEP = null; this.target = null; this.method = null; this.sessionInfo = null; this.cachedReply = null; }
/// <summary> /// Adds an explicit physical message handler to the dispatcher. /// </summary> /// <param name="callback">The handler callback.</param> /// <param name="msgType">The message type to be associated with the handler.</param> /// <param name="sessionInfo"> /// The session related information to be related to this handler (or <c>null</c>). /// </param> /// <remarks> /// <note> /// Methods registered as handlers via this method will have to declare /// their message parameter as the base Msg class so that the compiler can /// create MsgHandlerDelegate instances. The message parameter can then be /// cast to the proper message type within the handler method. /// </note> /// </remarks> public void AddPhysical(MsgHandlerDelegate callback, System.Type msgType, SessionHandlerInfo sessionInfo) { MethodInfo method; MsgHandler handler; method = callback.Method; handler = new MsgHandler(callback.Target, method, msgType, null, sessionInfo); using (TimedLock.Lock(syncLock)) { if (physHandlers.ContainsKey(msgType)) { throw new MsgException("Multiple physical handlers defined for message type [{0}].", msgType); } physHandlers.Add(msgType, handler); } }
/// <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); } }
/// <summary> /// Handles any received messages associated with this session. /// </summary> /// <param name="msg">The message.</param> /// <param name="sessionInfo">The session information associated with the handler.</param> public virtual void OnMsg(Msg msg, SessionHandlerInfo sessionInfo) { }
/// <summary> /// Scans the target object passed for dispatchable methods and adds /// them to the dispatcher, using the <see cref="IDynamicEPMunger" /> /// instances passed in the <paramref name="mungers"/> set to dynamically /// modify endpoints for message handlers marked with <c>[MsgHandler(DynamicScope="scope-name")]</c>. /// </summary> /// <param name="target">The object to be scanned.</param> /// <param name="mungers"> /// The collection of <see cref="ScopeMunger" /> instances that specify /// zero or more endpoint mungers. You may also pass <c>null</c>. /// </param> /// <param name="targetGroup">Optional dispatch target grouping instance (or <c>null</c>).</param> /// <remarks> /// <para> /// Calling this method will result in the GUID being updated and /// then call the associated router's <see cref="MsgRouter.OnLogicalEndpointSetChange"/> /// method. /// </para> /// <para> /// The <paramref name="targetGroup" /> parameter can be used to group /// together message dispatch handlers implemented by a different /// target object instances. This functionality is important when /// the impleentation of message type specific handlers is spread /// across multiple target classes. /// </para> /// <para> /// An example of this use is how the <see cref="ClusterMember" /> /// class' <see cref="ClusterMember.AddTarget" /> method to group the /// new target's message handler with the <see cref="ClusterMember" /> /// handlers since they'll share the same logical endpoint. /// </para> /// <para> /// If <paramref name="targetGroup" /> is passed as <c>null</c> then /// separate routes will be maintained for each target instance resulting /// in messages be load balanced randomly across the instances. /// </para> /// </remarks> public void AddTarget(object target, IEnumerable <ScopeMunger> mungers, object targetGroup) { const string msgBadMethodSignature = "Illegal [MsgHandler] method signature for [{0}.{1}()]: {2}."; var scopeToMunger = new Dictionary <string, IDynamicEPMunger>(StringComparer.OrdinalIgnoreCase); System.Type targetType = target.GetType(); System.Type msgType; MethodInfo[] methods; MsgHandler duplicate; ParameterInfo[] args; object[] handlerAttrs; object[] sessionAttrs; SessionHandlerInfo sessionInfo; bool fNewID; if (mungers != null) { foreach (var item in mungers) { scopeToMunger[item.DynamicScope] = item.Munger; } } using (TimedLock.Lock(syncLock)) { fNewID = false; methods = targetType.GetMethods(BindingFlags.Instance | BindingFlags.Public); foreach (MethodInfo method in methods) { // Ignore methods not tagged with [MsgHandler]. handlerAttrs = method.GetCustomAttributes(typeof(MsgHandlerAttribute), false); if (handlerAttrs.Length == 0) { continue; } // Get the handler's session information (if any). sessionAttrs = method.GetCustomAttributes(typeof(MsgSessionAttribute), false); if (sessionAttrs.Length == 0) { sessionInfo = null; } else { sessionInfo = new SessionHandlerInfo((MsgSessionAttribute)sessionAttrs[0]); } // Validate the method signature: void OnMsg(Msg msg); args = method.GetParameters(); if (method.ReturnType.UnderlyingSystemType != typeof(void)) { throw new MsgException(msgBadMethodSignature, targetType.Name, method.Name, "Must return void."); } if (method.ReturnType.UnderlyingSystemType != typeof(void)) { throw new MsgException(msgBadMethodSignature, targetType.Name, method.Name, "Must return void."); } if (args.Length != 1) { throw new MsgException(msgBadMethodSignature, targetType.Name, method.Name, "Must accept a single parameter."); } if (!args[0].ParameterType.IsDerivedFrom(typeof(Msg))) { throw new MsgException(msgBadMethodSignature, targetType.Name, method.Name, "Parameter must derive from Msg."); } foreach (MsgHandlerAttribute attr in handlerAttrs) { if (attr.IsPhysicalHandler) { // Deal with [MsgHandler(Default=true)] message handlers if (attr.Default) { if (defPhysHandler != null) { if (defPhysHandler.Target == target && defPhysHandler.Method == method) { continue; // Looks like we reflected the same method twice } throw new MsgException("Multiple physical default message handlers: [{0}.{1}()] and [{2}.{3}()].", defPhysHandler.Target.GetType().Name, defPhysHandler.Method.Name, targetType.Name, method.Name); } defPhysHandler = new MsgHandler(target, method, args[0].ParameterType, null, sessionInfo); continue; } // Add the handler to the map, making sure that there's only one // handler per message type msgType = args[0].ParameterType; physHandlers.TryGetValue(msgType, out duplicate); if (duplicate != null) { if (duplicate.Target == target && duplicate.Method == method) { continue; // Looks like we reflected the same method twice } throw new MsgException("Multiple physical handlers defined for message type [{0}]: [{1}{2}()] and [{1}{3}()].", msgType.Name, targetType.Name, duplicate.Method.Name, method.Name); } physHandlers[msgType] = new MsgHandler(target, method, msgType, null, sessionInfo); } else { // This is a logical endpoint handler. string paramType = attr.Default ? DefaultHandler : args[0].ParameterType.FullName; MsgHandler handler = new MsgHandler(target, method, args[0].ParameterType, attr, sessionInfo); MsgEP handlerEP; handlerEP = attr.LogicalEP; if (attr.DynamicScope != null) { IDynamicEPMunger munger; if (scopeToMunger.TryGetValue(attr.DynamicScope, out munger)) { handlerEP = munger.Munge(attr.LogicalEP, handler); } else { continue; // Ignore this message handler } } if (!logicalRoutes.Add(new LogicalRoute(handlerEP, paramType, handler), paramType, targetGroup)) { if (attr.Default) { throw new MsgException("A default logical handler is already defined for [{0}].", attr.LogicalEP); } else { throw new MsgException("A logical message handler is already defined for endpoint [{0}] and message type [{1}].", attr.LogicalEP, paramType); } } if (!fNewID) { fNewID = true; logicalEndpointSetID = Helper.NewGuid(); } } } } } if (fNewID && router != null) { router.OnLogicalEndpointSetChange(logicalEndpointSetID); } }
/// <summary> /// Adds an explicit logical endpoint message handler to the dispatcher. /// </summary> /// <param name="callback">The handler callback.</param> /// <param name="logicalEP">The logical endpoint.</param> /// <param name="msgType">The message type to be associated with the handler.</param> /// <param name="defHandler"><c>true</c> if this is the default handler for the endpoint.</param> /// <param name="sessionInfo"> /// The session related information to be related to this handler (or <c>null</c>). /// </param> /// <param name="suppressAdvertise"><c>true</c> if the advertise messages should be suppressed for this endpoint.</param> /// <remarks> /// <para> /// Pass suppressAdvertise=<c>true</c> if the dispatcher and associated router to suppress /// the transmission of advertise messages to the other routers on the network. /// This is useful when multiple logical handlers are being added. Once the handlers /// have been added, <see cref="LogicalAdvertise"/> can be called to force the /// advertise. /// </para> /// <note> /// Methods registered as handlers via this method will have to declare /// their message parameter as the base <see cref="Msg" /> class so that the compiler can /// create <see cref="MsgHandlerDelegate" /> instances. The message parameter can then be /// cast to the proper message type within the handler method. /// </note> /// </remarks> public void AddLogical(MsgHandlerDelegate callback, MsgEP logicalEP, System.Type msgType, bool defHandler, SessionHandlerInfo sessionInfo, bool suppressAdvertise) { string msgTypeName; string key; MethodInfo method; MsgHandler handler; method = callback.Method; msgTypeName = msgType.FullName; key = defHandler ? DefaultHandler : msgTypeName; handler = new MsgHandler(callback.Target, method, msgType, null, sessionInfo); using (TimedLock.Lock(syncLock)) { if (!logicalRoutes.Add(new LogicalRoute(logicalEP, key, new MsgHandler(callback.Target, method, msgType, null, sessionInfo)), key)) { if (defHandler) { throw new MsgException("A default logical handler is already defined for [{0}].", logicalEP); } else { throw new MsgException("A logical message handler is alreay defined for endpoint [{0}] and message type [{1}].", logicalEP, msgType); } } if (!suppressAdvertise) { logicalEndpointSetID = Helper.NewGuid(); } } if (!suppressAdvertise && router != null) { router.OnLogicalEndpointSetChange(logicalEndpointSetID); } }
/// <summary> /// Adds a, explicit logical endpoint message handler to the dispatcher. /// </summary> /// <param name="callback">The handler callback.</param> /// <param name="logicalEP">The logical endpoint.</param> /// <param name="msgType">The message type to be associated with the handler.</param> /// <param name="defHandler"><c>true</c> if this is the default handler for the endpoint.</param> /// <param name="sessionInfo"> /// The session related information to be related to this handler (or <c>null</c>). /// </param> /// <remarks> /// <note> /// Methods registered as handlers via this method will have to declare /// their message parameter as the base Msg class so that the compiler can /// create MsgHandlerDelegate instances. The message parameter can then be /// cast to the proper message type within the handler method. /// </note> /// </remarks> public void AddLogical(MsgHandlerDelegate callback, MsgEP logicalEP, System.Type msgType, bool defHandler, SessionHandlerInfo sessionInfo) { AddLogical(callback, logicalEP, msgType, defHandler, sessionInfo, false); }