private void TheWSSenderThread() { if (IsSenderThreadRunning /*|| MyTargetNodeChannel==null*/) { return; } if (MyTargetNodeChannel == null) { TheBaseAssets.MySYSLOG.WriteToLog(2371, TSM.L(eDEBUG_LEVELS.OFF) ? null : new TSM("WSQueuedSender", $"WSSender Thread could not be started because MTNC is null", eMsgLevel.l4_Message)); return; } TheDiagnostics.SetThreadName($"QSender:{MyTargetNodeChannel} WSSenderThread", true); IsSenderThreadRunning = true; mre = new ManualResetEvent(false); CloudCounter++; StringBuilder tSendBufferStr = new StringBuilder(TheBaseAssets.MyServiceHostInfo?.IsMemoryOptimized == true ? 1024 : TheBaseAssets.MAX_MessageSize[(int)MyTargetNodeChannel.SenderType] * 2); int QDelay = TheCommonUtils.CInt(TheBaseAssets.MySettings.GetSetting("ThrottleWS")); if (QDelay < 2) { QDelay = 2; } if (MyTargetNodeChannel.SenderType == cdeSenderType.CDE_JAVAJASON && TheBaseAssets.MyServiceHostInfo.WsJsThrottle > QDelay) { QDelay = TheBaseAssets.MyServiceHostInfo.WsJsThrottle; } if (eventSenderThreadRunning != null) { TheCommonUtils.cdeRunAsync("EventSenderThreadRunning", true, (p) => { eventSenderThreadRunning(this); }); } MyISBlock?.FireEvent("SenderThreadCreated"); try { while (TheBaseAssets.MasterSwitch && IsAlive && MyWebSocketProcessor.IsActive && IsSenderThreadRunning) { while (TheBaseAssets.MasterSwitch && IsAlive && MyWebSocketProcessor.IsActive && IsSenderThreadRunning && (MyTargetNodeChannel.MySessionState == null || IsConnecting || IsInWSPost || !MyWebSocketProcessor.ProcessingAllowed || MyCoreQueue.Count == 0)) { if (IsInWSPost && MyCoreQueue.Count > 200) { TheBaseAssets.MySYSLOG.WriteToLog(235, new TSM("WSQueuedSender", $"IsInWSPost was still on and has been reset for {MyTargetNodeChannel?.ToMLString()}", eMsgLevel.l2_Warning)); IsInWSPost = false; } if (MyTargetNodeChannel.MySessionState == null && !IsConnecting && MyCoreQueue.Count > 0) { break; } mre.WaitOne(QDelay); // System.Diagnostics.Debug.WriteLine($"Waiting :{IsAlive} {MyCoreQueue.Count} {IsConnected} {IsConnecting} {IsInWSPost} {MyWebSocketProcessor.ProcessingAllowed}"); } if (!TheBaseAssets.MasterSwitch || !IsAlive || !MyWebSocketProcessor.IsActive || MyTargetNodeChannel.MySessionState == null) { TheBaseAssets.MySYSLOG.WriteToLog(235, TSM.L(eDEBUG_LEVELS.ESSENTIALS) ? null : new TSM("WSQueuedSender", $"WSSender Thread Condition failed - Ending SenderThread IsAlive:{IsAlive} MyWSProccAlive:{(MyWebSocketProcessor == null ? false : MyWebSocketProcessor.IsActive)},SessionState:{(MyTargetNodeChannel?.MySessionState != null)} IsConnecting:{IsConnecting} IsConnected:{IsConnected}", eMsgLevel.l2_Warning)); break; } IsInWSPost = true; int MCQCount = 0; int IsBatchOn = 0; int FinalCnt = 0; #if CDE_NET35 tSendBufferStr = new StringBuilder(TheBaseAssets.MAX_MessageSize[(int)MyTargetNodeChannel.SenderType] * 2); #else tSendBufferStr.Clear(); #endif tSendBufferStr.Append("["); do { TheCoreQueueContent tQueued = GetNextMessage(out MCQCount); if (tQueued != null) { TheDeviceMessage tDev = new TheDeviceMessage(); if (tQueued.OrgMessage != null) { if (tQueued.OrgMessage.ToCloudOnly() && MyTargetNodeChannel.SenderType == cdeSenderType.CDE_CLOUDROUTE) { tQueued.OrgMessage.SetToCloudOnly(false); } tQueued.OrgMessage.GetNextSerial(tQueued.SubMsgCnt); tDev.MSG = tQueued.OrgMessage; } var tCurSessState = MyTargetNodeChannel.MySessionState; if (MyTargetNodeChannel.SenderType == cdeSenderType.CDE_JAVAJASON) { if (tDev.MSG == null) { // we have a pickup: never send to browser if (IsBatchOn > 0) { continue; //ignore HB as other messages are already in the queue } else { IsInWSPost = false; continue; //dont sent empty Message to Browser - HB not necessary } } else { tDev.MSG.SEID = null; //SECURITY: Don't send SEID to Browser tDev.MSG.UID = null; //SECURITY: Don't send SEID to Browser tDev.MSG.SID = null; //SECURITY: Don't send SID to Browser } tDev.DID = MyTargetNodeChannel.cdeMID.ToString(); } else { if (tDev.MSG == null) // CODE REVIEW: DO we also need to detect other simple topics with response message and ensure they get into their own batch? { // we have a pickup (only to be sent if nothing else is being sent) if (IsBatchOn > 0) { // Ignore pickups if another message is already being sent (can happen with race condition between check before pickup enqueue and dequeue here - another TSM could be enqueue in that time) continue; } else { // Close the batch here so that no other TSM gets into the same batch // Possible optimization: check if other TSMs are available and skip the pickup here as well IsBatchOn = -1; // IsInWSPost = false; } } tDev.DID = TheBaseAssets.MyServiceHostInfo.MyDeviceInfo.DeviceID.ToString(); if (!string.IsNullOrEmpty(tQueued?.OrgMessage?.SID)) { tDev.SID = tQueued.OrgMessage.SID; } else { if (TheBaseAssets.MyServiceHostInfo.EnableFastSecurity) { tDev.SID = tCurSessState.SScopeID; //SECURITY: All tDevs will have same Session Scrambled ScopeID - 4.209: SID from TSM if set. } else { tDev.SID = TheBaseAssets.MyScopeManager.GetScrambledScopeID(tCurSessState.SScopeID, false); //GRSI: high frequency } } } TheCDEKPIs.IncrementKPI(eKPINames.QSSent); tDev.TOP = tQueued.Topic; tDev.FID = tCurSessState.GetNextSerial().ToString(); if (TheCommonUtils.IsDeviceSenderType(MyTargetNodeChannel.SenderType)) //IDST-OK: Must create RSA for Devices { TheCommonUtils.CreateRSAKeys(tCurSessState); if (TheBaseAssets.MyServiceHostInfo.SecurityLevel > 3) { tDev.RSA = tCurSessState.RSAPublic; //TODO: Make depending on switch (HighSecurity=RSAGeneration every three seconds } } if (MyTargetNodeChannel.SenderType != cdeSenderType.CDE_CLOUDROUTE) //CODE-REVIEW: 4.0113 is this still valid: Must not reset HB if talking to cloud due to Cloud-Disconnect issue { ResetHeartbeatTimer(false, tCurSessState); } if (!cdeSenderType.CDE_JAVAJASON.Equals(MyTargetNodeChannel.SenderType)) { tDev.NPA = TheBaseAssets.MyScopeManager.GetISBPath(TheBaseAssets.MyServiceHostInfo.RootDir, MyTargetNodeChannel.SenderType, TheBaseAssets.MyServiceHostInfo.MyDeviceInfo.SenderType, tCurSessState.FID, tCurSessState.cdeMID, true); } if (MyTargetNodeChannel.MySessionState == null || MyTargetNodeChannel.MySessionState.HasExpired) { throw new Exception($"Session was deleted or has expired ({MyTargetNodeChannel?.MySessionState?.HasExpired})"); } #region Batch Serialization IsBatchOn++; FinalCnt++; var tToAdd = TheCommonUtils.SerializeObjectToJSONString(tDev); if (MCQCount == 0 || tQueued.IsChunked || IsBatchOn > TheBaseAssets.MyServiceHostInfo.MaxBatchedTelegrams) { if (MCQCount != 0) { tDev.CNT = MCQCount; } IsBatchOn = 0; } else { if (tSendBufferStr.Length + tToAdd.Length > TheBaseAssets.MAX_MessageSize[(int)MyTargetNodeChannel.SenderType]) { tDev.CNT = MCQCount; IsBatchOn = 0; } } if (tSendBufferStr.Length > 1) { tSendBufferStr.Append(","); } tSendBufferStr.Append(tToAdd); #endregion } else { IsBatchOn = 0; } } while (IsBatchOn > 0 && IsInWSPost && TheBaseAssets.MasterSwitch); if (!IsInWSPost || tSendBufferStr.Length < 2) { IsInWSPost = false; continue; } tSendBufferStr.Append("]"); if (FinalCnt > 1) { TheBaseAssets.MySYSLOG.WriteToLog(235, TSM.L(eDEBUG_LEVELS.FULLVERBOSE) ? null : new TSM("WSQueuedSender", $"Batched:{FinalCnt}", eMsgLevel.l3_ImportantMessage)); } if (!cdeSenderType.CDE_JAVAJASON.Equals(MyTargetNodeChannel.SenderType)) { MyWebSocketProcessor.PostToSocket(null, TheCommonUtils.cdeCompressString(tSendBufferStr.ToString()), true, false); } else { MyWebSocketProcessor.PostToSocket(null, TheCommonUtils.CUTF8String2Array(tSendBufferStr.ToString()), false, false); } IsInWSPost = false; } TheBaseAssets.MySYSLOG.WriteToLog(235, TSM.L(eDEBUG_LEVELS.ESSENTIALS) ? null : new TSM("WSQueuedSender", $"WSQSenderThread was closed for {MyTargetNodeChannel?.ToMLString()} IsAlive:{IsAlive} MyWSProccAlive:{(MyWebSocketProcessor == null ? "Is Null" : MyWebSocketProcessor?.IsActive.ToString())},SessionState:{(MyTargetNodeChannel?.MySessionState != null)} IsConnecting:{IsConnecting} IsConnected:{IsConnected}", eMsgLevel.l1_Error)); } catch (Exception e) { TheBaseAssets.MySYSLOG.WriteToLog(235, TSM.L(eDEBUG_LEVELS.OFF) ? null : new TSM("WSQueuedSender", "Exception in WSSenderThread.", eMsgLevel.l1_Error, "Error:" + e)); } finally { IsInWSPost = false; IsSenderThreadRunning = false; StopHeartBeat(); } if (IsAlive || (MyWebSocketProcessor != null && MyWebSocketProcessor.IsActive)) { IsAlive = false; if (MyWebSocketProcessor != null) { MyWebSocketProcessor.Shutdown(true, "1310:SenderThread Closed"); } } CloudCounter--; }
internal static void SetResponseBuffer(TheRequestData pRequestData, TheChannelInfo pChannelInfo, bool pSendPulse, string NopTopic, Guid owner, string pRefreshToken) { if (string.IsNullOrEmpty(NopTopic)) { TheQueuedSender tQ = null; if (pChannelInfo != null && pChannelInfo.cdeMID != Guid.Empty) //Send to Device First { tQ = TheQueuedSenderRegistry.GetSenderByGuid(pChannelInfo.cdeMID); } if (tQ != null) { if (pRequestData.WebSocket != null) { if (tQ.GetQueLength() == 0) { tQ.SendPickupMessage(); } return; } tQ.GetNextBackChannelBuffer(pRequestData); } } if (string.IsNullOrEmpty(NopTopic) && pRequestData.WebSocket != null) { return; //NEW:3.084 && !pSendPulse removed NEW:V3B3:2014-7-22 removed && pRequestData.ResponseBuffer != null } if (pRequestData.ResponseBuffer == null && pChannelInfo != null && (pSendPulse || (pChannelInfo.cdeMID != Guid.Empty || TheQueuedSenderRegistry.IsNodeIdInSenderList(pChannelInfo.cdeMID)))) { TheDeviceMessage tDev = new TheDeviceMessage { CNT = 0 }; //tDev.MET = 0; if (!string.IsNullOrEmpty(NopTopic)) { tDev.MSG = new TSM(); //Can be set without ORG and SID if (owner != Guid.Empty) { tDev.MSG.OWN = owner.ToString(); } if (NopTopic == "CDE_WSINIT") { NopTopic = TheCommCore.SetConnectingBufferStr(pChannelInfo, null); } } if (pChannelInfo.SenderType != cdeSenderType.CDE_JAVAJASON) //4.209: No longer sending SID to Browser; { if (TheBaseAssets.MyServiceHostInfo.EnableFastSecurity) { tDev.SID = pRequestData.SessionState.SScopeID; //SECURITY: All responses will have same Scrambled ScopeID - but ok because this is init telegram or HB } else { tDev.SID = TheBaseAssets.MyScopeManager.GetScrambledScopeID(pRequestData.SessionState.SScopeID, false); //GRSI: high frequency } } else { if (!string.IsNullOrEmpty(pRefreshToken)) { tDev.SID = pRefreshToken; } } tDev.FID = pRequestData.SessionState.GetNextSerial().ToString(); if (TheCommonUtils.IsDeviceSenderType(pChannelInfo.SenderType)) //IDST-OK: Must create RSA for Devices { TheCommonUtils.CreateRSAKeys(pRequestData.SessionState); tDev.RSA = pRequestData.SessionState.RSAPublic; } tDev.NPA = TheBaseAssets.MyScopeManager.GetISBPath(TheBaseAssets.MyServiceHostInfo.RootDir, pChannelInfo.SenderType, TheBaseAssets.MyServiceHostInfo.MyDeviceInfo.SenderType, pRequestData.SessionState.FID, pRequestData.SessionState.cdeMID, pRequestData.WebSocket != null); tDev.CNT = pSendPulse ? 1 : 0; tDev.TOP = NopTopic; tDev.DID = pChannelInfo.SenderType == cdeSenderType.CDE_JAVAJASON ? pChannelInfo.cdeMID.ToString() : TheBaseAssets.MyServiceHostInfo.MyDeviceInfo.DeviceID.ToString(); //There will be only one Message here - single poke or Mini Command or NOP Pickup List <TheDeviceMessage> tDevList = new List <TheDeviceMessage> { tDev }; if (pChannelInfo.SenderType == cdeSenderType.CDE_JAVAJASON || TheBaseAssets.MyServiceHostInfo.MyDeviceInfo.SenderType == cdeSenderType.CDE_MINI || pChannelInfo.SenderType == cdeSenderType.CDE_MINI || pRequestData.WebSocket != null) { pRequestData.ResponseBuffer = TheCommonUtils.CUTF8String2Array(TheCommonUtils.SerializeObjectToJSONString(tDevList)); pRequestData.ResponseMimeType = "application/json"; } else { pRequestData.ResponseBuffer = TheCommonUtils.cdeCompressString(TheCommonUtils.SerializeObjectToJSONString(tDevList)); pRequestData.ResponseMimeType = "application/x-gzip"; } } if (pRequestData.ResponseBuffer == null && pChannelInfo != null && pChannelInfo.cdeMID != Guid.Empty) { TheBaseAssets.MySYSLOG.WriteToLog(290, TSM.L(eDEBUG_LEVELS.FULLVERBOSE) ? null : new TSM("CoreComm", $"Nothing to Send Back to {pChannelInfo?.ToMLString()}", eMsgLevel.l7_HostDebugMessage)); } }