示例#1
0
        private static void Test()
        {
            // Make a Random instance with a fixed seed so that tests are repeatable.
            Random      random = new Random(0);
            DisjointSet test   = null;

            // Add a single range.
            DisjointSet.Add(ref test, new Range(5, 10));

            // Verify that DisjointSet.Contains works for each value in the range.
            for (ulong i = 0; i <= 15; i++)
            {
                Debug.Assert(!(DisjointSet.Contains(ref test, i) ^ (5 <= i && i <= 10)),
                             "DisjointSet.Contains does not work (simple case).");
            }

            // Verify that it works even when the set is splayed randomly.
            for (ulong j = 0; j <= 100; j++)
            {
                ulong i = (ulong)random.Next(0, 15);
                Debug.Assert(!(DisjointSet.Contains(ref test, i) ^ (5 <= i && i <= 10)),
                             "DisjointSet.Contains does not work (simple case, random splaying).");
            }

            // Add new ranges that overlap the existing range(s).
            DisjointSet.Add(ref test, new Range(4, 9));
            DisjointSet.Add(ref test, new Range(15, 20));
            DisjointSet.Add(ref test, new Range(10, 15));

            // Verify that the ranges were merged.
            Debug.Assert(test.Left == null && test.Right == null,
                         "DisjointSet.Add did not merge overlapping ranges.");
            Debug.Assert(test.Value == new Range(4, 20),
                         "DisjointSet.Add did not merge overlapping ranges.");

            // Add ranges adjacent to the ends of the existing range.
            DisjointSet.Add(ref test, new Range(21, 25));
            DisjointSet.Add(ref test, 3ul);

            // Verify that they were also merged.
            Debug.Assert(test.Left == null && test.Right == null,
                         "DisjointSet.Add did not merge adjacent ranges.");
            Debug.Assert(test.Value == new Range(3, 25),
                         "DisjointSet.Add did not merge adjacent ranges.");

            // Remove a range.
            DisjointSet.Remove(ref test, new Range(10, 15));

            // Verify that the ranges still work.
            for (ulong i = 0; i <= 30; i++)
            {
                Debug.Assert(!(DisjointSet.Contains(ref test, i)
                               ^ ((3 <= i && i <= 9) || (16 <= i && i <= 25))),
                             "DisjointSet.Contains does not work after removal.");
            }

            // Verify that it works even when the set is splayed randomly.
            for (ulong j = 0; j <= 300; j++)
            {
                ulong i = (ulong)random.Next(0, 30);
                Debug.Assert(!(DisjointSet.Contains(ref test, i)
                               ^ ((3 <= i && i <= 9) || (16 <= i && i <= 25))),
                             "DisjointSet.Contains does not work after removal (random splaying).");
            }

            // Remove the entire split ranges (on a cloned copy so we can do it several times).
            DisjointSet empty;

            empty = test.Clone();
            DisjointSet.Remove(ref empty, Range.UNIVERSE);
            Debug.Assert(empty == null,
                         "DisjointSet.Remove(UNIVERSE) didn't result in an empty set.");

            empty = test.Clone();
            DisjointSet.Remove(ref empty, new Range(3, 25));
            Debug.Assert(empty == null,
                         "DisjointSet.Remove of entire range didn't result in an empty set.");

            empty = test.Clone();
            DisjointSet.Remove(ref empty, new Range(3, 26));
            Debug.Assert(empty == null,
                         "DisjointSet.Remove of entire range didn't result in an empty set.");

            empty = test.Clone();
            DisjointSet.Remove(ref empty, new Range(2, 25));
            Debug.Assert(empty == null,
                         "DisjointSet.Remove of entire range didn't result in an empty set.");

            /////////////////////////////////////////////////////////////////////////

            // Now reset the set so we can do some stress tests (mostly to make
            // sure the splaying code works correctly).
            test = null;

            // Insert all integers divisible by 5, 7, or 11.  This makes the ranges
            // easily testable, and results in a set with some singletons as well
            // as some contiguous ranges.
            for (ulong i = 0; i < 1000; i++)
            {
                if (i % 5 == 0 || i % 7 == 0 || i % 11 == 0)
                {
                    DisjointSet.Add(ref test, i);
                }
            }

            // Verify the contents of the set.
            for (ulong i = 0; i < 1000; i++)
            {
                Debug.Assert(!(DisjointSet.Contains(ref test, i) ^ (i % 5 == 0 || i % 7 == 0 || i % 11 == 0)),
                             "DisjointSet.Contains failed the stress test.");
            }

            // Verify the contents of the set, even with random splaying.
            for (ulong j = 0; j < 10000; j++)
            {
                ulong i = (ulong)random.Next(0, 1000);
                Debug.Assert(!(DisjointSet.Contains(ref test, i) ^ (i % 5 == 0 || i % 7 == 0 || i % 11 == 0)),
                             "DisjointSet.Contains failed the stress test (random splaying).");
            }

            // Now remove all singletons divisible by 7.
            for (ulong i = 0; i < 1000; i++)
            {
                if (i % 7 == 0)
                {
                    DisjointSet.Remove(ref test, i);
                }
            }

            // Verify the contents of the set.
            for (ulong i = 0; i < 1000; i++)
            {
                Debug.Assert(!(DisjointSet.Contains(ref test, i) ^ ((i % 5 == 0 || i % 11 == 0) && (i % 7 != 0))),
                             "DisjointSet.Contains failed the stress test.");
            }

            // Verify the contents of the set, even with random splaying.
            for (ulong j = 0, i; j < 10000; j++)
            {
                i = (ulong)random.Next(0, 1000);
                Debug.Assert(!(DisjointSet.Contains(ref test, i) ^ ((i % 5 == 0 || i % 11 == 0) && (i % 7 != 0))),
                             "DisjointSet.Contains failed the stress test (random splaying).");
            }

            // Test the IEnumerator implementation.
            ArrayList enums = new ArrayList();
            ulong     prev  = 0;

            foreach (ulong i in test)
            {
                Debug.Assert(i >= prev, "The enumeration is not well ordered.");
                enums.Add(prev = i);
            }

            // Verify that the output of the enumerator is exactly what should be in the set.
            for (ulong i = 0; i < 1000; i++)
            {
                Debug.Assert(!(enums.Contains(i) ^ ((i % 5 == 0 || i % 11 == 0) && (i % 7 != 0))),
                             "The enumeration did not properly enumerate the set.");
            }

            // Test removing from a big tree.
            DisjointSet.Remove(ref test, Range.UNIVERSE);
            Debug.Assert(test == null, "DisjointSet.Remove(UNIVERSE) didn't yield an empty set.");


            /////////////////////////////////////////////////////////////////////////

            // Reset the test and add a lot of small random ranges.  This simulates
            // the kind of behavior experienced by RTPNetworkReceiver.

            test = null;
            ArrayList values = new ArrayList(10000);

            for (ulong i = 0; i < 10000; i++)
            {
                Range r = i;

                // Expand the range by some random number of indices.
                while (random.Next(2) == 1)
                {
                    r = new Range(r.Min, ++i);
                }

                // Add all the indices in the range to both sets.
                DisjointSet.Add(ref test, r);
                for (ulong v = r.Min; v <= r.Max; v++)
                {
                    values.Add(v);
                }

                // Skip some random number of indices.
                while (random.Next(2) == 1)
                {
                    i++;
                }
            }

            // Test removing all of the values from the set in randomized ascending order,
            // just like what happens when receiving responses to a NACK (ie., some frames
            // might be dropped twice (or more times), and then NACKed again).
            DisjointSet copy = test.Clone();

            for (ArrayList compare = (ArrayList)values.Clone(); compare.Count > 0;)
            {
                for (int i = 0; i < compare.Count;)
                {
                    Debug.Assert(copy != null, "DisjointSet is empty but not all entries have been removed.");

                    // Skip some random number of entries.
                    while (random.Next(2) == 1)
                    {
                        i++;
                    }
                    if (i >= compare.Count)
                    {
                        break;
                    }

                    // Remove the entries one at a time.
                    ulong v = (ulong)compare[i];
                    DisjointSet.Remove(ref copy, v);
                    compare.RemoveAt(i);
                }
            }
            Debug.Assert(copy == null, "All entries were removed but DisjointSet is not empty.");

            // Test that all of the entries were actually added to the set.
            foreach (ulong v in test)
            {
                Debug.Assert(values.Contains(v), "DisjointSet contains a value that it shouldn't.");
            }
            foreach (ulong v in values)
            {
                Debug.Assert(DisjointSet.Contains(ref test, v), "DisjointSet doesn't contain a value that it should.");
            }

            /////////////////////////////////////////////////////////////////////////

            // Simulate an error-case discovered during actual testing.
            // See Bug 821, attachment #45 (comment #3).

            DisjointSet current = null;

            DisjointSet.Add(ref current, new Range(1, 4));
            DisjointSet.Add(ref current, new Range(6, 16));
            DisjointSet.Add(ref current, new Range(18, 20));
            DisjointSet.Add(ref current, new Range(22, 33));
            DisjointSet.Add(ref current, new Range(35, 55));
            DisjointSet.Add(ref current, new Range(57, 61));
            DisjointSet.Add(ref current, 64);
            DisjointSet.Add(ref current, 67);

            DisjointSet stable = null;

            DisjointSet.Add(ref stable, new Range(1, 16));
            DisjointSet.Add(ref stable, new Range(18, 61));
            DisjointSet.Add(ref stable, 64);
            DisjointSet.Add(ref stable, 67);
            copy = stable.Clone();

            test = new Range(1, 100);
            DisjointSet.Remove(ref test, current);
            foreach (ulong i in test)
            {
                DisjointSet.Remove(ref copy, i);
            }
            DisjointSet.Remove(ref copy, 0);

            DisjointSet.Remove(ref copy, stable);
            Debug.Assert(copy == null, "Stable set is not a superset of the Current set.");
        }
示例#2
0
        /// <summary>
        /// Attempts to assemble a chunk from a chunked messages,
        /// and sends a NACK when dropped chunks are detected.
        /// </summary>
        /// <param name="chunk">The received chunk to process</param>
        /// <returns>If the chunk was the last remaining chunk needed to complete a message,
        /// returns the deserialized message; otherwise, <code>null</code></returns>
        public IEnumerable <object> Add(Chunk chunk)
        {
            if (chunk.FrameSequence > 0)
            {
                // Check if we have seen this packet before. If so, do nothing.
                if (DisjointSet.Contains(ref this.m_ReceivedFrameSequences, chunk.FrameSequence))
                {
                    yield break;
                }
                DisjointSet.Add(ref this.m_ReceivedFrameSequences, chunk.FrameSequence);

                // Save space in the ReceivedSequenceNumbers queue by adding all chunks
                // that we can't expect to receive anymore.
                if (chunk.OldestRecoverableFrame > ulong.MinValue)
                {
                    DisjointSet.Add(ref this.m_ReceivedFrameSequences, new Range(ulong.MinValue, chunk.OldestRecoverableFrame - 1));
                }

                // Check the frame sequence number to see if any frames were skipped.
                // If so, then it is likely that the frame has been dropped, so it is
                // necessary to send a NACK.
                if (chunk.FrameSequence > this.m_GreatestFrameSequence + 1ul)
                {
                    //Debug.WriteLine(string.Format("Frames #{0}-{1} were dropped ({2} total)! Requesting replacements (oldest recoverable is #{3})...",
                    //    this.m_GreatestFrameSequence + 1ul, chunk.FrameSequence - 1ul, chunk.FrameSequence - this.m_GreatestFrameSequence - 1,
                    //    chunk.OldestRecoverableFrame), this.GetType().ToString());
                    if (this.Nack != null)
                    {
                        // Send a NACK which requests rebroadcast of all the skipped frames.
                        Range range = new Range(
                            Math.Max(this.m_GreatestFrameSequence + 1ul, chunk.OldestRecoverableFrame),
                            chunk.FrameSequence - 1ul);
                        this.Nack(range);
                    }
                }

                // Otherwise, don't do anything special.  These two branches are for debugging.
                else if (chunk.FrameSequence == this.m_GreatestFrameSequence)
                {
                    // Duplicate chunk received, or else the first chunk ever received.
                }
                else if (chunk.FrameSequence < this.m_GreatestFrameSequence && chunk.FrameSequence > ulong.MinValue)
                {
                    Debug.WriteLine(string.Format("%%% Frame #{0} (message #{1}, chunk #{2} of {3}, {4} bytes) received out of order (off by {5})",
                                                  chunk.FrameSequence,
                                                  chunk.MessageSequence,
                                                  chunk.ChunkSequenceInMessage + 1,
                                                  chunk.NumberOfChunksInMessage,
                                                  chunk.Data.Length,
                                                  this.m_GreatestFrameSequence - chunk.FrameSequence),
                                    this.GetType().ToString());
                }

                // Assuming the chunk has not been duplicated or received out of order,
                // update our variable containing the highest sequence number seen
                // so far so we know which future frames have been dropped.
                if (chunk.FrameSequence > this.m_GreatestFrameSequence)
                {
                    this.m_GreatestFrameSequence = chunk.FrameSequence;
                }
            }

            // If the message depends on a message that has not yet been received,
            // add it to the queue of chunks waiting for that message.
            // Then, STOP PROCESSING the message.
            // Assemble(chunk) will get called by FlushWaitingChunks after
            // the dependencies are satisfied.
            if (chunk.MessageDependency > ulong.MinValue && !DisjointSet.Contains(ref this.m_ReceivedMessages, chunk.MessageDependency))
            {
                List <Chunk> waiters;
                if (!this.m_ChunksWaitingForDependencies.TryGetValue(chunk.MessageDependency, out waiters))
                {
                    this.m_ChunksWaitingForDependencies[chunk.MessageDependency] = waiters = new List <Chunk>();
                }
                Debug.WriteLine(string.Format("@@@ Unsatisfied dependency: message #{0} (chunk #{1} of {2}) depends on #{3}; {4} chunks are already waiting.",
                                              chunk.MessageSequence,
                                              chunk.ChunkSequenceInMessage + 1,
                                              chunk.NumberOfChunksInMessage,
                                              chunk.MessageDependency, waiters.Count));
                waiters.Add(chunk);
                yield break;
            }

            // Flush any dependencies of messages we can no longer expect to recover.
            // Do this by finding the difference between the range of all unrecoverable
            // message sequence numbers and the sequence numbers that have been received.
            // FlushWaitingChunks is called on each of the unsatisfiable sequence numbers.
            if (chunk.OldestRecoverableMessage > ulong.MinValue)
            {
                Range       unrecoverable = new Range(ulong.MinValue, chunk.OldestRecoverableMessage - 1);
                DisjointSet unsatisfiable = unrecoverable;
                DisjointSet.Remove(ref unsatisfiable, this.m_ReceivedMessages);
                if (unsatisfiable != null)
                {
                    foreach (ulong unsatisfied in unsatisfiable)
                    {
                        this.FlushWaitingChunks(unsatisfied);
                    }
                }
                DisjointSet.Add(ref this.m_ReceivedMessages, unrecoverable);
            }

            // If the dependency is already satisfied (or not specified),
            // attempt to assemble the chunk into a completed message.
            foreach (object message in this.Assemble(chunk))
            {
                yield return(message);
            }
        }