/// <summary> /// Ensure that messages in the low priority sending queue are sent in a timely manner, but in a way such that they do not interfere with /// high priority messages. /// </summary> private void SendThread() { while (!m_Disposed) { //Thread.Sleep(25); //Uncomment to simulate a really slow network. using (Synchronizer.Cookie cookie = Synchronizer.Lock(m_SendQueue)) { //find out if it is currently safe to send if (m_PendingOutboundMessageCount < m_MaxConcurrentSends) { SendParameters nextSendItem = null; using (Synchronizer.Lock(m_SendingParticipants)) { nextSendItem = (SendParameters)m_SendQueue.Dequeue(m_SendingParticipants, m_CurrentSlideID); } if (nextSendItem != null) { //Trace.WriteLine("SendNow: sequence=" + nextSendItem.MessageSequence.ToString()); //Trace.WriteLine("SendNow: CurrentSlide=" + m_CurrentSlideID.ToString() + // ";msg slide guid=" + nextSendItem.Tags.SlideID.ToString() + // ";IsPublicNode=" + nextSendItem.IsPublicNode.ToString(), ""); SendNow(nextSendItem); continue; } } // If nothing can be sent, wait for the queue's status to change. // This happens either when new items are inserted or m_PendingOutboundMessageCount is updated. cookie.Wait(); } } }
private void SendCallback(IAsyncResult ar) { SendState state = (SendState)ar.AsyncState; try { int len = state.SendSocket.EndSend(ar); if (len != state.SendParameters.Length) { //I don't know how this can happen, but just in case.. Debug.Assert(false, "Critical error: Server did not send the expected number of bytes. Buffer length = " + state.SendParameters.Length.ToString() + "; bytes sent = " + len.ToString() + "; remote endpoint = " + state.SendSocket.RemoteEndPoint.ToString()); } } catch (SocketException se) { Trace.WriteLine("SendCallback SocketException errorcode: " + se.ErrorCode.ToString(), this.GetType().ToString()); this.DisableClient(state.SendParameters.Id); } catch (ObjectDisposedException ode) { Trace.WriteLine("SendCallback: " + ode.Message, this.GetType().ToString()); this.DisableClient(state.SendParameters.Id); } catch (Exception e) { Trace.WriteLine(e.ToString(), this.GetType().ToString()); this.DisableClient(state.SendParameters.Id); } finally { using (Synchronizer.Cookie cookie = Synchronizer.Lock(this.m_SendQueue)) { using (Synchronizer.Lock(m_SendingParticipants)) { if (m_SendingParticipants.ContainsKey(state.SendParameters.Id)) { ((ReferenceCounter)m_SendingParticipants[state.SendParameters.Id]).Decrement(); } else { Debug.Assert(false, "Server send callback found a missing reference counter."); } --m_PendingOutboundMessageCount; Debug.Assert(m_PendingOutboundMessageCount >= 0, "Server send callback found a negative pending message count."); //Notify threads closing the socket if this is the last item in the socket queue. if (m_ClosingSockets.ContainsKey(state.SendSocket)) { if (((ReferenceCounter)m_SendingParticipants[state.SendParameters.Id]).IsZero) { ((ManualResetEvent)m_ClosingSockets[state.SendSocket]).Set(); } } // Notify the sender thread that the queue's status has changed. cookie.PulseAll(); } } } }
/// <summary> /// If a client reconnects, update the socket map and enable the client's queue /// </summary> /// <param name="id"></param> /// <param name="s"></param> internal void Reconnect(ClientData client, ulong lastMessage, ulong lastChunk) { using (Synchronizer.Lock(this.m_SocketMap)) { if (m_SocketMap.ContainsKey(client.Id)) { m_SocketMap[client.Id] = client.Socket; } } using (Synchronizer.Cookie cookie = Synchronizer.Lock(this.m_SendQueue)) { m_SendQueue.EnableClient(client, lastMessage, lastChunk); cookie.PulseAll(); } }
protected void FlusherThread() { using (Synchronizer.Cookie cookie = Synchronizer.Lock(this.m_Lock)) { for (int refresh = 0;;) { try { if (this.m_Disposed) { return; } // Wait a random amount of time (a different period each time). cookie.Wait(this.m_Random.Next(this.m_MaxRandomWait)); if (this.m_Disposed) { return; } foreach (Sets sets in this.m_Sets.Values) { // Send an RtpNackMessage containing the current nack set (if not empty). if (sets.Current != null) { this.m_Sender.SendNack(new RtpNackMessage(sets.SSRC, sets.Current)); sets.Flush(); } } // After a certain amount of time, copy the entire reference set // to the nack set. However, do this *after* flushing, so we wait // a little while to see if other clients will send the same NACK. if (++refresh > REFRESH_SCALE) { refresh = 0; foreach (Sets sets in this.m_Sets.Values) { sets.Refresh(); } } } catch (Exception e) { // Report the error, but do not break out of the thread. // TODO: Log the error to the system event log. Trace.Fail("RTPNackManager encountered an error: " + e.ToString(), e.StackTrace); } } } }
protected virtual void Dispose(bool disposing) { this.m_Disposed = true; if (disposing) { m_Instructor.Changed["CurrentDeckTraversal"].Remove(new PropertyEventHandler(OnCurrentDeckTraversalChanged)); if (m_DeckTraversal != null) { m_DeckTraversal.Changed["Current"].Remove(new PropertyEventHandler(OnTableOfContentsEntryChanged)); } // Notify the sending thread that we're disposed. using (Synchronizer.Cookie cookie = Synchronizer.Lock(this.m_SendQueue)) cookie.PulseAll(); } }
/// <summary> /// The thread loop which executes queued events. /// </summary> /// <remarks> /// The loop iterates each time <c>Monitor.Pulse(this.m_Queue)</c> is called. /// It is expected that this will <em>only</em> happen when either <see cref="Invoker"/>s /// are added to the queue <em>or</em> <see cref="Dispose"/> is called. /// </remarks> private void ProcessMessages() { Synchronizer.Cookie cookie = Synchronizer.Lock(this.m_Queue); try { while (!this.m_Disposed) { try { // Wait until we have a message in the queue. if (this.m_Queue.Count <= 0) { cookie.Wait(); } // Halt the thread as soon as the ThreadEventQueue is disposed. if (this.m_Disposed) { return; } // Monitor.Pulse is only called either when the ThreadEventQueue is disposed // or when a message is added to the queue. Debug.Assert(this.m_Queue.Count > 0); Invoker invoker = this.m_Queue.Dequeue(); // Temporarily release the lock so other threads can add things to the // queue while the message is processing. This also avoids potential deadlocks. cookie.Dispose(); try { invoker.Method(); } finally { cookie = Synchronizer.Lock(this.m_Queue); } } catch (Exception e) { // Report the error, but do not break out of the thread. // TODO: Log the error to the system event log. Trace.Fail("The ThreadEventQueue encountered an error: " + e.ToString(), e.StackTrace); } } } finally { cookie.Dispose(); } }
/// <summary> /// Enqueue the send to occur when network bandwidth is available. /// </summary> /// <param name="buffer"></param> /// <param name="length"></param> /// <param name="socket"></param> /// <param name="participant"></param> internal void Send(byte[] buffer, int length, ClientData client, MessageTags tags, ulong messageSequence, ulong chunkSequence, bool isHeartbeat) { using (Synchronizer.Lock(m_SocketMap)) { Debug.Assert(m_SocketMap.ContainsKey(client.Id), "TCPServerSender.Send found a missing Socket Map entry."); } //Trace.WriteLine("TCPServerSender.Send seq=" + messageSequence.ToString()); //Enqueue the message using (Synchronizer.Cookie cookie = Synchronizer.Lock(m_SendQueue)) { m_SendQueue.Enqueue(new SendParameters(buffer, length, client.Id, tags, messageSequence, chunkSequence, isHeartbeat), client.Participant); // Notify the sender thread that the queue's status has changed. cookie.PulseAll(); } }
protected virtual void Dispose(bool disposing) { this.m_Disposed = true; if (disposing) { using (Synchronizer.Cookie cookie = Synchronizer.Lock(this.m_Lock)) { if (this.m_Disposed) { return; } this.m_Disposed = true; this.m_Classroom.Changed["Participants"].Remove(new PropertyEventHandler(this.HandleClassroomParticipantsChanged)); // Setting m_Disposed to true will cause the thread to stop, once we pulse the monitor. Monitor.Pulse(this.m_Lock); } } }
/// <summary> /// Stops executing queued events, stops the event thread, and releases any /// resources used by the <see cref="ThreadEventQueue"/>. /// </summary> /// <param name="disposing"></param> protected override void Dispose(bool disposing) { try { if (disposing) { using (Synchronizer.Cookie cookie = Synchronizer.Lock(this.m_Queue)) { if (this.m_Disposed) { return; } this.m_Disposed = true; // Setting m_Disposed to true will cause the ProcessMessages thread to stop, once we pulse the monitor. Monitor.Pulse(this.m_Queue); } } } finally { base.Dispose(disposing); } }
private void BeaconSenderThreadStart() { while (true) { try { using (Synchronizer.Cookie cookie = Synchronizer.Lock(this)) { if (this.m_Disposed) { return; } // Get the time remaining before sending the next beacon. TimeSpan interval = this.BeaconInterval; long remaining = interval.Ticks; // Get the current time so we can see how much time we've actually waited. long start = DateTime.Now.Ticks; do { // Wait until either the time expired, or until Monitor.Pulse(this) // indicates that the BeaconService's state has changed. cookie.Wait(new TimeSpan(remaining)); // Return immediately if we've been disposed. if (this.m_Disposed) { return; } // If the interval was previously infinite, // restart the timer from scratch with the new interval. if (remaining < 0) { remaining = this.BeaconInterval.Ticks; continue; } // Subtract the actual time elapsed while waiting // (this will be less than "remaining" if we were Pulsed). remaining -= (DateTime.Now.Ticks - start); // In case the interval has changed, get the new interval. TimeSpan replaced = this.BeaconInterval; if (replaced.Ticks < 0) { // Special case if the new interval is infinite; // otherwise the math is wrong since -1 milliseconds is a special value. remaining = replaced.Ticks; continue; } else { // Add or subtract the difference between the new and old intervals. // If the new interval is longer, this increases the time remaining. // if the new interval is shorter, this decreases the time remaining. remaining += (replaced.Ticks - interval.Ticks); interval = replaced; } // As long as we've got more time before the next beacon, repeat. } while(remaining > 0); Message message = this.MakeBeaconMessage(); if (message != null) { // Ideally, the beacon message wouldn't be subject to rebroadcast if dropped. // It's periodic, so subsequent beacons will replace any dropped frames. // But we want beacon messages to trigger rebroadcast of *other* dropped frames. // Also, the beacon messages should be processed in the same order as other // messages dealing with the state of the presentation, to avoid timing issues. // Therefore, beacon messages are sent with Default priority instead of RealTime. this.m_Sender.Send(message, MessagePriority.Default); } } } catch (Exception e) { Trace.WriteLine("BeaconService encountered an error: " + e.ToString() + "\r\n" + e.StackTrace); } } }